Exercise feedback lookup failures, atomic retries and rollback ownership
This commit is contained in:
1 parent
6a4fa8065a
commit
1f19720cb8
1 file changed
+89
@@ -313,6 +313,94 @@ static void membership_regressions(struct UASYNC* ua) {
|
||||
assert(sqlite3_close(inviter.topo_sqlite_db) == SQLITE_OK);
|
||||
}
|
||||
|
||||
/* Реальный file DB позволяет проверить commit другим соединением, в том числе из события. */
|
||||
static struct UTUN_INSTANCE* feedback_reader;
|
||||
static struct chat_feedback_record event_record;
|
||||
static void fault_event(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len) {
|
||||
if (type != CHAT_EVT_FEEDBACK_CHANGED) return;
|
||||
assert(sqlite3_get_autocommit(inst->topo_sqlite_db) && len == 38 && data[0] == 4);
|
||||
assert_stored(feedback_reader, &event_record);
|
||||
uint8_t writer[32], reader[32]; root(inst, writer);
|
||||
assert(!merkle_tree_get(feedback_reader->topo_sqlite_db, "1:1001", 0, 0, reader) && !memcmp(reader, writer, 32)); events++;
|
||||
}
|
||||
|
||||
static void sql_fault_regressions(struct UASYNC* ua) {
|
||||
char path[] = "/tmp/utun-feedback-fault-XXXXXX"; int fd = mkstemp(path); assert(fd >= 0); close(fd);
|
||||
struct UTUN_INSTANCE author, target, reader; memset(&reader, 0, sizeof(reader));
|
||||
setup(&author, ua, NULL); setup(&target, ua, path);
|
||||
add_member(&author, &author, "1001"); add_member(&target, &author, "1001"); add_member(&target, &target, "1001");
|
||||
assert(sqlite3_open_v2(path, &reader.topo_sqlite_db, SQLITE_OPEN_READONLY, NULL) == SQLITE_OK);
|
||||
feedback_reader = &reader; chat_event_set_handler(&target, fault_event);
|
||||
uint8_t message[32] = {61}, reaction[] = {1, 0}, before[32], after[32]; uint64_t version;
|
||||
struct chat_feedback_record old, current, candidate;
|
||||
assert(chat_feedback_set(&author, "1001", message, CHAT_FEEDBACK_REACTION, reaction, sizeof(reaction), NULL) == 1);
|
||||
assert(chat_feedback_get(&author, "1001", message, author.node_id, CHAT_FEEDBACK_REACTION, &old) == 1);
|
||||
assert(chat_feedback_set(&author, "1001", message, CHAT_FEEDBACK_REACTION, NULL, 0, NULL) == 1);
|
||||
assert(chat_feedback_get(&author, "1001", message, author.node_id, CHAT_FEEDBACK_REACTION, ¤t) == 1);
|
||||
event_record = current; assert(chat_feedback_apply(&target, "1001", ¤t) == 1);
|
||||
int saved = events; root(&target, before);
|
||||
/* Ошибка поиска не должна воскрешать старую реакцию поверх более новой отмены. */
|
||||
injected_operation = F_GET; injected_bind = 1; injected = F_BIND;
|
||||
event_record = old;
|
||||
assert(chat_feedback_apply(&target, "1001", &old) < 0 && injected == F_NONE && events == saved);
|
||||
assert_stored(&reader, ¤t); root(&target, after); assert(!memcmp(before, after, 32));
|
||||
unsigned cases = 0;
|
||||
for (enum sql_fault point = F_BEGIN; point <= F_COMMIT; point++) {
|
||||
for (enum sql_operation operation = F_GET; operation <= F_PUT; operation++) {
|
||||
if (operation == F_GET && (point == F_BEGIN || point == F_CHANGES || point == F_COMMIT)) continue;
|
||||
int bindings = point == F_BIND ? (operation == F_GET ? 3 : 8) : 1;
|
||||
for (int bind = 1; bind <= bindings; bind++) {
|
||||
candidate = old; candidate.ver = current.ver + 1; sign_record(&author, 1001, &candidate);
|
||||
saved = events; root(&target, before); event_record = candidate;
|
||||
injected_operation = operation; injected_bind = bind; injected = point;
|
||||
assert(chat_feedback_apply(&target, "1001", &candidate) < 0 && injected == F_NONE && events == saved);
|
||||
assert(sqlite3_get_autocommit(target.topo_sqlite_db)); assert_stored(&reader, ¤t);
|
||||
root(&target, after); assert(!memcmp(before, after, 32));
|
||||
assert(chat_feedback_apply(&target, "1001", &candidate) == 1 && events == saved + 1);
|
||||
current = candidate; cases++;
|
||||
}
|
||||
}
|
||||
}
|
||||
const char* triggers[] = {
|
||||
"CREATE TEMP TRIGGER reject_feedback BEFORE INSERT ON feedback_1001 BEGIN SELECT RAISE(ABORT,'feedback'); END",
|
||||
"CREATE TEMP TRIGGER reject_feedback BEFORE INSERT ON feedback_1001 BEGIN SELECT RAISE(IGNORE); END"
|
||||
};
|
||||
for (unsigned i = 0; i < 2; i++) {
|
||||
candidate = current; candidate.ver++; sign_record(&author, 1001, &candidate); event_record = candidate;
|
||||
assert(sqlite3_exec(target.topo_sqlite_db, triggers[i], NULL, NULL, NULL) == SQLITE_OK); saved = events;
|
||||
assert(chat_feedback_apply(&target, "1001", &candidate) < 0 && events == saved); assert_stored(&reader, ¤t);
|
||||
assert(sqlite3_exec(target.topo_sqlite_db, "DROP TRIGGER reject_feedback", NULL, NULL, NULL) == SQLITE_OK);
|
||||
assert(chat_feedback_apply(&target, "1001", &candidate) == 1); current = candidate;
|
||||
}
|
||||
/* Настоящий veto откатывает запись и индекс, повтор успешно фиксирует оба. */
|
||||
candidate = current; candidate.ver++; sign_record(&author, 1001, &candidate); event_record = candidate;
|
||||
int vetoes = 1; sqlite3_commit_hook(target.topo_sqlite_db, veto_commit, &vetoes); saved = events;
|
||||
assert(chat_feedback_apply(&target, "1001", &candidate) < 0 && events == saved && vetoes == 0);
|
||||
sqlite3_commit_hook(target.topo_sqlite_db, NULL, NULL); assert_stored(&reader, ¤t);
|
||||
assert(chat_feedback_apply(&target, "1001", &candidate) == 1); current = candidate;
|
||||
/* Даже локальный no-op не должен принимать внешнюю транзакцию. */
|
||||
chat_event_set_handler(&target, on_event);
|
||||
assert(chat_feedback_set(&target, "1001", message, CHAT_FEEDBACK_REACTION, reaction, sizeof(reaction), &version) == 1);
|
||||
saved = events; assert(sqlite3_exec(target.topo_sqlite_db, "BEGIN;UPDATE channels SET name='pending' WHERE channel_id='1002'",
|
||||
NULL, NULL, NULL) == SQLITE_OK);
|
||||
version = UINT64_MAX;
|
||||
assert(chat_feedback_set(&target, "1001", message, CHAT_FEEDBACK_REACTION, reaction, sizeof(reaction), &version) < 0);
|
||||
uint8_t page[2 + FB_WIRE_MAX]; page[0] = 0; page[1] = 1;
|
||||
candidate = current; candidate.ver++; sign_record(&author, 1001, &candidate);
|
||||
int bytes = fb_encode(&candidate, page + 2); assert(bytes > 0);
|
||||
assert(fb_apply_page(&target, "1001", author.node_id, page, 2 + bytes) < 0);
|
||||
assert(!sqlite3_get_autocommit(target.topo_sqlite_db) && version == UINT64_MAX && events == saved);
|
||||
assert_stored(&reader, ¤t); assert(sqlite3_exec(target.topo_sqlite_db, "ROLLBACK", NULL, NULL, NULL) == SQLITE_OK);
|
||||
/* После неудачного rollback нельзя RELEASE и commit частичной страницы. */
|
||||
target.running = 1; injected_operation = F_PUT; injected = F_FINALIZE; failed_rollback = 1;
|
||||
assert(chat_feedback_apply(&target, "1001", &candidate) < 0 && injected == F_NONE && !failed_rollback);
|
||||
assert(!target.running && !sqlite3_get_autocommit(target.topo_sqlite_db) && events == saved); assert_stored(&reader, ¤t);
|
||||
member_sync_destroy(&target); assert(sqlite3_close(target.topo_sqlite_db) == SQLITE_OK); assert_stored(&reader, ¤t);
|
||||
member_sync_destroy(&author); assert(sqlite3_close(author.topo_sqlite_db) == SQLITE_OK);
|
||||
assert(sqlite3_close(reader.topo_sqlite_db) == SQLITE_OK); assert(!remove(path)); feedback_reader = NULL;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "PASS feedback:%u SQL faults/retries, stale-state lookup failure, ABORT/IGNORE/veto, foreign no-op/page, failed rollback stops owner; reader sees exact committed records/roots before events", cases);
|
||||
}
|
||||
|
||||
/* Диагностика копии профиля: без изменения записей или доступа к приватным ключам. */
|
||||
static int check_profile_authors(const char* path) {
|
||||
struct UTUN_INSTANCE inst = {0};
|
||||
@@ -445,6 +533,7 @@ int main(int argc, char** argv) {
|
||||
uint8_t empty[32] = {0}; assert(!memcmp(received, empty, 32));
|
||||
puts("PASS: deleting a channel removes its feedback table and derived root");
|
||||
membership_regressions(ua);
|
||||
sql_fault_regressions(ua);
|
||||
member_sync_destroy(&source); member_sync_destroy(&target);
|
||||
assert(sqlite3_close(source.topo_sqlite_db) == SQLITE_OK && sqlite3_close(target.topo_sqlite_db) == SQLITE_OK);
|
||||
uasync_poll(ua, 0); uasync_destroy(ua, 0);
|
||||
|
||||
Reference in new issue
Block a user