Bound a Supply run toggle's session load and CSRF refresh by its deadline (Feature 17)
- loadMe takes an optional abort signal; AuthBootstrap calls it with none. - Each toggle try loads GET /api/v1/me itself under its deadline when the session is not loaded, instead of joining a /me query already in flight that may never answer. - The /me refresh a csrf 403 makes is bounded by the request's signal: a request whose signal aborts stops waiting at once and the refresh is no longer shared, so the next csrf 403 starts a fresh /me. Requests without a signal still share it, and it is aborted only once no request waits. - Tests: a hung /me and a hung csrf refresh during a toggle exchange, both recovered on the next try in order; the client's refresh sharing and abort. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+8
-12
@@ -228,8 +228,9 @@ export type Toggles = {
|
||||
}
|
||||
|
||||
// TOGGLE_DEADLINE is how long, in milliseconds, one try of a toggle — its
|
||||
// GET /api/v1/me and its PATCH — may take before it counts as the server not
|
||||
// answering. Only toggles have a deadline: the API client sets none.
|
||||
// GET /api/v1/me, its PATCH and the /me refresh a csrf 403 to it makes — may
|
||||
// take before it counts as the server not answering. Only toggles have a
|
||||
// deadline: the API client sets none.
|
||||
export const TOGGLE_DEADLINE = 10_000
|
||||
|
||||
// RETRY_FIRST and RETRY_CEILING bound, in milliseconds, how long a toggle
|
||||
@@ -258,14 +259,6 @@ function reachable(network: NetworkStatus, delay: number): Promise<void> {
|
||||
})
|
||||
}
|
||||
|
||||
// beforeDeadline is promise, rejected with signal's reason once it aborts.
|
||||
function beforeDeadline<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
signal.addEventListener('abort', () => reject(signal.reason as Error), { once: true })
|
||||
promise.then(resolve, reject)
|
||||
})
|
||||
}
|
||||
|
||||
// toggleOptions is the mutation every toggle of the week's Supply run is,
|
||||
// made on the Supply run screen (useToggles) or replayed from the queue saved
|
||||
// on this device (useSupplyRunSync).
|
||||
@@ -313,8 +306,11 @@ function toggleOptions(
|
||||
const deadline = new AbortController()
|
||||
const timer = setTimeout(() => deadline.abort(new DOMException(`${where}: no answer in time`, 'TimeoutError')), TOGGLE_DEADLINE)
|
||||
try {
|
||||
const me = queryClient.ensureQueryData({ queryKey: meQueryKey, queryFn: api.loadMe, networkMode: 'always' })
|
||||
await beforeDeadline(me, deadline.signal)
|
||||
// Each try loads /me itself, under its deadline, rather than share
|
||||
// a /me already in flight that may never answer.
|
||||
if (queryClient.getQueryData(meQueryKey) === undefined) {
|
||||
queryClient.setQueryData(meQueryKey, await api.loadMe(deadline.signal))
|
||||
}
|
||||
body = await api.apiFetch<unknown>(path, { method: 'PATCH', body: { needed }, signal: deadline.signal })
|
||||
break
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user