Clean up download logic for missing/lost chunks

This commit is contained in:
2025-11-14 11:56:29 +01:00
parent 787807031b
commit 2bbe1ac0e8
5 changed files with 161 additions and 104 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
CFLAGS = -Wall -Wextra -ggdb -fsanitize=address,undefined CFLAGS = -Wall -Wextra -ggdb
COVERAGE_CFLAGS = $(CFLAGS) --coverage COVERAGE_CFLAGS = $(CFLAGS) --coverage
COVERAGE_LFLAGS = --coverage COVERAGE_LFLAGS = --coverage
+135 -96
View File
@@ -6,6 +6,7 @@
#include "basic.h" #include "basic.h"
#include "byte_queue.h" #include "byte_queue.h"
#include "config.h" #include "config.h"
#include "hash_set.h"
#include "sha256.h" #include "sha256.h"
#include "message.h" #include "message.h"
#include "file_system.h" #include "file_system.h"
@@ -13,49 +14,55 @@
#include "chunk_server.h" #include "chunk_server.h"
static void static void
pending_download_list_init(PendingDownloadList *list) download_targets_init(DownloadTargets *targets)
{ {
list->count = 0; targets->count = 0;
list->capacity = 0; targets->capacity = 0;
list->items = NULL; targets->items = NULL;
} }
static void static void
pending_download_list_free(PendingDownloadList *list) download_targets_free(DownloadTargets *targets)
{ {
sys_free(list->items); sys_free(targets->items);
}
static void
download_targets_remove(DownloadTargets *targets, SHA256 hash)
{
assert(0); // TODO: remove all downloads of this chunk
} }
static int static int
pending_download_list_add(PendingDownloadList *list, Address addr, SHA256 hash) download_targets_add(DownloadTargets *targets, Address addr, SHA256 hash)
{ {
// Avoid duplicates // Avoid duplicates
for (int i = 0; i < list->count; i++) for (int i = 0; i < targets->count; i++)
if (addr_eql(list->items[i].addr, addr) && !memcmp(&list->items[i].hash, &hash, sizeof(SHA256))) if (addr_eql(targets->items[i].addr, addr) && !memcmp(&targets->items[i].hash, &hash, sizeof(SHA256)))
return 0; return 0;
if (list->count == list->capacity) { if (targets->count == targets->capacity) {
int new_capacity; int new_capacity;
if (list->capacity == 0) if (targets->capacity == 0)
new_capacity = 8; new_capacity = 8;
else else
new_capacity = 2 * list->capacity; new_capacity = 2 * targets->capacity;
PendingDownload *new_items = sys_malloc(new_capacity * sizeof(PendingDownload)); DownloadTarget *new_items = sys_malloc(new_capacity * sizeof(DownloadTarget));
if (new_items == NULL) if (new_items == NULL)
return -1; return -1;
if (list->capacity > 0) { if (targets->capacity > 0) {
memcpy(new_items, list->items, list->count * sizeof(list->items[0])); memcpy(new_items, targets->items, targets->count * sizeof(targets->items[0]));
sys_free(list->items); sys_free(targets->items);
} }
list->items = new_items; targets->items = new_items;
list->capacity = new_capacity; targets->capacity = new_capacity;
} }
list->items[list->count++] = (PendingDownload) { addr, hash }; targets->items[targets->count++] = (DownloadTarget) { addr, hash };
return 0; return 0;
} }
@@ -152,7 +159,7 @@ static bool chunk_store_exists(ChunkStore *store, SHA256 hash)
string path = hash2path(store, hash, buf); string path = hash2path(store, hash, buf);
// Try to open the file to check if it exists // Try to open the file to check if it exists
// TODO: this isn't right // TODO: this isn't right. There should be something like file_exists
Handle fd; Handle fd;
if (file_open(path, &fd) == 0) { if (file_open(path, &fd) == 0) {
file_close(fd); file_close(fd);
@@ -209,45 +216,52 @@ static int send_error(TCP *tcp, int conn_idx,
return 0; return 0;
} }
static void start_download_if_necessary(ChunkServer *state) static bool download_targets_pop(DownloadTargets *targets,
DownloadTarget *target)
{ {
if (state->pending_download_list.count == 0 || state->downloading) if (targets->count == 0)
return; return false;
*target = targets->items[0];
for (int i = 0; i < targets->count-1; i++)
targets->items[i] = targets->items[i+1];
targets->count--;
if (targets->count == 0) {
free(targets->items);
targets->items = NULL;
targets->capacity = 0;
}
return true;
}
static void start_download(ChunkServer *state)
{
if (state->downloading)
return; // Already started
DownloadTarget target;
if (!download_targets_pop(&state->download_targets, &target))
return; // No more downloads
ByteQueue *output; ByteQueue *output;
if (tcp_connect(&state->tcp, state->pending_download_list.items[0].addr, TAG_CHUNK_SERVER, &output) < 0) { if (tcp_connect(&state->tcp, target.addr, TAG_CHUNK_SERVER, &output) < 0)
// Failed to connect, remove this download from the list and try next time return; // Couldn't start connect operation
if (state->pending_download_list.count > 1) {
memmove(&state->pending_download_list.items[0],
&state->pending_download_list.items[1],
(state->pending_download_list.count - 1) * sizeof(PendingDownload));
}
state->pending_download_list.count--;
return;
}
state->downloading = true;
MessageWriter writer; MessageWriter writer;
message_writer_init(&writer, output, MESSAGE_TYPE_DOWNLOAD_CHUNK); message_writer_init(&writer, output, MESSAGE_TYPE_DOWNLOAD_CHUNK);
// Write the hash of the chunk to download message_write(&writer, &target.hash, sizeof(target.hash));
message_write(&writer, &state->pending_download_list.items[0].hash,
sizeof(state->pending_download_list.items[0].hash));
// Request the entire chunk: offset = 0
uint32_t offset = 0; uint32_t offset = 0;
message_write(&writer, &offset, sizeof(offset)); message_write(&writer, &offset, sizeof(offset));
// Request maximum reasonable chunk size (64MB) uint32_t length = 64 * 1024 * 1024; // TODO: there should be a special value for this
uint32_t length = 64 * 1024 * 1024;
message_write(&writer, &length, sizeof(length)); message_write(&writer, &length, sizeof(length));
if (!message_writer_free(&writer)) { if (!message_writer_free(&writer))
// Failed to send message, close connection and retry return; // ???
state->downloading = false;
return; state->current_download_target_hash = target.hash;
} state->downloading = true;
} }
static int process_metadata_server_sync_2(ChunkServer *state, static int process_metadata_server_sync_2(ChunkServer *state,
@@ -431,15 +445,15 @@ process_metadata_server_sync_4(ChunkServer *state, int conn_idx, ByteView msg)
// Add to pending download list // Add to pending download list
for (uint32_t k = 0; k < total_ipv4; k++) for (uint32_t k = 0; k < total_ipv4; k++)
pending_download_list_add( download_targets_add(
&state->pending_download_list, &state->download_targets,
(Address) { .is_ipv4=true, .ipv4=ipv4[k], .port=ipv4_port[k] }, (Address) { .is_ipv4=true, .ipv4=ipv4[k], .port=ipv4_port[k] },
hash hash
); );
for (uint32_t k = 0; k < total_ipv6; k++) for (uint32_t k = 0; k < total_ipv6; k++)
pending_download_list_add( download_targets_add(
&state->pending_download_list, &state->download_targets,
(Address) { .is_ipv4=false, .ipv6=ipv6[k], .port=ipv6_port[k] }, (Address) { .is_ipv4=false, .ipv6=ipv6[k], .port=ipv6_port[k] },
hash hash
); );
@@ -448,16 +462,61 @@ process_metadata_server_sync_4(ChunkServer *state, int conn_idx, ByteView msg)
if (binary_read(&reader, NULL, 1)) if (binary_read(&reader, NULL, 1))
return -1; return -1;
start_download_if_necessary(state); start_download(state);
// There is no need to respond here // There is no need to respond here
return 0; return 0;
} }
static int
process_metadata_server_auth_response(ChunkServer *state, int conn_idx, ByteView msg)
{
BinaryReader reader;
if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
return -1;
// TODO: Read whether the metadata server already
// holds our list of chunks. If it doesn't,
// add all held chunks to the add list.
bool avoid_full_scan = false;
if (binary_read(&reader, NULL, 1))
return -1;
if (avoid_full_scan)
return 0;
DirectoryScanner scanner;
if (directory_scanner_init(&scanner, state->path) < 0) {
assert(0); // TODO
}
for (;;) {
string name;
int ret = directory_scanner_next(&scanner, &name);
if (ret < 0) {
assert(0); // TODO
}
if (ret == 1)
break;
assert(ret == 0);
// TODO: translate name to hash
if (hash_set_insert(&state->cs_add_list, hash) < 0) {
assert(0); // TODO
}
}
directory_scanner_free(&scanner);
}
static int static int
process_metadata_server_message(ChunkServer *state, int conn_idx, uint16_t type, ByteView msg) process_metadata_server_message(ChunkServer *state, int conn_idx, uint16_t type, ByteView msg)
{ {
switch (type) { switch (type) {
case MESSAGE_TYPE_AUTH_RESPONSE: return process_metadata_server_auth_response(state, conn_idx, msg);
case MESSAGE_TYPE_SYNC_2: return process_metadata_server_sync_2(state, conn_idx, msg); case MESSAGE_TYPE_SYNC_2: return process_metadata_server_sync_2(state, conn_idx, msg);
case MESSAGE_TYPE_SYNC_4: return process_metadata_server_sync_4(state, conn_idx, msg); case MESSAGE_TYPE_SYNC_4: return process_metadata_server_sync_4(state, conn_idx, msg);
} }
@@ -470,22 +529,9 @@ process_chunk_server_download_error(ChunkServer *state, int conn_idx, ByteView m
(void) msg; (void) msg;
(void) conn_idx; (void) conn_idx;
// Download failed, mark as not downloading and remove the failed item
state->downloading = false; state->downloading = false;
if (state->pending_download_list.count > 0) { start_download(state);
// Remove the first item (the one that failed)
if (state->pending_download_list.count > 1) {
memmove(&state->pending_download_list.items[0],
&state->pending_download_list.items[1],
(state->pending_download_list.count - 1) * sizeof(PendingDownload));
}
state->pending_download_list.count--;
}
// Try next download if any pending
start_download_if_necessary(state);
return 0; return 0;
} }
@@ -496,53 +542,43 @@ process_chunk_server_download_success(ChunkServer *state, int conn_idx, ByteView
BinaryReader reader = { msg.ptr, msg.len, 0 }; BinaryReader reader = { msg.ptr, msg.len, 0 };
// Read header
if (!binary_read(&reader, NULL, sizeof(MessageHeader))) if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
return -1; return -1;
// Read data length
uint32_t data_len; uint32_t data_len;
if (!binary_read(&reader, &data_len, sizeof(data_len))) if (!binary_read(&reader, &data_len, sizeof(data_len)))
return -1; return -1;
// Read the chunk data
if (data_len > (uint32_t) (reader.len - reader.cur)) if (data_len > (uint32_t) (reader.len - reader.cur))
return -1; return -1;
string data = { (char*) reader.src + reader.cur, data_len }; string data = { (char*) reader.src + reader.cur, data_len };
if (!binary_read(&reader, NULL, data_len))
return -1;
if (binary_read(&reader, NULL, 1))
return -1;
// Store the downloaded chunk // Store the downloaded chunk
if (chunk_store_add(&state->store, data) < 0) { if (chunk_store_add(&state->store, data) < 0) {
// Failed to store, treat as error assert(0); // TODO
state->downloading = false;
if (state->pending_download_list.count > 0) {
if (state->pending_download_list.count > 1) {
memmove(&state->pending_download_list.items[0],
&state->pending_download_list.items[1],
(state->pending_download_list.count - 1) * sizeof(PendingDownload));
}
state->pending_download_list.count--;
}
start_download_if_necessary(state);
return 0;
} }
// Download succeeded, mark as not downloading and remove the completed item // The download succeded!
// Mark that we are not downloading anymore
state->downloading = false; state->downloading = false;
if (state->pending_download_list.count > 0) { // Since we managed to acquire this chunk, we can
// Remove the first item (the one that succeeded) // remove any other downloads to it.
if (state->pending_download_list.count > 1) { download_targets_remove(&state->download_targets, state->current_download_target_hash);
memmove(&state->pending_download_list.items[0],
&state->pending_download_list.items[1], // Add the newly acquired chunk to the add list
(state->pending_download_list.count - 1) * sizeof(PendingDownload)); if (hash_set_insert(&state->cs_add_list, state->current_download_target_hash) < 0) {
} assert(0); // TODO
state->pending_download_list.count--;
} }
// Try next download if any pending start_download(state);
start_download_if_necessary(state);
return 0; return 0;
} }
@@ -784,6 +820,9 @@ static int send_sync_message(ChunkServer *state)
MessageWriter writer; MessageWriter writer;
message_writer_init(&writer, output, MESSAGE_TYPE_SYNC); message_writer_init(&writer, output, MESSAGE_TYPE_SYNC);
// TODO: May be worth it to add a limit to how many
// items from the add list are sent every update
// to keep messages under 4GB.
uint32_t count = state->cs_add_list.count; // TODO: check implicit conversions uint32_t count = state->cs_add_list.count; // TODO: check implicit conversions
message_write(&writer, &count, sizeof(count)); message_write(&writer, &count, sizeof(count));
@@ -831,7 +870,7 @@ int chunk_server_init(ChunkServer *state, int argc, char **argv, void **contexts
} }
state->downloading = false; state->downloading = false;
pending_download_list_init(&state->pending_download_list); download_targets_init(&state->download_targets);
hash_set_init(&state->cs_add_list); hash_set_init(&state->cs_add_list);
hash_set_init(&state->cs_lst_list); hash_set_init(&state->cs_lst_list);
timed_hash_set_init(&state->cs_rem_list); timed_hash_set_init(&state->cs_rem_list);
@@ -890,7 +929,7 @@ int chunk_server_init(ChunkServer *state, int argc, char **argv, void **contexts
int chunk_server_free(ChunkServer *state) int chunk_server_free(ChunkServer *state)
{ {
pending_download_list_free(&state->pending_download_list); download_targets_free(&state->download_targets);
timed_hash_set_free(&state->cs_rem_list); timed_hash_set_free(&state->cs_rem_list);
hash_set_free(&state->cs_lst_list); hash_set_free(&state->cs_lst_list);
hash_set_free(&state->cs_add_list); hash_set_free(&state->cs_add_list);
@@ -1015,7 +1054,7 @@ int chunk_server_step(ChunkServer *state, void **contexts, struct pollfd *polled
// TODO: periodically look for chunks that have their hashes messed up and delete them // TODO: periodically look for chunks that have their hashes messed up and delete them
// Periodically retry pending downloads // Periodically retry pending downloads
start_download_if_necessary(state); start_download(state);
if (state->disconnect_time != INVALID_TIME) { if (state->disconnect_time != INVALID_TIME) {
Time reconnect_time = state->disconnect_time + (Time) CHUNK_SERVER_RECONNECT_TIME * 1000000000; Time reconnect_time = state->disconnect_time + (Time) CHUNK_SERVER_RECONNECT_TIME * 1000000000;
+10 -5
View File
@@ -3,6 +3,7 @@
#include <limits.h> #include <limits.h>
#include "basic.h"
#include "metadata_server.h" #include "metadata_server.h"
#include "tcp.h" #include "tcp.h"
@@ -18,13 +19,13 @@ typedef struct {
typedef struct { typedef struct {
Address addr; Address addr;
SHA256 hash; SHA256 hash;
} PendingDownload; } DownloadTarget;
typedef struct { typedef struct {
DownloadTarget *items;
int count; int count;
int capacity; int capacity;
PendingDownload *items; } DownloadTargets;
} PendingDownloadList;
typedef struct { typedef struct {
@@ -41,9 +42,13 @@ typedef struct {
ChunkStore store; ChunkStore store;
bool downloading; // --- Download Management ---
PendingDownloadList pending_download_list; bool downloading;
SHA256 current_download_target_hash;
DownloadTargets download_targets;
// --- Chunk Management ---
// List of chunks added since the last update // List of chunks added since the last update
HashSet cs_add_list; HashSet cs_add_list;
+1 -1
View File
@@ -34,7 +34,7 @@ bool message_writer_free(MessageWriter *writer)
{ {
uint32_t length = byte_queue_size_from_offset(writer->output, writer->start); uint32_t length = byte_queue_size_from_offset(writer->output, writer->start);
byte_queue_patch(writer->output, writer->patch, &length, sizeof(length)); byte_queue_patch(writer->output, writer->patch, &length, sizeof(length));
if (byte_queue_error(writer->output)) if (byte_queue_error(writer->output)) // TODO: is it possible to restore the state of the queue to before the failure?
return false; return false;
return true; return true;
} }
+14 -1
View File
@@ -1,3 +1,5 @@
#include "byte_queue.h"
#include "tcp.h"
#define _GNU_SOURCE #define _GNU_SOURCE
#include <string.h> #include <string.h>
@@ -752,7 +754,18 @@ static int process_chunk_server_auth(MetadataServer *state,
// we accept all connections that provide valid address information. // we accept all connections that provide valid address information.
chunk_server->auth = true; chunk_server->auth = true;
// No need to respond ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
assert(output);
MessageWriter writer;
message_writer_init(&writer, output, MESSAGE_TYPE_AUTH_RESPONSE);
// TODO: Check whether we already hold the chunk list
// of this chunk server. If we do, tell it.
if (!message_writer_free(&writer)) {
assert(0); // TODO
}
return 0; return 0;
} }