Skip to content

Add credentials refresh in more locations#332

Draft
mst wants to merge 3 commits into
duckdb:mainfrom
mst:auto_refresh_fix
Draft

Add credentials refresh in more locations#332
mst wants to merge 3 commits into
duckdb:mainfrom
mst:auto_refresh_fix

Conversation

@mst

@mst mst commented May 24, 2026

Copy link
Copy Markdown

Supersedes #165.

Rebased auto_refresh_fix onto current main.

TODO follow-ups:

  • batched RemoveFiles refresh support
  • 4xx handling for non-GET paths
  • regression test
  • Thread safety / single-flight refresh

@mst mst marked this pull request as draft May 24, 2026 07:03
Marcel Steinbach added 2 commits May 24, 2026 10:29
Secret refresh can be triggered by multiple sub-requests within one statement; make SQLLogicTests assert presence rather than exact row count.

Follow-up: consider per-statement/per-secret refresh rate limiting (deferred).
@mst mst force-pushed the auto_refresh_fix branch from cfe5edd to 93b44c2 Compare May 28, 2026 11:43
@KumoSiunaus

Copy link
Copy Markdown

I built auto_refresh_fix against a local MinIO and can confirm it fixes refresh on the glob-listing and range-GET paths. To help with the regression test and 4xx handling for non-GET paths TODOs, I opened a PR into your branch: mst#1

It adds (1) a deterministic sqllogictest covering the open/glob/range-GET/write paths (passes on your branch, fails on main at the new surfaces), and (2) a small fix so the write path actually refreshes — Put/PostRequest report a 4xx via the response status rather than throwing, so the error was raised outside ExecuteWithRefresh and writes never refreshed.

One thing to flag for the Thread safety / single-flight refresh TODO: because ExecuteWithRefresh mutates a shared auth_params by reference, concurrent range GETs (parallel scans) or multipart part PUTs can race on the credential fields during a refresh. Details in the PR.

KumoSiunaus added a commit to KumoSiunaus/duckdb-httpfs that referenced this pull request Jun 12, 2026
duckdb#332 made ExecuteWithRefresh take the handle's S3AuthParams by reference and
TryRefreshCredentials() mutate its credential strings in place. Multiple threads
share one auth_params (parallel scan range GETs on one S3FileHandle; multipart
part PUTs on detached threads sharing one S3HTTPInput), so on credential expiry
concurrent refreshes raced on the std::string fields (UB) and fired redundant
STS calls.

Make the refresh single-flight and synchronized:
- S3AuthParams gains a copy-safe shared_ptr<mutex> + a generation counter and a
  refresh_last_succeeded flag (default-initialized so every value-init path has a
  non-null lock). shared_ptr (not a bare mutex) keeps S3AuthParams copyable.
- TryRefreshCredentials writes the credential fields only under the lock.
  RefreshCredentialsLocked advances the generation on every completed attempt and
  records success; the (captured_generation) overload reuses a peer's completed
  result instead of firing a duplicate STS call, while a later request still gets
  to retry.
- ExecuteWithRefresh snapshots auth_params together with its generation under the
  lock and signs against the snapshot, so signing never tears against a refresh
  and the request knows exactly which generation it used. The snapshot also covers
  the request lambda's own reads (e.g. oauth2_bearer_token). A region correction
  made on the snapshot (glob 301 path) is propagated back to the shared object.
- GetHTTPError and S3FileHandle::CreateClient read region/endpoint via Snapshot()
  so the error/client-creation paths don't race a concurrent refresh.

Initialize's direct callers keep the no-arg TryRefreshCredentials() (single-threaded
open). The any>=400 refresh trigger is unchanged.

Existing secret-refresh and upload tests pass; the fixed build is ThreadSanitizer-
clean on a concurrent parquet-scan refresh workload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@KumoSiunaus

Copy link
Copy Markdown

Follow-up: I've added the single-flight / thread-safety piece for the shared auth_params (one refresh per expiry via a generation counter + snapshot-under-lock for signing, covering the parallel range-GET and multipart part-PUT paths) to that PR — mst#1. Addresses the "single-flight refresh" TODO; happy to reshape it however fits best.

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.

3 participants