==> tests/store.c <==1/* jobd.rs's `a_torn_write_is_cut_back` (review finding 4): a write that fails half-way is cut back to
2 * the last whole record, and a torn tail found at start (a crash mid-append) is dropped; every record
3 * before it replays. Also compaction and the in-memory store. */
4#include "mesh/jobd/store.h"
5#include "mesh/check.h"
6#include "mesh/strbuf.h"
7
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <sys/stat.h>
12#include <unistd.h>
13
14static mj_log e(uint64_t l, uint64_t n) {
15 mj_ops ops = {0};
16 mj_ops_push(&ops, mj_op_dupes(1, n));
17 return mj_log_entry(l, n, false, ops);
18}
19
20static uint64_t size_of(const char *p) {
21 struct stat st;
22 return stat(p, &st) == 0 ? (uint64_t)st.st_size : 0;
23}
24
25static int logged;
26static void logline(void *c, const char *line) {
27 (void)c;
28 if (strstr(line, "torn last record")) logged++;
29}
30
31int main(void) {
32 char *d = mfmt("%s/meshd-jobd-torn-%d", getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp", (int)getpid());
33 mkdir(d, 0777);
34 char *p = mfmt("%s/jobs", d);
35 uint64_t l = (uint64_t)1 << 40;
36 mj_replica r;
37 char *err = NULL;
38 mjd_store *s = mjd_store_open(p, &r, NULL, NULL, &err);
39 mj_replica empty;
40 mj_replica_init(&empty);
41 CHECK(s && mj_replica_eq(&r, &empty));
42 mj_replica_free(&r);
43 mj_disk dk = mjd_store_disk(s);
44 mj_log recs[3] = {mj_log_lid(l), e(l, 1), e(l, 2)};
45 for (int i = 0; i < 3; i++) CHECK(dk.append(dk.ctx, &recs[i], &err));
46 CHECK(dk.sync(dk.ctx, &err));
47 uint64_t whole = size_of(p);
48 mjd_store_tear_next(s, 7);
49 mj_log e3 = e(l, 3);
50 CHECK(!dk.append(dk.ctx, &e3, &err) && strstr(err, "torn"));
51 free(err);
52 CHECK_U(size_of(p), whole); /* cut back to the last whole record */
53 mjd_store_free(s);
54 s = mjd_store_open(p, &r, NULL, NULL, &err);
55 CHECK(s && r.lid == l && r.npending == 2);
56 mjd_store_free(s);
57 /* A torn tail left by a crash: dropped at start, and the file rewritten without it. */
58 msb b = {0};
59 mj_log_encode(&e3, &b);
60 FILE *f = fopen(p, "ab");
61 fwrite(b.p, 1, 9, f);
62 fclose(f);
63 msb_free(&b);
64 mj_replica r2;
65 s = mjd_store_open(p, &r2, logline, NULL, &err);
66 CHECK(s && mj_replica_eq(&r2, &r) && logged == 1);
67 FILE *g = fopen(p, "rb");
68 msb all = {0};
69 char buf[4096];
70 size_t n;
71 while ((n = fread(buf, 1, sizeof buf, g)) > 0) msb_put(&all, buf, n);
72 fclose(g);
73 mj_replica r3;
74 size_t recs_n, at;
75 CHECK(mj_replay(all.p, all.n, &r3, &recs_n, &at, NULL) && at == all.n);
76 mj_replica_free(&r3);
77 msb_free(&all);
78 /* A compaction rewrites it as the replica's own log. */
79 CHECK(!mjd_store_wants_compact(s));
80 CHECK(mjd_store_rewrite(s, &r2, &err));
81 CHECK(mjd_store_persisted(s));
82 mjd_store_free(s);
83 /* In memory: nothing written, appends succeed. */
84 mj_replica rm;
85 s = mjd_store_open(NULL, &rm, NULL, NULL, &err);
86 dk = mjd_store_disk(s);
87 CHECK(!mjd_store_persisted(s) && dk.append(dk.ctx, &e3, &err) && dk.sync(dk.ctx, &err));
88 mjd_store_free(s);
89 mj_replica_free(&rm);
90 for (int i = 0; i < 3; i++) mj_log_free(&recs[i]);
91 mj_log_free(&e3);
92 mj_replica_free(&r);
93 mj_replica_free(&r2);
94 mj_replica_free(&empty);
95 unlink(p);
96 char *tmp = mfmt("%s.jobs-tmp", p);
97 unlink(tmp);
98 rmdir(d);
99 free(tmp);
100 free(p);
101 free(d);
102 return mesh_check_done();
103}