feat: implement --delay-updates receiver staging and publication
CI / lint (pull_request) Failing after 22s
CI / build-and-test (pull_request) Skipped
CI / sanitizers (address) (pull_request) Skipped
CI / sanitizers (undefined) (pull_request) Skipped
CI / fuzz-build (pull_request) Skipped
CI / coverage (pull_request) Skipped
CI / valgrind (pull_request) Skipped
CI / lint (pull_request) Failing after 22s
CI / build-and-test (pull_request) Skipped
CI / sanitizers (address) (pull_request) Skipped
CI / sanitizers (undefined) (pull_request) Skipped
CI / fuzz-build (pull_request) Skipped
CI / coverage (pull_request) Skipped
CI / valgrind (pull_request) Skipped
Stage every successfully written file under a private 0700 .fastsync-stage directory inside the receive root and atomically publish all staged files only after the whole protocol stream (manifest/delete handling included) has completed, immediately before the success/outcome frame. On any abort/error before publication nothing is installed and staging is removed; a publish failure aborts the transfer with best-effort cleanup of the remainder (already-published files are not rolled back). Crash leftovers are wiped when the next delayed transfer starts. Wire: new delay_updates config flag (selection-options block), protocol version bumped to 2.6.0, client/server validation rejects --inplace. CLI/usage/validation updated. Works in single-threaded and -m modes (exactly one write_thread stages files; the staged-file registry is mutex-protected; publication runs once after both threads join). --existing/--ignore-existing/--update decide against the final destination at stage time; --backup is deferred to publication. remove_source_files outcomes are only sent after publication so skipped/unpublished sources are never deleted. Default (no flag) behavior is unchanged. Tests: config wire round-trip, CLI parse, --inplace rejection, new test_delay_updates unit suite (27 suites total), and integration TestDelayUpdates covering single/-m parity, incremental reruns, remove source files, receiver-skip ordering, and a deterministic publish-failure abort path.
This commit is contained in:
+18
-7
@@ -1,5 +1,6 @@
|
||||
#include "config.h"
|
||||
#include "chmod.h"
|
||||
#include "delay_updates.h"
|
||||
#include "delta.h"
|
||||
#include "log.h"
|
||||
#include "protocol.h"
|
||||
@@ -84,6 +85,7 @@ static void config_set_defaults(Config* config) {
|
||||
config->ignore_existing = false;
|
||||
config->update = false;
|
||||
config->inplace = false;
|
||||
config->delay_updates = false;
|
||||
config->use_fsync = false;
|
||||
config->append = false;
|
||||
config->append_verify = false;
|
||||
@@ -119,6 +121,7 @@ static void config_set_defaults(Config* config) {
|
||||
config->skip_compress_suffixes = NULL;
|
||||
config->skip_compress_count = 0;
|
||||
config->skip_compress_set = false;
|
||||
config->delay_context = NULL;
|
||||
}
|
||||
|
||||
static bool valid_wire_bool(int value) {
|
||||
@@ -152,6 +155,7 @@ static bool validate_received_config(const Config* config) {
|
||||
valid_wire_bool(config->use_fsync) && valid_wire_bool(config->append_verify) &&
|
||||
valid_wire_bool(config->delete_excluded) && valid_wire_bool(config->delete_after) &&
|
||||
valid_wire_bool(config->relative) && valid_wire_bool(config->prune_empty_dirs) &&
|
||||
valid_wire_bool(config->delay_updates) && !(config->delay_updates && config->inplace) &&
|
||||
valid_wire_bool(config->partial) && valid_wire_bool(config->delete_before) &&
|
||||
valid_wire_bool(config->checksum) && valid_wire_bool(config->eight_bit_output) &&
|
||||
!(config->skip_compress_set && config->use_chunk_serialization) &&
|
||||
@@ -248,6 +252,12 @@ void config_delete(Config* config) {
|
||||
if (config->filters) {
|
||||
array_list_delete(config->filters);
|
||||
}
|
||||
/* A --delay-updates staging tree is transient receiver state: remove any
|
||||
leftovers on every exit path (success already emptied it). */
|
||||
if (config->delay_context)
|
||||
delay_updates_cleanup(config->delay_context);
|
||||
delay_updates_context_destroy(config->delay_context);
|
||||
config->delay_context = NULL;
|
||||
free(config);
|
||||
}
|
||||
|
||||
@@ -287,10 +297,11 @@ static bool send_file_options(int fd, const Config* c) {
|
||||
|
||||
static bool send_selection_options(int fd, const Config* c) {
|
||||
return send_int(fd, c->ignore_existing) && send_int(fd, c->existing) && send_int(fd, c->update) &&
|
||||
send_int(fd, c->inplace) && send_int(fd, c->append) && send_int(fd, c->use_fsync) &&
|
||||
send_int(fd, c->append_verify) && send_int(fd, c->delete_excluded) &&
|
||||
send_int(fd, c->delete_after) && send_n_data(fd, &c->max_delete, sizeof(c->max_delete)) &&
|
||||
send_int(fd, c->relative) && send_int(fd, c->prune_empty_dirs);
|
||||
send_int(fd, c->inplace) && send_int(fd, c->delay_updates) && send_int(fd, c->append) &&
|
||||
send_int(fd, c->use_fsync) && send_int(fd, c->append_verify) &&
|
||||
send_int(fd, c->delete_excluded) && send_int(fd, c->delete_after) &&
|
||||
send_n_data(fd, &c->max_delete, sizeof(c->max_delete)) && send_int(fd, c->relative) &&
|
||||
send_int(fd, c->prune_empty_dirs);
|
||||
}
|
||||
|
||||
static bool send_skip_compress_options(int fd, const Config* c) {
|
||||
@@ -382,9 +393,9 @@ static bool receive_file_options(int fd, Config* c) {
|
||||
}
|
||||
|
||||
static bool receive_selection_options(int fd, Config* c) {
|
||||
bool* flags[] = {&c->ignore_existing, &c->existing, &c->update,
|
||||
&c->inplace, &c->append, &c->use_fsync,
|
||||
&c->append_verify, &c->delete_excluded, &c->delete_after};
|
||||
bool* flags[] = {&c->ignore_existing, &c->existing, &c->update, &c->inplace,
|
||||
&c->delay_updates, &c->append, &c->use_fsync, &c->append_verify,
|
||||
&c->delete_excluded, &c->delete_after};
|
||||
for (size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
|
||||
if (!receive_wire_bool(fd, flags[i]))
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user