fix: wait for the early-delete ACK with an extended deadline
The receiver performs the whole bounded deletion walk (up to MAX_SERVER_DELETE_COUNT unlinks) before answering the delete-before/during manifest, so its STATUS_OK reply can take far longer than the default 60 s per-message receive window. Waiting with the default would make the sender abort AFTER the deletion had already committed on the receiver. Add a timed receive variant (receive_status_timed / protocol_receive_n_data_timed) and use it for the early-manifest ACK with a 1 h explicit deadline; connection errors and EOF still abort immediately.
This commit is contained in:
@@ -597,14 +597,20 @@ static int send_delete_manifest(int fd, ArrayList* manifest) {
|
|||||||
--delete-before/--delete-during, where the extras are removed on the receiver
|
--delete-before/--delete-during, where the extras are removed on the receiver
|
||||||
BEFORE the first byte of file data is sent: the receiver acknowledges with
|
BEFORE the first byte of file data is sent: the receiver acknowledges with
|
||||||
STATUS_OK once the bounded delete committed, or STATUS_ERROR if it could not
|
STATUS_OK once the bounded delete committed, or STATUS_ERROR if it could not
|
||||||
(in which case the sender aborts without streaming any data). */
|
(in which case the sender aborts without streaming any data). The ACK may
|
||||||
|
take much longer than an ordinary per-message round trip because the receiver
|
||||||
|
performs the whole bounded deletion walk (up to MAX_SERVER_DELETE_COUNT
|
||||||
|
unlinks) before replying, so the wait uses a generous explicit deadline
|
||||||
|
instead of the default 60 s receive window. */
|
||||||
|
#define DELETE_ACK_TIMEOUT_SEC 3600
|
||||||
|
|
||||||
static bool send_delete_manifest_early(Client* client, ArrayList* manifest) {
|
static bool send_delete_manifest_early(Client* client, ArrayList* manifest) {
|
||||||
if (!client || !manifest)
|
if (!client || !manifest)
|
||||||
return false;
|
return false;
|
||||||
if (send_delete_manifest(client->file_descriptor, manifest) != 0)
|
if (send_delete_manifest(client->file_descriptor, manifest) != 0)
|
||||||
return false;
|
return false;
|
||||||
Status ack;
|
Status ack;
|
||||||
if (!receive_status(client->file_descriptor, &ack))
|
if (!receive_status_timed(client->file_descriptor, &ack, DELETE_ACK_TIMEOUT_SEC))
|
||||||
return false;
|
return false;
|
||||||
if (ack != STATUS_OK) {
|
if (ack != STATUS_OK) {
|
||||||
log_message(LOG_LEVEL_ERROR, "Server failed to delete files before the transfer");
|
log_message(LOG_LEVEL_ERROR, "Server failed to delete files before the transfer");
|
||||||
|
|||||||
+26
-2
@@ -302,15 +302,25 @@ bool protocol_send_n_data(ProtocolSession* session, const void* data, size_t dat
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool protocol_receive_n_data_timed(ProtocolSession* session, void* data, size_t data_size,
|
||||||
|
int timeout_sec);
|
||||||
|
|
||||||
bool protocol_receive_n_data(ProtocolSession* session, void* data, size_t data_size) {
|
bool protocol_receive_n_data(ProtocolSession* session, void* data, size_t data_size) {
|
||||||
|
return protocol_receive_n_data_timed(session, data, data_size, RECEIVE_TIMEOUT_SEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool protocol_receive_n_data_timed(ProtocolSession* session, void* data, size_t data_size,
|
||||||
|
int timeout_sec) {
|
||||||
log_debug_message(LOG_DEBUG_IO, " Receiving n Data: %zu", data_size);
|
log_debug_message(LOG_DEBUG_IO, " Receiving n Data: %zu", data_size);
|
||||||
if (!session)
|
if (!session)
|
||||||
return false;
|
return false;
|
||||||
int fd = session->read_fd;
|
int fd = session->read_fd;
|
||||||
|
if (timeout_sec <= 0)
|
||||||
|
timeout_sec = RECEIVE_TIMEOUT_SEC;
|
||||||
|
|
||||||
struct timespec deadline;
|
struct timespec deadline;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &deadline);
|
clock_gettime(CLOCK_MONOTONIC, &deadline);
|
||||||
deadline.tv_sec += RECEIVE_TIMEOUT_SEC;
|
deadline.tv_sec += timeout_sec;
|
||||||
|
|
||||||
size_t total_bytes_received = 0;
|
size_t total_bytes_received = 0;
|
||||||
short wait_events = POLLIN;
|
short wait_events = POLLIN;
|
||||||
@@ -319,7 +329,7 @@ bool protocol_receive_n_data(ProtocolSession* session, void* data, size_t data_s
|
|||||||
struct pollfd pfd = {.fd = fd, .events = wait_events};
|
struct pollfd pfd = {.fd = fd, .events = wait_events};
|
||||||
int poll_result = poll(&pfd, 1, deadline_remaining_ms(&deadline));
|
int poll_result = poll(&pfd, 1, deadline_remaining_ms(&deadline));
|
||||||
if (poll_result == 0) {
|
if (poll_result == 0) {
|
||||||
log_message(LOG_LEVEL_ERROR, "Receive timeout after %ds", RECEIVE_TIMEOUT_SEC);
|
log_message(LOG_LEVEL_ERROR, "Receive timeout after %ds", timeout_sec);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (poll_result < 0) {
|
if (poll_result < 0) {
|
||||||
@@ -516,6 +526,17 @@ bool protocol_receive_status(ProtocolSession* session, Status* status) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* protocol_receive_status with an explicit per-message deadline (seconds).
|
||||||
|
Used where a single reply may legitimately take far longer than the default
|
||||||
|
60 s receive window - e.g. the sender waiting for the early-delete ACK after
|
||||||
|
the receiver committed a large (up to MAX_SERVER_DELETE_COUNT) deletion. */
|
||||||
|
bool protocol_receive_status_timed(ProtocolSession* session, Status* status, int timeout_sec) {
|
||||||
|
if (!protocol_receive_n_data_timed(session, status, sizeof(Status), timeout_sec))
|
||||||
|
return false;
|
||||||
|
log_debug_message(LOG_DEBUG_PROTO, "Received Status: %s", status_to_string(*status));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool send_str(int fd, const char* data) {
|
bool send_str(int fd, const char* data) {
|
||||||
return protocol_send_str(legacy_session(-1, fd), data);
|
return protocol_send_str(legacy_session(-1, fd), data);
|
||||||
}
|
}
|
||||||
@@ -543,3 +564,6 @@ bool send_status(int fd, Status status) {
|
|||||||
bool receive_status(int fd, Status* status) {
|
bool receive_status(int fd, Status* status) {
|
||||||
return protocol_receive_status(legacy_session(fd, -1), status);
|
return protocol_receive_status(legacy_session(fd, -1), status);
|
||||||
}
|
}
|
||||||
|
bool receive_status_timed(int fd, Status* status, int timeout_sec) {
|
||||||
|
return protocol_receive_status_timed(legacy_session(fd, -1), status, timeout_sec);
|
||||||
|
}
|
||||||
|
|||||||
@@ -112,5 +112,10 @@ bool send_int(int file_descriptor, int data);
|
|||||||
bool receive_int(int file_descriptor, int* data);
|
bool receive_int(int file_descriptor, int* data);
|
||||||
bool send_status(int file_descriptor, Status status);
|
bool send_status(int file_descriptor, Status status);
|
||||||
bool receive_status(int file_descriptor, Status* status);
|
bool receive_status(int file_descriptor, Status* status);
|
||||||
|
/* receive_status with an explicit per-message deadline in seconds, instead of
|
||||||
|
the default RECEIVE_TIMEOUT_SEC. A reply that may legitimately take longer
|
||||||
|
(e.g. the early-delete ACK after a large receiver-side deletion) must use
|
||||||
|
this so the sender does not abort after the deletion already committed. */
|
||||||
|
bool receive_status_timed(int file_descriptor, Status* status, int timeout_sec);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user