Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package dqlite for openSUSE:Factory checked in at 2022-08-11 18:32:07 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/dqlite (Old) and /work/SRC/openSUSE:Factory/.dqlite.new.1521 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "dqlite" Thu Aug 11 18:32:07 2022 rev:3 rq:994365 version:1.11.1 Changes: -------- --- /work/SRC/openSUSE:Factory/dqlite/dqlite.changes 2022-05-02 16:25:53.612859822 +0200 +++ /work/SRC/openSUSE:Factory/.dqlite.new.1521/dqlite.changes 2022-08-11 18:32:19.470262571 +0200 @@ -1,0 +2,10 @@ +Sat Jul 23 11:29:54 UTC 2022 - Andreas Stieger <andreas.stie...@gmx.de> + +- update to 1.11.1: + * Replace deprecated raft function raft_fixture_init with + raft_fixture_initialize +- includes changes from 1.11.0: + * Fix a bug that causes a node to crash under certain conditions + after losing leadership in the middle of a transaction + +------------------------------------------------------------------- Old: ---- dqlite-1.10.0.tar.gz New: ---- dqlite-1.11.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ dqlite.spec ++++++ --- /var/tmp/diff_new_pack.Vk7fDq/_old 2022-08-11 18:32:19.914263610 +0200 +++ /var/tmp/diff_new_pack.Vk7fDq/_new 2022-08-11 18:32:19.918263620 +0200 @@ -18,7 +18,7 @@ %define lname libdqlite0 Name: dqlite -Version: 1.10.0 +Version: 1.11.1 Release: 0 Summary: Distributed SQLite License: LGPL-3.0-only WITH LGPL-3.0-linking-exception ++++++ dqlite-1.10.0.tar.gz -> dqlite-1.11.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/configure.ac new/dqlite-1.11.1/configure.ac --- old/dqlite-1.10.0/configure.ac 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/configure.ac 2022-07-13 13:35:54.000000000 +0200 @@ -1,5 +1,5 @@ AC_PREREQ(2.60) -AC_INIT([libdqlite], [1.10.0], [https://github.com/canonical/dqlite]) +AC_INIT([libdqlite], [1.11.1], [https://github.com/canonical/dqlite]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_AUX_DIR([ac]) @@ -49,7 +49,7 @@ # Checks for libraries PKG_CHECK_MODULES(SQLITE, [sqlite3 >= 3.22.0], [], []) PKG_CHECK_MODULES(UV, [libuv >= 1.8.0], [], []) -PKG_CHECK_MODULES(RAFT, [raft >= 0.13.0], [], []) +PKG_CHECK_MODULES(RAFT, [raft >= 0.14.0], [], []) CC_CHECK_FLAGS_APPEND([AM_CFLAGS],[CFLAGS],[ \ -std=c11 \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/src/client.c new/dqlite-1.11.1/src/client.c --- old/dqlite-1.10.0/src/client.c 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/src/client.c 2022-07-13 13:35:54.000000000 +0200 @@ -365,6 +365,15 @@ return 0; } +int clientSendTransfer(struct client *c, unsigned id) +{ + tracef("client send transfer fd %d id %u", c->fd, id); + struct request_transfer request; + request.id = id; + REQUEST(transfer, TRANSFER); + return 0; +} + int clientRecvEmpty(struct client *c) { tracef("client recv empty fd %d", c->fd); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/src/client.h new/dqlite-1.11.1/src/client.h --- old/dqlite-1.10.0/src/client.h 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/src/client.h 2022-07-13 13:35:54.000000000 +0200 @@ -79,6 +79,9 @@ /* Send a request to remove a server from the cluster. */ int clientSendRemove(struct client *c, unsigned id); +/* Send a request to transfer leadership to node with id `id`. */ +int clientSendTransfer(struct client *c, unsigned id); + /* Receive an empty response. */ int clientRecvEmpty(struct client *c); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/src/gateway.c new/dqlite-1.11.1/src/gateway.c --- old/dqlite-1.10.0/src/gateway.c 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/src/gateway.c 2022-07-13 13:35:54.000000000 +0200 @@ -31,11 +31,10 @@ g->protocol = DQLITE_PROTOCOL_VERSION; } -void gateway__close(struct gateway *g) +void gateway__leader_close(struct gateway *g, int reason) { - tracef("gateway close"); - if (g->leader == NULL) { - stmt__registry_close(&g->stmts); + if (g == NULL || g->leader == NULL) { + tracef("gateway:%p or gateway->leader are NULL", g); return; } @@ -43,7 +42,7 @@ if (g->leader->inflight != NULL) { tracef("finish inflight apply request"); struct raft_apply *req = &g->leader->inflight->req; - req->cb(req, RAFT_SHUTDOWN, NULL); + req->cb(req, reason, NULL); assert(g->req == NULL); assert(g->stmt == NULL); } else if (g->barrier.cb != NULL) { @@ -52,12 +51,12 @@ * around g->barrier.cb and when called, will set g->barrier.cb to NULL. * */ struct raft_barrier *b = &g->barrier.req; - b->cb(b, RAFT_SHUTDOWN); + b->cb(b, reason); assert(g->barrier.cb == NULL); } else if (g->leader->exec != NULL && g->leader->exec->barrier.cb != NULL) { tracef("finish inflight exec barrier"); struct raft_barrier *b = &g->leader->exec->barrier.req; - b->cb(b, RAFT_SHUTDOWN); + b->cb(b, reason); assert(g->leader->exec == NULL); } else if (g->req != NULL && g->req->type != DQLITE_REQUEST_QUERY && g->req->type != DQLITE_REQUEST_EXEC) { @@ -71,6 +70,18 @@ stmt__registry_close(&g->stmts); leader__close(g->leader); sqlite3_free(g->leader); + g->leader = NULL; +} + +void gateway__close(struct gateway *g) +{ + tracef("gateway close"); + if (g->leader == NULL) { + stmt__registry_close(&g->stmts); + return; + } + + gateway__leader_close(g, RAFT_SHUTDOWN); } /* Declare a request struct and a response struct of the appropriate types and @@ -293,6 +304,7 @@ * affected. */ static void fill_result(struct gateway *g, struct response_result *response) { + assert(g->leader != NULL); response->last_insert_id = (uint64_t)sqlite3_last_insert_rowid(g->leader->conn); response->rows_affected = (uint64_t)sqlite3_changes(g->leader->conn); @@ -325,6 +337,7 @@ fill_result(g, &response); SUCCESS(result, RESULT); } else { + assert(g->leader != NULL); failure(req, status, error_message(g->leader->conn, status)); sqlite3_reset(stmt); } @@ -371,6 +384,7 @@ rc = query__batch(stmt, req->buffer); if (rc != SQLITE_ROW && rc != SQLITE_DONE) { sqlite3_reset(stmt); + assert(g->leader != NULL); failure(req, rc, sqlite3_errmsg(g->leader->conn)); goto done; } @@ -480,6 +494,7 @@ if (status == SQLITE_DONE) { handle_exec_sql_next(req, NULL); } else { + assert(g->leader != NULL); failure(req, status, error_message(g->leader->conn, status)); sqlite3_reset(g->stmt); sqlite3_finalize(g->stmt); @@ -507,6 +522,7 @@ } /* g->stmt will be set to NULL by sqlite when an error occurs. */ + assert(g->leader != NULL); rv = sqlite3_prepare_v2(g->leader->conn, g->sql, -1, &g->stmt, &tail); if (rv != SQLITE_OK) { tracef("exec sql prepare failed %d", rv); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/src/gateway.h new/dqlite-1.11.1/src/gateway.h --- old/dqlite-1.10.0/src/gateway.h 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/src/gateway.h 2022-07-13 13:35:54.000000000 +0200 @@ -47,6 +47,12 @@ void gateway__close(struct gateway *g); /** + * Closes the leader connection to the database, reason should contain a raft + * error code. + */ +void gateway__leader_close(struct gateway *g, int reason); + +/** * Asynchronous request to handle a client command. */ typedef void (*handle_cb)(struct handle *req, int status, int type); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/src/leader.c new/dqlite-1.11.1/src/leader.c --- old/dqlite-1.10.0/src/leader.c 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/src/leader.c 2022-07-13 13:35:54.000000000 +0200 @@ -9,11 +9,10 @@ #include "tracing.h" #include "vfs.h" -static void maybeExecDone(struct exec *req) +/* Called when a leader exec request terminates and the associated callback can + * be invoked. */ +static void leaderExecDone(struct exec *req) { - if (!req->done) { - return; - } req->leader->exec = NULL; if (req->cb != NULL) { req->cb(req, req->status); @@ -134,9 +133,8 @@ /* TODO: there shouldn't be any ongoing exec request. */ if (l->exec != NULL) { assert(l->inflight == NULL); - l->exec->done = true; l->exec->status = SQLITE_ERROR; - maybeExecDone(l->exec); + leaderExecDone(l->exec); } rc = sqlite3_close(l->conn); assert(rc == 0); @@ -273,8 +271,7 @@ finish: l->inflight = NULL; l->db->tx_id = 0; - l->exec->done = true; - maybeExecDone(l->exec); + leaderExecDone(l->exec); } static int leaderApplyFrames(struct exec *req, @@ -364,12 +361,11 @@ return; finish: - l->exec->done = true; if (rv != 0) { tracef("exec v2 failed %d", rv); l->exec->status = rv; } - maybeExecDone(l->exec); + leaderExecDone(l->exec); } static void execBarrierCb(struct barrier *barrier, int status) @@ -378,9 +374,8 @@ struct exec *req = barrier->data; struct leader *l = req->leader; if (status != 0) { - l->exec->done = true; l->exec->status = status; - maybeExecDone(l->exec); + leaderExecDone(l->exec); return; } leaderExecV2(req); @@ -402,7 +397,6 @@ req->leader = l; req->stmt = stmt; req->cb = cb; - req->done = false; req->barrier.data = req; req->barrier.cb = NULL; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/src/leader.h new/dqlite-1.11.1/src/leader.h --- old/dqlite-1.10.0/src/leader.h 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/src/leader.h 2022-07-13 13:35:54.000000000 +0200 @@ -64,7 +64,6 @@ struct leader *leader; struct barrier barrier; sqlite3_stmt *stmt; - bool done; int status; queue queue; exec_cb cb; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/src/lib/registry.h new/dqlite-1.11.1/src/lib/registry.h --- old/dqlite-1.10.0/src/lib/registry.h 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/src/lib/registry.h 2022-07-13 13:35:54.000000000 +0200 @@ -55,7 +55,7 @@ /** * Define the methods of a registry */ -#define REGISTRY_METHODS(NAME, TYPE) \ +#define REGISTRY_METHODS(NAME, TYPE) \ void NAME##_init(struct NAME *r) { \ assert(r != NULL); \ \ @@ -82,8 +82,11 @@ } \ } \ \ + r->len = 0; \ + r->cap = 0; \ if (r->buf != NULL) { \ sqlite3_free(r->buf); \ + r->buf = NULL; \ } \ } \ \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/src/lib/util.h new/dqlite-1.11.1/src/lib/util.h --- old/dqlite-1.10.0/src/lib/util.h 1970-01-01 01:00:00.000000000 +0100 +++ new/dqlite-1.11.1/src/lib/util.h 2022-07-13 13:35:54.000000000 +0200 @@ -0,0 +1,7 @@ +#ifndef LIB_UTIL_H_ +#define LIB_UTIL_H_ + +#define LIKELY(x) __builtin_expect(!!(x),1) +#define UNLIKELY(x) __builtin_expect(!!(x),0) + +#endif /* LIB_UTIL_H_ */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/src/server.c new/dqlite-1.11.1/src/server.c --- old/dqlite-1.10.0/src/server.c 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/src/server.c 2022-07-13 13:35:54.000000000 +0200 @@ -572,27 +572,21 @@ { struct dqlite_node *d = monitor->data; int state = raft_state(&d->raft); - /* queue *head; struct conn *conn; - */ if (state == RAFT_UNAVAILABLE) { return; } - /* TODO: we should shutdown clients that are performing SQL requests, - * but not the ones which are doing management-requests, such as - * transfer leadership. */ - /* if (d->raft_state == RAFT_LEADER && state != RAFT_LEADER) { + tracef("node %llu@%s: leadership lost", d->raft.id, d->raft.address); QUEUE__FOREACH(head, &d->conns) { conn = QUEUE__DATA(head, struct conn, queue); - conn__stop(conn); + gateway__leader_close(&conn->gateway, RAFT_LEADERSHIPLOST); } } - */ d->raft_state = state; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/src/tracing.h new/dqlite-1.11.1/src/tracing.h --- old/dqlite-1.10.0/src/tracing.h 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/src/tracing.h 2022-07-13 13:35:54.000000000 +0200 @@ -8,12 +8,14 @@ #include <stdint.h> #include <time.h> +#include "lib/util.h" + /* This global variable is only written once at startup and is only read * from there on. Users should not manipulate the value of this variable. */ extern bool _dqliteTracingEnabled; #define tracef(...) do { \ - if (_dqliteTracingEnabled) { \ + if (UNLIKELY(_dqliteTracingEnabled)) { \ static char _msg[1024]; \ snprintf(_msg, sizeof(_msg), __VA_ARGS__); \ struct timespec ts = {0}; \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/test/integration/test_fsm.c new/dqlite-1.11.1/test/integration/test_fsm.c --- old/dqlite-1.10.0/test/integration/test_fsm.c 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/test/integration/test_fsm.c 2022-07-13 13:35:54.000000000 +0200 @@ -188,8 +188,7 @@ EXEC(stmt_id, &last_insert_id, &rows_affected); /* Close and reopen the client and open a second database */ - rv = test_server_client_reconnect(&f->servers[0]); - munit_assert_int(rv, ==, 0); + test_server_client_reconnect(&f->servers[0], &f->servers[0].client); HANDSHAKE; OPEN_NAME("test2"); @@ -253,8 +252,7 @@ /* Close and reopen the client and open a second database, * and ensure finalize succeeds. */ - rv = test_server_client_reconnect(&f->servers[0]); - munit_assert_int(rv, ==, 0); + test_server_client_reconnect(&f->servers[0], &f->servers[0].client); HANDSHAKE; OPEN_NAME("test2"); @@ -391,8 +389,7 @@ munit_assert_int(rv, ==, 0); /* Table is there on fresh connection. */ - rv = test_server_client_reconnect(&f->servers[0]); - munit_assert_int(rv, ==, 0); + test_server_client_reconnect(&f->servers[0], &f->servers[0].client); HANDSHAKE; OPEN; PREPARE("SELECT COUNT(*) from test", &stmt_id); @@ -469,8 +466,7 @@ PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id); EXEC(stmt_id, &last_insert_id, &rows_affected); - rv = test_server_client_reconnect(&f->servers[0]); - munit_assert_int(rv, ==, 0); + test_server_client_reconnect(&f->servers[0], &f->servers[0].client); HANDSHAKE; OPEN_NAME("test2"); PREPARE("CREATE TABLE test2a (n INT)", &stmt_id); @@ -498,8 +494,7 @@ munit_assert_int(rv, ==, 0); /* Reopen connection */ - rv = test_server_client_reconnect(&f->servers[0]); - munit_assert_int(rv, ==, 0); + test_server_client_reconnect(&f->servers[0], &f->servers[0].client); HANDSHAKE; OPEN_NAME("test2"); @@ -514,8 +509,7 @@ munit_assert_string_equal(msg, "no such table: test2b"); /* Table is there on first DB */ - rv = test_server_client_reconnect(&f->servers[0]); - munit_assert_int(rv, ==, 0); + test_server_client_reconnect(&f->servers[0], &f->servers[0].client); HANDSHAKE; OPEN_NAME("test"); PREPARE("SELECT * from test", &stmt_id); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/test/integration/test_membership.c new/dqlite-1.11.1/test/integration/test_membership.c --- old/dqlite-1.10.0/test/integration/test_membership.c 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/test/integration/test_membership.c 2022-07-13 13:35:54.000000000 +0200 @@ -17,7 +17,8 @@ #define N_SERVERS 3 #define FIXTURE \ struct test_server servers[N_SERVERS]; \ - struct client *client + struct client *client; \ + struct rows rows; #define SETUP \ unsigned i_; \ @@ -51,6 +52,20 @@ /* Use the client connected to the server with the given ID. */ #define SELECT(ID) f->client = test_server_client(&f->servers[ID - 1]) +/* Wait a bounded time until server ID has applied at least the entry at INDEX */ +#define AWAIT_REPLICATION(ID, INDEX) \ + do { \ + struct timespec _start = {0}; \ + struct timespec _end = {0}; \ + clock_gettime(CLOCK_MONOTONIC, &_start); \ + clock_gettime(CLOCK_MONOTONIC, &_end); \ + while((f->servers[ID].dqlite->raft.last_applied < INDEX) \ + && ((_end.tv_sec - _start.tv_sec) < 2) ) { \ + clock_gettime(CLOCK_MONOTONIC, &_end); \ + } \ + munit_assert_ullong(f->servers[ID].dqlite->raft.last_applied, >=, INDEX); \ + } while(0) + /****************************************************************************** * * join @@ -107,3 +122,153 @@ REMOVE(id); return MUNIT_OK; } + +TEST(membership, transfer, setUp, tearDown, 0, NULL) +{ + struct fixture *f = data; + unsigned id = 2; + const char *address = "@2"; + unsigned stmt_id; + unsigned last_insert_id; + unsigned rows_affected; + raft_index last_applied; + struct client c_transfer; /* Client used for transfer requests */ + + HANDSHAKE; + ADD(id, address); + ASSIGN(id, 1 /* voter */); + OPEN; + PREPARE("CREATE TABLE test (n INT)", &stmt_id); + EXEC(stmt_id, &last_insert_id, &rows_affected); + PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id); + EXEC(stmt_id, &last_insert_id, &rows_affected); + + /* Transfer leadership and wait until first leader has applied a new + * entry replicated from the new leader. */ + test_server_client_connect(&f->servers[0], &c_transfer); + HANDSHAKE_C(&c_transfer); + TRANSFER(2, &c_transfer); + test_server_client_close(&f->servers[0], &c_transfer); + last_applied = f->servers[0].dqlite->raft.last_applied; + + SELECT(2); + HANDSHAKE; + OPEN; + PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id); + EXEC(stmt_id, &last_insert_id, &rows_affected); + + AWAIT_REPLICATION(0, last_applied + 1); + + return MUNIT_OK; +} + +/* Transfer leadership away from a member that has a pending transaction */ +TEST(membership, transferPendingTransaction, setUp, tearDown, 0, NULL) +{ + struct fixture *f = data; + unsigned id = 2; + const char *address = "@2"; + unsigned stmt_id; + unsigned last_insert_id; + unsigned rows_affected; + raft_index last_applied; + struct client c_transfer; /* Client used for transfer requests */ + + HANDSHAKE; + ADD(id, address); + ASSIGN(id, 1 /* voter */); + OPEN; + PREPARE("CREATE TABLE test (n INT)", &stmt_id); + EXEC(stmt_id, &last_insert_id, &rows_affected); + PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id); + EXEC(stmt_id, &last_insert_id, &rows_affected); + + /* Pending transaction */ + PREPARE("BEGIN", &stmt_id); + EXEC(stmt_id, &last_insert_id, &rows_affected); + PREPARE("SELECT * FROM test", &stmt_id); + QUERY(stmt_id, &f->rows); + clientCloseRows(&f->rows); + + /* Transfer leadership and wait until first leader has applied a new + * entry replicated from the new leader. */ + test_server_client_connect(&f->servers[0], &c_transfer); + HANDSHAKE_C(&c_transfer); + TRANSFER(2, &c_transfer); + test_server_client_close(&f->servers[0], &c_transfer); + last_applied = f->servers[0].dqlite->raft.last_applied; + + SELECT(2); + HANDSHAKE; + OPEN; + PREPARE("INSERT INTO test(n) VALUES(2)", &stmt_id); + EXEC(stmt_id, &last_insert_id, &rows_affected); + + AWAIT_REPLICATION(0, last_applied + 1); + + return MUNIT_OK; +} + +/* Transfer leadership back and forth from a member that has a pending transaction */ +TEST(membership, transferTwicePendingTransaction, setUp, tearDown, 0, NULL) +{ + struct fixture *f = data; + unsigned id = 2; + const char *address = "@2"; + unsigned stmt_id; + unsigned last_insert_id; + unsigned rows_affected; + raft_index last_applied; + struct client c_transfer; /* Client used for transfer requests */ + + HANDSHAKE; + ADD(id, address); + ASSIGN(id, 1 /* voter */); + OPEN; + PREPARE("CREATE TABLE test (n INT)", &stmt_id); + EXEC(stmt_id, &last_insert_id, &rows_affected); + PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id); + EXEC(stmt_id, &last_insert_id, &rows_affected); + + /* Pending transaction */ + PREPARE("BEGIN", &stmt_id); + EXEC(stmt_id, &last_insert_id, &rows_affected); + PREPARE("SELECT * FROM test", &stmt_id); + QUERY(stmt_id, &f->rows); + clientCloseRows(&f->rows); + + /* Transfer leadership and wait until first leader has applied a new + * entry replicated from the new leader. */ + test_server_client_connect(&f->servers[0], &c_transfer); + HANDSHAKE_C(&c_transfer); + TRANSFER(2, &c_transfer); + test_server_client_close(&f->servers[0], &c_transfer); + last_applied = f->servers[0].dqlite->raft.last_applied; + + SELECT(2); + HANDSHAKE; + OPEN; + PREPARE("INSERT INTO test(n) VALUES(2)", &stmt_id); + EXEC(stmt_id, &last_insert_id, &rows_affected); + + AWAIT_REPLICATION(0, last_applied + 1); + + /* Transfer leadership back to original node, reconnect the client and + * ensure queries can be executed. */ + test_server_client_connect(&f->servers[1], &c_transfer); + HANDSHAKE_C(&c_transfer); + TRANSFER(1, &c_transfer); + test_server_client_close(&f->servers[1], &c_transfer); + + last_applied = f->servers[1].dqlite->raft.last_applied; + test_server_client_reconnect(&f->servers[0], &f->servers[0].client); + SELECT(1); + HANDSHAKE; + OPEN; + PREPARE("INSERT INTO test(n) VALUES(3)", &stmt_id); + EXEC(stmt_id, &last_insert_id, &rows_affected); + + AWAIT_REPLICATION(1, last_applied + 1); + + return MUNIT_OK; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/test/lib/client.h new/dqlite-1.11.1/test/lib/client.h --- old/dqlite-1.10.0/test/lib/client.h 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/test/lib/client.h 2022-07-13 13:35:54.000000000 +0200 @@ -39,6 +39,14 @@ munit_assert_int(rv_, ==, 0); \ } +/* Send the initial client handshake for a specific client. */ +#define HANDSHAKE_C(CLIENT) \ + { \ + int rv_; \ + rv_ = clientSendHandshake(CLIENT); \ + munit_assert_int(rv_, ==, 0); \ + } + /* Send an add request. */ #define ADD(ID, ADDRESS) \ { \ @@ -69,6 +77,16 @@ munit_assert_int(rv_, ==, 0); \ } +/* Send a transfer request. */ +#define TRANSFER(ID, CLIENT) \ + { \ + int rv_; \ + rv_ = clientSendTransfer(CLIENT, ID); \ + munit_assert_int(rv_, ==, 0); \ + rv_ = clientRecvEmpty(CLIENT); \ + munit_assert_int(rv_, ==, 0); \ + } + /* Open a test database. */ #define OPEN \ { \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/test/lib/cluster.h new/dqlite-1.11.1/test/lib/cluster.h --- old/dqlite-1.10.0/test/lib/cluster.h 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/test/lib/cluster.h 2022-07-13 13:35:54.000000000 +0200 @@ -52,10 +52,11 @@ int _rv; \ SETUP_HEAP; \ SETUP_SQLITE; \ - _rv = raft_fixture_init(&f->cluster, N_SERVERS, f->fsms); \ + _rv = raft_fixture_initialize(&f->cluster); \ munit_assert_int(_rv, ==, 0); \ for (_i = 0; _i < N_SERVERS; _i++) { \ SETUP_SERVER(_i, VERSION); \ + raft_fixture_grow(&f->cluster, &f->fsms[_i]); \ } \ _rv = raft_fixture_configuration(&f->cluster, N_SERVERS, \ &_configuration); \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/test/lib/server.c new/dqlite-1.11.1/test/lib/server.c --- old/dqlite-1.10.0/test/lib/server.c 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/test/lib/server.c 2022-07-13 13:35:54.000000000 +0200 @@ -17,8 +17,7 @@ *fd = socket(AF_UNIX, SOCK_STREAM, 0); munit_assert_int(*fd, !=, -1); rv = connect(*fd, (struct sockaddr *)&addr, sizeof(sa_family_t) + strlen(address + 1) + 1); - munit_assert_int(rv, ==, 0); - return 0; + return rv; } void test_server_setup(struct test_server *s, @@ -39,8 +38,7 @@ { int rv; - clientClose(&s->client); - close(s->client.fd); + test_server_client_close(s, &s->client); rv = dqlite_node_stop(s->dqlite); munit_assert_int(rv, ==, 0); dqlite_node_destroy(s->dqlite); @@ -54,7 +52,6 @@ void test_server_start(struct test_server *s) { - int client; int rv; rv = dqlite_node_create(s->id, s->address, s->dir, &s->dqlite); @@ -72,12 +69,7 @@ rv = dqlite_node_start(s->dqlite); munit_assert_int(rv, ==, 0); - /* Connect a client. */ - rv = endpointConnect(NULL, s->address, &client); - munit_assert_int(rv, ==, 0); - - rv = clientInit(&s->client, client); - munit_assert_int(rv, ==, 0); + test_server_client_connect(s, &s->client); } struct client *test_server_client(struct test_server *s) @@ -85,12 +77,29 @@ return &s->client; } -int test_server_client_reconnect(struct test_server *s) +void test_server_client_reconnect(struct test_server *s, struct client *c) +{ + test_server_client_close(s, c); + test_server_client_connect(s, c); +} + +void test_server_client_connect(struct test_server *s, struct client *c) +{ + int rv; + int fd; + + rv = endpointConnect(NULL, s->address, &fd); + munit_assert_int(rv, ==, 0); + + rv = clientInit(c, fd); + munit_assert_int(rv, ==, 0); +} + +void test_server_client_close(struct test_server *s, struct client *c) { - clientClose(&s->client); - close(s->client.fd); - endpointConnect(NULL, s->address, &s->client.fd); - return clientInit(&s->client, s->client.fd); + (void) s; + clientClose(c); + close(c->fd); } static void setOther(struct test_server *s, struct test_server *other) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dqlite-1.10.0/test/lib/server.h new/dqlite-1.11.1/test/lib/server.h --- old/dqlite-1.10.0/test/lib/server.h 2022-04-13 16:59:33.000000000 +0200 +++ new/dqlite-1.11.1/test/lib/server.h 2022-07-13 13:35:54.000000000 +0200 @@ -43,7 +43,13 @@ /* Return a client connected to the server. */ struct client *test_server_client(struct test_server *s); -/* Closes and reopens the client connection to the server. */ -int test_server_client_reconnect(struct test_server *s); +/* Closes and reopens a client connection to the server. */ +void test_server_client_reconnect(struct test_server *s, struct client *c); + +/* Opens a client connection to the server. */ +void test_server_client_connect(struct test_server *s, struct client *c); + +/* Closes a client connection to ther server. */ +void test_server_client_close(struct test_server *s, struct client *c); #endif /* TEST_SERVER_H */