Skip to content

Fix verify() stalling until expiry after a select() timeout#211

Open
djw8605 wants to merge 1 commit into
scitokens:masterfrom
djw8605:fix/verify-select-stall
Open

Fix verify() stalling until expiry after a select() timeout#211
djw8605 wants to merge 1 commit into
scitokens:masterfrom
djw8605:fix/verify-select-stall

Conversation

@djw8605

@djw8605 djw8605 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Problem

The synchronous verify() loop in src/scitokens_internal.h only calls verify_async_continue() when select() reports I/O activity (select_result > 0).

select() is value-result: on a timeout it clears the fd_sets, and the sets are only repopulated by curl_multi_fdset() inside perform_continue() — which is only reachable through verify_async_continue(). So the first time the issuer takes more than 50 ms to produce socket activity (slow response, DNS resolution, curl internal retry timers, or max_fd == -1 during connection setup), the loop keeps selecting on empty fd_sets, never drives libcurl again, and fails with "Timeout when loading the OIDC metadata" after the full expiry_time deadline (20 s for the enforcer paths).

libcurl's documentation also explicitly requires calling curl_multi_perform() after a select() timeout so it can run internal non-socket work (DNS retries, connect timeouts, timers).

This was a regression introduced in commit cb1463f (monitoring API, #182), which replaced an unconditional verify_async_continue() call with the select_result > 0 condition.

Impact

Every enforcer_test / enforcer_generate_acls / validator_validate call that triggers a key fetch or refresh (every next_update interval, default 600 s) hangs for the full 20 s deadline and then fails whenever the issuer doesn't respond within 50 ms — i.e., essentially any real-world WAN latency.

Fix

Continue on select_result >= 0 so timeouts drive the transfer forward; only -1 (signal/interrupt) skips the continue call.

Testing

  • ctest unit, env_config, and monitoring suites pass.

🤖 Generated with Claude Code

The synchronous verify() loop only called verify_async_continue() when
select() reported I/O activity (select_result > 0).  select() clears
the fd_sets on timeout, and the sets are only repopulated inside
curl_multi_fdset() via verify_async_continue(), so after the first
50ms period with no socket activity the loop kept selecting on empty
fd_sets and never drove libcurl again, spinning until expiry_time and
failing with 'Timeout when loading the OIDC metadata'.

libcurl also requires curl_multi_perform() to be called after a
select() timeout so it can run its internal (non-socket) work such as
DNS retries and connection timeouts.

Continue on select_result >= 0 so timeouts drive the transfer forward.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant