## Root Cause
The most likely failure is in the C test harness, not `storage_space`
itself.
The callback writes `r->ret` before it finishes writing `r->msg`, and
the polling thread treats `ret != -1` as “response is complete”. That
can make `is_resp_ok` return with `res == NULL`, after which
`check_space` calls `strstr(res, ...)` and segfaults.
The segfault is very likely a race in `tests/cbindings/storage.c`.
In `callback`:
```c
r->ret = ret;
...
r->msg = malloc(len + 1);
memcpy(r->msg, msg, len);
```
But `wait_resp` treats `r->ret != -1` as “the response is ready”:
```c
while (get_ret(r) == -1 && retries < MAX_RETRIES)
```
So this can happen:
1. Storage worker thread enters `callback`.
2. Callback sets `r->ret = RET_OK`.
3. Main C test thread sees `ret != -1` and exits `wait_resp`.
4. `is_resp_ok` observes `r->msg == NULL`, so `res` remains `NULL`.
5. `check_space` calls:
```c
strstr(res, "totalBlocks")
```
6. `strstr(NULL, ...)` segfaults.
It can also be worse: `is_resp_ok` may call `free_resp(r)` while the
callback is still writing into `r`, causing use-after-free.
## Why It Appears At `check_space`
This is probably timing. Earlier responses happened to finish copying
before the polling thread observed `ret`; `check_space` just hit the
race.
The new metrics PR did not reach `check_get_metrics`; the crash happens
before it. But since the PR now makes `make testLibstorage` run the C
test consistently, it exposes this existing test harness race.
## Minimal Fix
1. In `callback`, set `r->ret = ret` last, after `msg`, `chunk`, and
`len` are fully populated.
2. Add defensive `res == NULL` checks in tests like `check_space`,
similar to `check_get_metrics`.
## Longer-Term Fix
Ideally, replace polling shared fields with a mutex/condition variable
or atomics, because the current cross-thread access is technically a C
data race.
```
For point 3: the current fix improves ordering, but it still relies on unsynchronized reads/writes between threads. In C, one thread writing `r->ret`, `r->msg`, and `r->len` while another thread reads them without atomics or a lock is undefined behavior. Practically, it may work most of the time, but the compiler/CPU are not required to make those writes visible in the order we expect.
A proper fix would make `Resp` thread-safe. The cleanest approach is usually:
```c
typedef struct
{
pthread_mutex_t mutex;
pthread_cond_t cond;
bool done;
int ret;
char *msg;
char *chunk;
size_t len;
} Resp;
```
Then:
- `callback` locks the mutex.
- It writes `msg`, `chunk`, `len`, and `ret`.
- It sets `done = true`.
- It signals the condition variable.
- It unlocks.
And:
- `wait_resp` locks the mutex.
- It waits on the condition variable until `done == true` or timeout.
- It unlocks.
This removes the sleep-based polling and gives the test a real happens-before relationship between callback writes and test-thread reads.
Because on MacOS Nix does not have proper filesystem namespacing like on
linux the /tmp filesystem is the real /tmp on the machine.
This can cause permission issues when different Nix build users creat it:
> cannot create directory: /tmp/nim/koch_d
Signed-off-by: Jakub Sokołowski <jakub@status.im>