Skip to content

Errors and Retries

NihilDigit edited this page Sep 30, 2026 · 3 revisions

Errors and Retries

What throws what

  • PikPakException — PikPak itself reported a failure: an error envelope (errorCode, errorMessage, errorDescription), a status the SDK does not recover from, or a response it could not make sense of. httpStatus, headers and rawBody are kept whenever the failure was seen at the HTTP layer; they are how a caller tells a 502 from a 403 from an envelope error.
    • UrlExpiredException — a signed CDN or OSS link was refused with 401/403. The CDN answers an expired signature with 403 and an empty body, which only the status tells apart from a deleted file.
    • InstantContentUnavailableException — instantCreate on content PikPak does not hold; see Magnets and Instant Create.
    • ShareUnavailableException, ArchivePasswordException — refusals that arrive as an HTTP 200 with a status inside.
  • The engine's own IO exception — a request that got no answer at all: a timeout, a refused connection, a dropped network. It is not wrapped, because callers depend on telling "offline" from "refused": the two call for opposite handling.
  • Whatever a password supplier throws, unchanged.
  • CancellationException passes through everywhere.

Predicates on PikPakException: isCaptchaRequired and isRefreshTokenInvalid. error_code 9 is not captcha-only — a trashed file's detail and a few refusals (file_in_recycle_bin, file_move_or_copy_to_cur, file_restore_own) use it too — so isCaptchaRequired also checks that the message names a captcha. login() never throws isRefreshTokenInvalid; see Getting Started.

What the client retries

Transport and 5xx/429, before the response is handed over. The HTTP layer retries a request per retryPolicy (3 attempts, 200 ms doubling to 5 s) and honours Retry-After up to 30 s. It never retries once the caller's block has started reading the response: the block may already have written bytes somewhere. On the last attempt a 5xx is handed to the block, which is where a range read sees a 503 as backpressure.

A POST only when the server cannot have acted on it. A 429 or 503 refuses the request; a failure before any byte was sent — no connection, no route, a name that did not resolve, a TLS handshake that was cut — never reached the server. Those are replayed. A read timeout, a connection reset after the body went out, a 502 from a gateway whose backend committed: the effect is unknown, and replaying a folder creation, an offline task or a multipart completion would make a duplicate, charge quota twice, or find the upload already done and report it gone. They are thrown instead. GET, DELETE, PATCH and PUT are replayed on all of them. "Before sending" is recognised by the platform's own exception types; on iOS only a connect timeout is, so fewer POSTs are replayed there.

Captcha, once. An API call made with a captchaAction that comes back error_code 9 naming a captcha gets a fresh captcha token for that action and is sent once more. Concurrent failures on the same token produce one refresh: the token each request was sent with is captured before sending and passed in, and a refresh finds the current token already changed and reuses it. A snapshot taken inside the refresh would be too late — a coroutine arriving after the first refresh would snapshot the fresh token and refresh it again. One token serves every action; a token minted for one action has so far been accepted on all of them, but whether the server would ever bind it is not measured.

401, once. A token the server refuses before its own expiry sends the request through the login ladder again, passing over the refused token, and retries once. Concurrent 401s on the same token produce one re-authentication, the same way.

Range reads add their own layer on top — expiry refresh, 503 waits, resuming truncated bodies, moving off silent and slow hosts — described in Playback. An attempt the reader sent to a sibling host that then fails, with a 403, a 404, a refused handshake or silence, is not a failure of the read: the sibling is set aside, the link is not refreshed, and the next attempt goes to the link's own host without counting against the read's attempts. A 503 there is still backpressure. A block of the file cache is attempted 3 times, each with all of that underneath, before its reads fail. A download fails the same way and is not retried beyond that: calling download again is the retry, and it fetches only what the store does not hold yet.

Rate limiting

API calls take a token from rateLimiter: 5 per second with a burst of 5 by default, to stay under the captcha wall. The CDN is not rate limited by the SDK; a caller that wants a bandwidth ceiling on downloads passes a BandwidthLimiter to downloadTo (Playback). Waiters are handed staggered reservations so they do not all wake at once; one that is cancelled while waiting gives its reservation back — kept, a cancelled search of fifty queued listings left the next interactive request ten seconds behind nobody. Pass one limiter to several clients to share a budget.

Timeouts

The SDK-built API client connects within 15 s, reads with at most 30 s of silence and gives a whole call 60 s: it carries JSON only, and sign-in runs under the client's auth lock, where a stalled request would hold every other login behind it. The CDN client connects within 15 s and allows a minute of silence, with no overall limit; range reads watch their own, shorter deadlines (Playback).

Clone this wiki locally