2 Commits
Author SHA1 Message Date
TapTap 558782d339 test: eliminate fork/write race in incremental-check server tests
CI / lint (push) Successful in 2m1s
CI / parity-fast (push) Skipped
CI / parity-full (push) Successful in 21s
CI / sanitizers (address) (push) Successful in 53s
CI / sanitizers (undefined) (push) Successful in 44s
CI / build-and-test (push) Successful in 1m11s
CI / fuzz-build (push) Successful in 47s
CI / coverage (push) Successful in 43s
CI / valgrind (push) Successful in 2m14s
The parent sends the file data body after the receiver's STATUS_NEXT, but
the forked child exited as soon as receive_incremental_check returned.  The
parent's send_data could then race the child's exit into a spurious EPIPE
(seen in the coverage job as test_server.c:1222), or the reverse: the parent
could be descheduled past the child's exit.

Keep the child alive until the parent closes its write end (drain to EOF),
and close the parent's write end before waitpid so the child can observe EOF.
Applied to the three tests sharing the pattern: size-mismatch, FIFO
destination, and FIFO basis.  Child exit status remains the authoritative
assertion.
2026-09-20 12:09:21 +02:00
TapTap 5597e74f6a docs(handoff): v2.28.0 released to main (PR #304, tag v2.28.0)
CI / lint (push) Successful in 2m0s
CI / parity-fast (push) Skipped
CI / parity-full (push) Successful in 19s
CI / sanitizers (address) (push) Successful in 51s
CI / sanitizers (undefined) (push) Successful in 43s
CI / build-and-test (push) Successful in 1m7s
CI / coverage (push) Failing after 31s
CI / fuzz-build (push) Successful in 47s
CI / valgrind (push) Successful in 2m13s
2026-09-20 01:23:49 +02:00
2 changed files with 26 additions and 5 deletions
+5 -2
View File
@@ -9,8 +9,11 @@
`project(FastFileTransfer VERSION 2.28.0)`. The cycle batched all wire `project(FastFileTransfer VERSION 2.28.0)`. The cycle batched all wire
changes (stats counters, filter-rule block, `--verify-basis`) under the one changes (stats counters, filter-rule block, `--verify-basis`) under the one
bump. bump.
- **`main` = `ef76c90`** (tag `v2.26.0`); the 2.27.0/2.28.0 work is on `dev` - **Release `v2.28.0` tagged and merged to `main`** via PR #304
and not yet released. A `dev -> main` v2.28.0 release PR is the next step. (`b4d54504`); tag `v2.28.0`. Main push CI run **585** fully green (lint,
build-and-test, parity-full, ASan, UBSan, fuzz-build, coverage, valgrind).
Gitea release `v2.28.0` published. `dev` and `main` are at the release
content.
- Parity matrix: **116 ✅ / 14 ⚠️ / 27 ❌ = 157** (was 111/13/33 at cycle start). - Parity matrix: **116 ✅ / 14 ⚠️ / 27 ❌ = 157** (was 111/13/33 at cycle start).
- Working tree clean; feature branch deleted; no scratch trees or worktrees. - Working tree clean; feature branch deleted; no scratch trees or worktrees.
+21 -3
View File
@@ -458,6 +458,11 @@ static void test_incremental_check_size_mismatch_full_transfer() {
File* file = receive_incremental_check(p[0], cfg, &skipped); File* file = receive_incremental_check(p[0], cfg, &skipped);
bool ok = file != NULL && !skipped && file->path != NULL && strcmp(file->path, "file.txt") == 0; bool ok = file != NULL && !skipped && file->path != NULL && strcmp(file->path, "file.txt") == 0;
file_destroy(file); file_destroy(file);
/* Stay alive until the parent closes its write end so its send_data can
never race this exit into a spurious EPIPE. */
char drain;
while (read(p[0], &drain, 1) > 0) {
}
config_delete(cfg); config_delete(cfg);
close(p[0]); close(p[0]);
_exit(ok ? 0 : 1); _exit(ok ? 0 : 1);
@@ -487,9 +492,9 @@ static void test_incremental_check_size_mismatch_full_transfer() {
EXPECT_TRUE(send_data(p[1], body)); EXPECT_TRUE(send_data(p[1], body));
data_destroy(body); data_destroy(body);
close(p[1]);
int status; int status;
waitpid(pid, &status, 0); waitpid(pid, &status, 0);
close(p[1]);
config_delete(cfg); config_delete(cfg);
unlink(path); unlink(path);
rmdir(root); rmdir(root);
@@ -1025,6 +1030,11 @@ static void test_incremental_check_fifo_destination_does_not_hang() {
File* file = receive_incremental_check(p[0], cfg, &skipped); File* file = receive_incremental_check(p[0], cfg, &skipped);
bool ok = file != NULL && !skipped; bool ok = file != NULL && !skipped;
file_destroy(file); file_destroy(file);
/* Stay alive until the parent closes its write end so its send_data can
never race this exit into a spurious EPIPE. */
char drain;
while (read(p[0], &drain, 1) > 0) {
}
config_delete(cfg); config_delete(cfg);
close(p[0]); close(p[0]);
_exit(ok ? 0 : 1); _exit(ok ? 0 : 1);
@@ -1051,9 +1061,9 @@ static void test_incremental_check_fifo_destination_does_not_hang() {
EXPECT_TRUE(send_data(p[1], body)); EXPECT_TRUE(send_data(p[1], body));
data_destroy(body); data_destroy(body);
close(p[1]);
int status; int status;
waitpid(pid, &status, 0); waitpid(pid, &status, 0);
close(p[1]);
config_delete(cfg); config_delete(cfg);
unlink(path); unlink(path);
rmdir(root); rmdir(root);
@@ -1191,6 +1201,12 @@ static void test_incremental_check_basis_fifo_does_not_hang() {
File* file = receive_incremental_check(p[0], cfg, &skipped); File* file = receive_incremental_check(p[0], cfg, &skipped);
bool ok = file != NULL && !skipped; bool ok = file != NULL && !skipped;
file_destroy(file); file_destroy(file);
/* The parent sends the data body after the check reply and only then closes
its write end. Stay alive until that EOF so the parent's send_data can
never race this exit into a spurious EPIPE. */
char drain;
while (read(p[0], &drain, 1) > 0) {
}
config_delete(cfg); config_delete(cfg);
close(p[0]); close(p[0]);
_exit(ok ? 0 : 1); _exit(ok ? 0 : 1);
@@ -1222,9 +1238,11 @@ static void test_incremental_check_basis_fifo_does_not_hang() {
EXPECT_TRUE(send_data(p[1], body)); EXPECT_TRUE(send_data(p[1], body));
data_destroy(body); data_destroy(body);
/* Close the write end before waiting: this hands the child EOF so it can
exit, and guarantees the child was still alive for the data write. */
close(p[1]);
int status; int status;
waitpid(pid, &status, 0); waitpid(pid, &status, 0);
close(p[1]);
config_delete(cfg); config_delete(cfg);
unlink(basis_path); unlink(basis_path);
rmdir(basis_dir); rmdir(basis_dir);