AtProtoClient's clock never reaches its XRPC transport #187

Closed
opened 2026-09-27 17:29:37 +00:00 by Grandiras · 0 comments
Owner

Description

AtProtoClient's internal constructor (AtProtoClient(options, httpClient, sessionStore, logger, TimeProvider timeProvider), used by tests) builds its XrpcClient without setting XrpcClient.TimeProvider. The clock only reaches SessionManager.

XrpcClient.TimeProvider drives the 429 retry Task.Delay, the Retry-After / RateLimit-Reset arithmetic, and the timestamps on the exceptions it creates. Production is unaffected, since the public constructor always passes TimeProvider.System. But tests have to work around it: VideoUploadTests.UploadVideoAsync_BacksOffExponentially builds its own XrpcClient + VideoClient to get its clock into the transport.

Steps to Reproduce

  1. Build an AtProtoClient through the internal constructor with a fake TimeProvider that records the timers it is asked for.
  2. Answer a call with 429 (no Retry-After), then 200.

Expected Behavior

The retry back-off (1 s, with jitter) goes through the injected clock.

Actual Behavior

The fake clock sees no delay; the retry waits on the system clock.

ATProtoNet Version

main (0.7.0 cycle)

.NET Version

10.0

Fix

Pass timeProvider to the XrpcClient initializer alongside UserAgent and RateLimit, add a regression test that observes the retry back-off through a client-level clock, and drop the video test's workaround.

### Description `AtProtoClient`'s internal constructor (`AtProtoClient(options, httpClient, sessionStore, logger, TimeProvider timeProvider)`, used by tests) builds its `XrpcClient` without setting `XrpcClient.TimeProvider`. The clock only reaches `SessionManager`. `XrpcClient.TimeProvider` drives the 429 retry `Task.Delay`, the `Retry-After` / `RateLimit-Reset` arithmetic, and the timestamps on the exceptions it creates. Production is unaffected, since the public constructor always passes `TimeProvider.System`. But tests have to work around it: `VideoUploadTests.UploadVideoAsync_BacksOffExponentially` builds its own `XrpcClient` + `VideoClient` to get its clock into the transport. ### Steps to Reproduce 1. Build an `AtProtoClient` through the internal constructor with a fake `TimeProvider` that records the timers it is asked for. 2. Answer a call with 429 (no `Retry-After`), then 200. ### Expected Behavior The retry back-off (1 s, with jitter) goes through the injected clock. ### Actual Behavior The fake clock sees no delay; the retry waits on the system clock. ### ATProtoNet Version `main` (0.7.0 cycle) ### .NET Version 10.0 ### Fix Pass `timeProvider` to the `XrpcClient` initializer alongside `UserAgent` and `RateLimit`, add a regression test that observes the retry back-off through a client-level clock, and drop the video test's workaround.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/ATProto.NET#187
No description provided.