Fix GET request returning corrupted data for multi-chunk blobs

Four bugs caused data mismatches between PUT and GET operations:

1. chunk_store_exists() used file_open (O_CREAT) to check if a chunk
   file exists, which created empty files as a side effect. Servers
   would then read from these empty files and return uninitialized
   heap memory as chunk data. Fixed by using file_exists (access())
   instead.

2. Server's process_fetch_chunk checked `ret < 0` after
   chunk_store_read, missing the EOF case (ret == 0) from empty
   files. Changed to `ret <= 0`.

3. begin_transfers() == 0 was used as a completion check in both
   STEP_FETCH_CHUNK and STEP_STORE_CHUNK, but returning 0 only means
   "no new transfers started", not "all done". With concurrent chunk
   transfers, this caused premature completion, leaving in-flight
   transfers to hit UNREACHABLE. Fixed by using
   all_chunk_transfers_completed() instead.

4. choose_store_locations_for_chunk() returned [0, 1] for ALL chunks
   regardless of index, while GET fetches chunk i from servers
   (i+j) % num_servers. This made some chunks unreachable. Fixed by
   using (chunk_idx + j) % num_servers for store locations.

Also added SO_REUSEADDR to the server listen socket so restarting
the cluster doesn't fail with "Couldn't setup TCP listener", and
added a note on mock_access about the simulation mismatch.

https://claude.ai/code/session_01JniNxxDP4NbsDXNGmGNG7H
This commit is contained in:
Claude
2026-02-21 22:31:33 +00:00
parent fa3dc2483b
commit b0cc529773
6 changed files with 126 additions and 20 deletions
+11 -11
View File
@@ -165,6 +165,9 @@ void toastyfs_free(ToastyFS *tfs)
free(tfs->put_data);
}
static int find_completed_transfer_for_hash(ToastyFS *tfs, SHA256 hash);
static bool all_chunk_transfers_completed(ToastyFS *tfs);
static bool
transfer_for_hash_already_started(ToastyFS *tfs, SHA256 hash)
{
@@ -400,12 +403,11 @@ static int process_message(ToastyFS *tfs,
mark_waiting_transfers_for_hash_as_aborted(tfs, resp.hash);
client_log(tfs, "RECV FETCH_RESP", "size=%u", resp.size);
if (begin_transfers(tfs) == 0) {
begin_transfers(tfs);
if (all_chunk_transfers_completed(tfs)) {
tfs->error = TOASTYFS_ERROR_VOID;
tfs->step = STEP_GET_DONE;
client_log_simple(tfs, "ALL CHUNKS FETCHED");
} else {
tfs->step = STEP_FETCH_CHUNK;
}
} else {
@@ -434,7 +436,8 @@ static int process_message(ToastyFS *tfs,
tfs->transfers[transfer_idx].state = TRANSFER_COMPLETED;
mark_waiting_transfers_for_hash_as_aborted(tfs, ack.hash);
if (begin_transfers(tfs) == 0) {
begin_transfers(tfs);
if (all_chunk_transfers_completed(tfs)) {
CommitPutMessage msg = {
.base = {
@@ -462,9 +465,6 @@ static int process_message(ToastyFS *tfs,
tfs->step = STEP_COMMIT;
client_log(tfs, "SEND COMMIT_PUT", "key=%s chunks=%d req=%lu",
tfs->key, tfs->num_chunks, tfs->request_id);
} else {
tfs->step = STEP_STORE_CHUNK;
}
} else {
@@ -748,10 +748,10 @@ int toastyfs_register_events(ToastyFS *tfs, void **ctxs, struct pollfd *pdata, i
}
static void
choose_store_locations_for_chunk(ToastyFS *tfs, int *locations)
choose_store_locations_for_chunk(ToastyFS *tfs, int chunk_idx, int *locations)
{
for (int i = 0; i < REPLICATION_FACTOR; i++) {
locations[i] = i % tfs->num_servers;
for (int j = 0; j < REPLICATION_FACTOR; j++) {
locations[j] = (chunk_idx + j) % tfs->num_servers;
}
}
@@ -802,7 +802,7 @@ int toastyfs_async_put(ToastyFS *tfs, char *key, int key_len,
SHA256 hash = compute_chunk_hash(data_copy + offset, size);
int locations[REPLICATION_FACTOR];
choose_store_locations_for_chunk(tfs, locations);
choose_store_locations_for_chunk(tfs, i, locations);
for (int j = 0; j < REPLICATION_FACTOR; j++)
add_transfer(tfs, hash, locations[j], data_copy + offset, size);