==> tests/store.c <==1/* blobs.rs's tests: write-rename durability, hash verification, a blob of two jobs outlives either
2 * (persisted and in memory), eviction (persisted and in memory). */
3#include "mesh/blobs/store.h"
4#include "mesh/blobs/durable.h"
5#include "mesh/check.h"
6#include "mesh/ref/sha256d.h"
7#include "mesh/strbuf.h"
8
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <sys/stat.h>
13#include <unistd.h>
14
15static char *dir(const char *name) {
16 char *d = mfmt("%s/meshd-blobs-%s-%d", getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp", name, (int)getpid());
17 mb_remove_tree(d);
18 return d;
19}
20
21static mb_live listed(void *c, mj_job_id j) {
22 (void)c;
23 (void)j;
24 return MB_LISTED;
25}
26
27static mj_hash sha(const void *b, size_t n) {
28 mj_hash h;
29 mesh_sha256(b, n, h.b);
30 return h;
31}
32
33/* blobd's path: reserve, make durable (write or link), directory fsync, written. */
34static bool put(mb_store *s, mj_job_id job, const void *b, size_t n, mb_live_fn live, mj_hash *h, mb_reserved *r, char **err) {
35 *h = sha(b, n);
36 if (!mb_reserve(s, job, *h, (uint32_t)n, live, NULL, r, err)) return false;
37 char *d = mb_dir_of(s, job);
38 if (d) {
39 char x[65];
40 mj_hex(h, x);
41 CHECK(mb_make_durable(d, x, r->link ? r->path : NULL, b, n, NULL));
42 CHECK(mb_sync_dir(d, NULL));
43 mb_written(s, job, h, true);
44 free(d);
45 } else mb_put_mem(s, job, *h, b, n);
46 return true;
47}
48
49static bool exists(const char *p) {
50 struct stat st;
51 return stat(p, &st) == 0;
52}
53
54static void write_rename_durability(void) {
55 char *d = dir("durable");
56 mb_store *s = mb_open(d, 1 << 20, NULL);
57 mj_job_id j = (uint64_t)1 << 32 | 1;
58 mj_hash h;
59 mb_reserved r;
60 CHECK(put(s, j, "pass sums", 9, listed, &h, &r, NULL));
61 CHECK(!r.link);
62 mb_reserved_free(&r);
63 size_t n;
64 uint8_t *b = mb_get(s, &h, &n);
65 CHECK(mb_has(s, &h) && mb_has_ref(s, j, &h) && b && n == 9 && memcmp(b, "pass sums", 9) == 0);
66 free(b);
67 char *jd = mfmt("%s/1.1", d), x[65];
68 mj_hex(&h, x);
69 char *f = mfmt("%s/%s", jd, x);
70 CHECK(exists(f));
71 /* A crash between the write and the rename: only the .tmp is there. */
72 mj_hash torn = sha("half", 4);
73 char tx[65];
74 mj_hex(&torn, tx);
75 char *tmp = mfmt("%s/%s.tmp", jd, tx);
76 FILE *t = fopen(tmp, "wb");
77 fputs("ha", t);
78 fclose(t);
79 /* A reserved reference is not present until written. */
80 mj_hash p = sha("pending", 7);
81 CHECK(mb_reserve(s, j, p, 7, listed, NULL, &r, NULL));
82 mb_reserved_free(&r);
83 CHECK(mb_known_ref(s, j, &p) && !mb_has(s, &p) && !mb_has_ref(s, j, &p));
84 mb_written(s, j, &p, false);
85 CHECK(!mb_known_ref(s, j, &p) && mb_bytes(s) == 9);
86 mb_store_free(s);
87 s = mb_open(d, 1 << 20, NULL);
88 CHECK(mb_has_ref(s, j, &h) && !mb_has(s, &torn) && mb_count(s) == 1 && mb_bytes(s) == 9);
89 CHECK(!exists(tmp));
90 b = mb_get(s, &h, &n);
91 CHECK(b && n == 9);
92 free(b);
93 mb_store_free(s);
94 mb_remove_tree(d);
95 free(d);
96 free(jd);
97 free(f);
98 free(tmp);
99}
100
101static void hash_verification(void) {
102 char *d = dir("verify");
103 mb_store *s = mb_open(d, 1 << 20, NULL);
104 mj_hash h;
105 mb_reserved r;
106 CHECK(put(s, 5, "payload", 7, listed, &h, &r, NULL));
107 mb_reserved_free(&r);
108 char x[65];
109 mj_hex(&h, x);
110 char *f = mfmt("%s/0.5/%s", d, x);
111 FILE *t = fopen(f, "wb");
112 fputs("pAyload", t);
113 fclose(t);
114 size_t n;
115 CHECK(mb_get(s, &h, &n) == NULL);
116 CHECK(!mb_known_ref(s, 5, &h) && mb_bytes(s) == 0);
117 mb_refs lost = mb_take_lost(s);
118 CHECK(lost.n == 1 && lost.v[0].job == 5 && mj_hash_eq(&lost.v[0].h, &h));
119 mb_refs_free(&lost);
120 mb_store_free(s);
121 mb_remove_tree(d);
122 free(d);
123 free(f);
124}
125
126static void two_jobs(void) {
127 for (int persisted = 1; persisted >= 0; persisted--) {
128 char *d = dir("shared");
129 mj_job_id a = (uint64_t)1 << 32 | 2, b = (uint64_t)2 << 32 | 1;
130 mb_store *s = mb_open(persisted ? d : NULL, 1 << 20, NULL);
131 mj_hash h, h2;
132 mb_reserved r;
133 CHECK(put(s, a, "identical pass sums", 19, listed, &h, &r, NULL));
134 mb_reserved_free(&r);
135 CHECK(mb_has(s, &h) && !mb_known_ref(s, b, &h)); /* job b has no copy until it makes one */
136 CHECK(put(s, b, "identical pass sums", 19, listed, &h2, &r, NULL));
137 CHECK(mj_hash_eq(&h, &h2));
138 char x[65];
139 mj_hex(&h, x);
140 if (persisted) {
141 char *want = mfmt("%s/1.2/%s", d, x), *other = mfmt("%s/2.1/%s", d, x);
142 CHECK(r.link && strcmp(r.path, want) == 0);
143 struct stat s1, s2;
144 CHECK(stat(want, &s1) == 0 && stat(other, &s2) == 0 && s1.st_ino == s2.st_ino); /* a hard link, not a second copy */
145 free(want);
146 free(other);
147 }
148 mb_reserved_free(&r);
149 CHECK(mb_has_ref(s, a, &h) && mb_has_ref(s, b, &h) && mb_count(s) == 1 && mb_refs_total(s) == 2 && mb_bytes(s) == 19);
150 if (persisted) {
151 mb_store_free(s);
152 s = mb_open(d, 1 << 20, NULL);
153 CHECK(mb_count(s) == 1 && mb_refs_total(s) == 2 && mb_bytes(s) == 19);
154 }
155 /* job forget a: b's copy stays. */
156 mb_refs g = mb_drop_job(s, a);
157 CHECK(g.n == 1 && mj_hash_eq(&g.v[0].h, &h));
158 mb_refs_free(&g);
159 size_t n;
160 uint8_t *bytes = mb_get(s, &h, &n);
161 CHECK(mb_has_ref(s, b, &h) && bytes && n == 19 && memcmp(bytes, "identical pass sums", 19) == 0 && mb_bytes(s) == 19);
162 free(bytes);
163 if (persisted) {
164 char *ad = mfmt("%s/1.2", d);
165 CHECK(!exists(ad));
166 free(ad);
167 mb_store_free(s);
168 s = mb_open(d, 1 << 20, NULL);
169 CHECK(mb_has_ref(s, b, &h));
170 }
171 g = mb_drop_job(s, b);
172 mb_refs_free(&g);
173 CHECK(!mb_has(s, &h) && mb_bytes(s) == 0 && mb_count(s) == 0);
174 mb_store_free(s);
175 mb_remove_tree(d);
176 free(d);
177 }
178 /* A link whose source went meanwhile (its job forgotten while the writer ran): the bytes are written. */
179 char *d = dir("shared-race");
180 mb_store *s = mb_open(d, 1 << 20, NULL);
181 mj_hash h;
182 mb_reserved r;
183 CHECK(put(s, 1, "bytes", 5, listed, &h, &r, NULL));
184 mb_reserved_free(&r);
185 CHECK(mb_reserve(s, 2, h, 5, listed, NULL, &r, NULL) && r.link);
186 mb_refs g = mb_drop_job(s, 1);
187 mb_refs_free(&g);
188 char *d2 = mb_dir_of(s, 2), x[65];
189 mj_hex(&h, x);
190 CHECK(mb_make_durable(d2, x, r.path, "bytes", 5, NULL));
191 mb_reserved_free(&r);
192 mb_written(s, 2, &h, true);
193 size_t n;
194 uint8_t *b = mb_get(s, &h, &n);
195 CHECK(b && n == 5 && memcmp(b, "bytes", 5) == 0);
196 free(b);
197 /* A reference dropped while its write ran leaves no file behind. */
198 mj_hash late = sha("late", 4);
199 CHECK(mb_reserve(s, 3, late, 4, listed, NULL, &r, NULL));
200 mb_reserved_free(&r);
201 g = mb_drop_job(s, 3);
202 mb_refs_free(&g);
203 char *d3 = mfmt("%s/0.3", d), lx[65];
204 mj_hex(&late, lx);
205 CHECK(mb_make_durable(d3, lx, NULL, "late", 4, NULL));
206 mb_written(s, 3, &late, true);
207 char *lf = mfmt("%s/%s", d3, lx);
208 CHECK(!mb_known_ref(s, 3, &late) && !exists(lf));
209 mb_store_free(s);
210 mb_remove_tree(d);
211 free(d);
212 free(d2);
213 free(d3);
214 free(lf);
215}
216
217static mb_live one_forgotten(void *c, mj_job_id j) {
218 (void)c;
219 return j == 1 ? MB_FORGOTTEN : MB_LISTED;
220}
221
222static void eviction(void) {
223 for (int persisted = 1; persisted >= 0; persisted--) {
224 char *d = dir("evict");
225 mb_store *s = mb_open(persisted ? d : NULL, 100, NULL);
226 uint8_t fill[5][30];
227 for (int i = 0; i < 5; i++) memset(fill[i], i + 1, 30);
228 mj_hash a, b, c, e, x;
229 mb_reserved r;
230 CHECK(put(s, 1, fill[0], 30, one_forgotten, &a, &r, NULL));
231 mb_reserved_free(&r);
232 CHECK(put(s, 2, fill[1], 30, one_forgotten, &b, &r, NULL));
233 mb_reserved_free(&r);
234 CHECK(put(s, 3, fill[2], 30, one_forgotten, &c, &r, NULL));
235 mb_reserved_free(&r);
236 /* 90 of 100: 30 more evicts the forgotten job's. */
237 CHECK(put(s, 4, fill[3], 30, one_forgotten, &e, &r, NULL));
238 CHECK(r.evicted.n == 1 && r.evicted.v[0].job == 1 && mj_hash_eq(&r.evicted.v[0].h, &a));
239 mb_reserved_free(&r);
240 CHECK(!mb_has(s, &a) && mb_has(s, &b) && mb_has(s, &c) && mb_has(s, &e));
241 /* Then nothing is evictable: refused, nothing evicted. */
242 char *err = NULL;
243 CHECK(!put(s, 5, fill[4], 30, one_forgotten, &x, &r, &err) && err && strstr(err, "full"));
244 free(err);
245 CHECK(mb_has(s, &b) && mb_has(s, &c) && mb_has(s, &e) && mb_bytes(s) == 90);
246 /* The same bytes for another job: a reference, no room needed. */
247 mj_hash b2;
248 CHECK(put(s, 5, fill[1], 30, one_forgotten, &b2, &r, NULL));
249 CHECK(mj_hash_eq(&b2, &b) && r.evicted.n == 0 && mb_has_ref(s, 5, &b) && mb_bytes(s) == 90);
250 mb_reserved_free(&r);
251 if (persisted) {
252 char *d1 = mfmt("%s/0.1", d), *d4 = mfmt("%s/0.4", d);
253 CHECK(!exists(d1) && exists(d4));
254 free(d1);
255 free(d4);
256 }
257 mb_refs g = mb_drop_job(s, 2);
258 CHECK(g.n == 1 && mj_hash_eq(&g.v[0].h, &b));
259 mb_refs_free(&g);
260 CHECK(mb_has_ref(s, 5, &b) && mb_bytes(s) == 90);
261 g = mb_drop_job(s, 4);
262 CHECK_U(g.n, 1);
263 mb_refs_free(&g);
264 CHECK_U(mb_bytes(s), 60);
265 mb_store_free(s);
266 mb_remove_tree(d);
267 free(d);
268 }
269}
270
271/* The index under churn: thousands of blobs over many jobs, jobs dropped at random, every blob's presence
272 * and the byte count checked against a plain list (deletions shift probe runs back). */
273static void churn(void) {
274 mb_store *s = mb_open(NULL, (uint64_t)1 << 40, NULL);
275 enum { N = 3000 };
276 static mj_hash hs[N];
277 static mj_job_id job[N];
278 static bool alive[N];
279 uint64_t r = 0x2545f4914f6cdd1du;
280 for (int i = 0; i < N; i++) {
281 uint8_t b[16];
282 memcpy(b, &i, sizeof i);
283 memset(b + sizeof i, 7, sizeof b - sizeof i);
284 hs[i] = sha(b, sizeof b);
285 r ^= r << 13;
286 r ^= r >> 7;
287 r ^= r << 17;
288 job[i] = r % 40;
289 mb_reserved rv;
290 CHECK(mb_reserve(s, job[i], hs[i], 16, listed, NULL, &rv, NULL));
291 mb_reserved_free(&rv);
292 mb_put_mem(s, job[i], hs[i], b, sizeof b);
293 alive[i] = true;
294 }
295 for (mj_job_id j = 0; j < 40; j += 3) {
296 mb_refs g = mb_drop_job(s, j);
297 mb_refs_free(&g);
298 for (int i = 0; i < N; i++)
299 if (job[i] == j) alive[i] = false;
300 }
301 uint64_t want = 0;
302 for (int i = 0; i < N; i++) {
303 CHECK(mb_has(s, &hs[i]) == alive[i]);
304 want += alive[i] ? 16 : 0;
305 }
306 CHECK_U(mb_bytes(s), want);
307 mb_store_free(s);
308}
309
310int main(void) {
311 churn();
312 write_rename_durability();
313 hash_verification();
314 two_jobs();
315 eviction();
316 return mesh_check_done();
317}