1 | #include "catch.hpp" |
---|---|
2 | #include "duckdb/common/file_system.hpp" |
3 | #include "duckdb.hpp" |
4 | #include "duckdb/main/appender.hpp" |
5 | #include "test_helpers.hpp" |
6 | |
7 | #include <signal.h> |
8 | #include <sys/mman.h> |
9 | #include <unistd.h> |
10 | |
11 | using namespace duckdb; |
12 | using namespace std; |
13 | |
14 | TEST_CASE("Test transactional integrity when facing process aborts", "[persistence][.]") { |
15 | FileSystem fs; |
16 | |
17 | // shared memory to keep track of insertions |
18 | size_t *count = (size_t *)mmap(NULL, sizeof(size_t), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, 0, 0); |
19 | |
20 | string db_folder_parent = TestCreatePath("llstorage"); |
21 | if (fs.DirectoryExists(db_folder_parent)) { |
22 | fs.RemoveDirectory(db_folder_parent); |
23 | } |
24 | fs.CreateDirectory(db_folder_parent); |
25 | |
26 | string db_folder = fs.JoinPath(db_folder_parent, "dbfolder"); |
27 | { |
28 | DuckDB db(db_folder); |
29 | Connection con(db); |
30 | con.Query("CREATE TABLE a (i INTEGER)"); |
31 | } |
32 | |
33 | // fork away a child to be mercilessy shot in a bit |
34 | pid_t pid = fork(); |
35 | |
36 | if (pid == 0) { // child process |
37 | DuckDB db(db_folder); |
38 | Connection con(db); |
39 | while (true) { |
40 | con.Query("INSERT INTO a VALUES(42)"); |
41 | (*count)++; |
42 | } |
43 | } else if (pid > 0) { // parent process |
44 | // wait until child has inserted at least 1000 rows |
45 | while (*count < 1000) { |
46 | usleep(100); |
47 | } |
48 | if (kill(pid, SIGKILL) != 0) { |
49 | FAIL(); |
50 | } |
51 | unique_ptr<DuckDB> db; |
52 | // it may take some time for the OS to reclaim the lock |
53 | // loop and wait until the database is successfully started again |
54 | for (size_t i = 0; i < 1000; i++) { |
55 | usleep(10000); |
56 | try { |
57 | db = make_unique<DuckDB>(db_folder); |
58 | } catch (...) { |
59 | } |
60 | if (db) { |
61 | break; |
62 | } |
63 | } |
64 | Connection con(*db); |
65 | auto res = con.Query("SELECT COUNT(*) FROM a"); |
66 | // there may be an off-by-one if we kill exactly between query and count increment |
67 | REQUIRE(abs((int64_t)(res->GetValue(0, 0).GetValue<int64_t>() - *count)) < 2); |
68 | } else { |
69 | FAIL(); |
70 | } |
71 | } |
72 |