skip to content
==> 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}