Skip to content

[adapters] Register the S3 test's error collector before the reader - #6785

Merged
gz merged 1 commit into
mainfrom
fix-s3-list-object-read-error-hang
Aug 1, 2026
Merged

[adapters] Register the S3 test's error collector before the reader#6785
gz merged 1 commit into
mainfrom
fix-s3-list-object-read-error-hang

Conversation

@gz

@gz gz commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

transport::s3::test::list_object_read_error could block forever, holding the whole test binary and every job waiting on it. A merge queue run sat for an hour in this test with no output and no failure: https://github.com/feldera/feldera/actions/runs/30675189907/job/91302225039

What happens

Order Site
1 s3.rs test_setup installs a no-op on_error callback
2 s3.rs:399 new_inner spawns the s3-input-tokio-wrapper thread right away
3 s3.rs:455 worker_task a tokio task starts calling get_object_keys at once, without waiting for a command
4 test replaces on_error with its channel sender
5 test rx.recv(), no timeout

The mock returns NoSuchBucket on the first get_object_keys. When step 3 beats step 4, the error reaches the no-op callback from step 1 and is dropped. Nothing ever sends on the channel, so step 5 waits forever. reader.extend() does not gate this: the listing task loops independently of the command channel.

gdb against the live runner showed the harness parked in recv() on CompletedTest waiting for this one test, which was itself parked in mpsc::Receiver::recv().

Fix

test_setup takes the error callback and installs it before constructing the reader, so no window exists in which errors go to a callback the caller does not want. The wait is bounded as well, which turns a future regression into a failure after a minute rather than a hang.

Bounding the adapter job with timeout-minutes: 30 covers the general case. The suite finishes in 3 to 10 minutes, so without a bound a hang holds a runner for the 6 hour default. The cancel-if-* sentinel scheme cannot cover this, because a job that hangs never reports failure.

Validation

Restoring the old ordering (register the collector after test_setup, plus a sleep to make the race deterministic) reproduces the defect against the bounded wait:

thread 'transport::s3::test::list_object_read_error' panicked at crates/adapters/src/transport/s3.rs:1252:18:
connector reported no error for a missing bucket: Timeout

With the fix, all 6 transport::s3 tests pass.

`transport::s3::test::list_object_read_error` could block forever, holding the
whole test binary and every job waiting on it. A merge queue run sat for an hour
in this test with no output and no failure.

`S3InputReader::new_inner` starts listing objects as soon as it is called, so the
mocked `NoSuchBucket` error can arrive before the test swaps in its own callback.
It then lands in the no-op callback `test_setup` installs and is dropped, and the
test's `rx.recv()`, which had no timeout, waits for a message nobody will send.

`test_setup` now takes the error callback and installs it before constructing the
reader, so no window exists in which errors go to a callback the caller does not
want. Bounding the wait as well turns a future regression into a failure after a
minute rather than a hang.

Bound the adapter job with `timeout-minutes: 30`. The suite finishes in 3 to 10
minutes, so without a bound a hang holds a runner for the 6 hour default. The
cancel-if-* sentinels cannot cover this: a job that hangs never reports failure.

Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
@gz
gz enabled auto-merge August 1, 2026 02:02
@gz
gz added this pull request to the merge queue Aug 1, 2026
Merged via the queue into main with commit 2b39bc0 Aug 1, 2026
1 check passed
@gz
gz deleted the fix-s3-list-object-read-error-hang branch August 1, 2026 04:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants