Do not re-send non-idempotent writes after ambiguous failures in the b2 package
I maintainer di solito rispondono entro 1 giorno
@ffumero2003 ci sta già lavorando.
Dal 8/11/2026.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 45/100
Direzione di ricerca
Start with b2/backend.go's withBackoff and the write methods listed in the issue, then trace retry classification in base/base.go. Read b2/compose.go and the existing Compose test in b2/compose_extended_wire_test.go for finish cleanup behavior. Run the recording-server cases described in the issue; done means ambiguous writes are not resent, errors carry the known identifiers, and Writer.Close and Compose do not cancel after an ambiguous finish.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Context
This is a follow-up to Backblaze/blazer#167 (merged, closes #165). Since #167, base.Action returns Retry for 408 on non-upload calls and for 502 and 504 on all calls, in addition to 429, 500 and 503. Every control-plane call in the b2 package goes through withBackoff, which re-sends the request whenever base.Action returns Retry, so non-idempotent writes are now also sent again after 408, 502 and 504, as they already were after 500 and after a transport error once the request was sent. A 502 or 504 does not say whether B2 applied the write. When it did, the second attempt either repeats the effect or turns a success into an error:
Client.CreateKey,Bucket.CreateKey: a second key is created; the caller receives only the second key's secret, and the first key stays active until deleted manually.Client.NewBucket:duplicate_bucket_namealthough the bucket was created.WriterandBucket.Composelarge-file start: an extra unfinished large file.Writer.Close(finish): an error although the object exists; withWithCancelOnError, a cancel call on a finished file.Bucket.Compose(finish): an error although the object exists, followed by a deferred cancel of the file it may already have finished.Bucket.Copy(documented at b2/copy.go:109) andObject.Hide: an extra version or hide marker.Object.Delete,Object.Cancel: an error although the operation succeeded.Bucket.Update: a 409 because the revision already moved.
None of these errors says the outcome is unknown, and none carries the identifiers needed to check. This issue asks that the retries be limited to calls that are safe to repeat.
Current behavior
At a5e0d13:
- b2/backend.go:1056-1082:
withBackoffapplies one policy to every method; it retries whenbase.Action(err) == base.Retry(b2/baseline.go:240-242). - Writes wrapped in it:
createBucket(b2/backend.go:329-349),createKey(:375-395),createKeyMultiBucket(:397-417),updateBucket(:450-458),startLargeFile(:518-538),hideFile(:688-708),copyFile(:710-727),deleteFileVersion(:786-794),finishLargeFile(:954-974),cancel(:976-984),Key.del(:1043-1048). - b2/compose.go:237-274:
Composestarts and finishes through the same policy; after a failed finish, the deferred cleanup (:241-248) cancels the large file. b2/compose_extended_wire_test.go:902 expects that cancel. - Classification: base/base.go:94-100 (408 on non-upload calls, and 429, 500, 502, 503 and 504 on all calls, return
Retry), :552-556 (post-send transport errors marked retryable), :213-224 (MaxRetries, 5 for these calls).
Reproduced at a5e0d13 with a recording httptest server that fails the first call once and then succeeds:
status b2_create_key b2_copy_file b2_hide_file b2_update_bucket b2_delete_file_version
408 2, err=nil 2, err=nil 2, err=nil 2, err=nil 2, err=nil
500 2, err=nil 2, err=nil 2, err=nil 2, err=nil 2, err=nil
502 2, err=nil 2, err=nil 2, err=nil 2, err=nil 2, err=nil
504 2, err=nil 2, err=nil 2, err=nil 2, err=nil 2, err=nil
Before #167 (7888c5b), 408, 502 and 504 sent each call once and returned the error. With Compose, a 408, 500, 502 or 504 on the first b2_finish_large_file sends it again; if the second finish fails because the first one was applied (a 400 in the test), Compose returns that error and calls b2_cancel_large_file.
Expected behavior
A write that may have been applied is not repeated automatically, and the caller gets an error that says so, with enough context to check the result. Reads keep the retries from #167.
Acceptance criteria
- R1. A 500, 502 or 504 after an applied
createKey,finishLargeFile,copyFile,hideFile,deleteFileVersionorcreateBucketis not re-sent; the caller gets the unknown-outcome error with the identifiers above. - R2. A dial error before send is still retried, and reads still retry on 408, 500, 502, 503 and 504.
- R3.
Writer.CloseandComposeafter an ambiguous finish return the unknown-outcome error and do not callb2_cancel_large_file.
In scope
Proposed change
- Keep
withBackofffor reads and idempotent updates; add a write policy that retries only when the request provably did not reach B2 (pre-send dial/TLS errors, 429/503), never after a post-send transport error or a bare 500, 502 or 504. If repeatingb2_copy_partwith the same part number is safe (not confirmed here), it keeps the retries from #167. - Return a typed error, for example
*b2.UnknownOutcomeError, withMethodand any knownFileID,KeyName,BucketNameorSourceFileID, usable witherrors.As. - Add reconcile helpers: look up a key by name after an ambiguous
CreateKey; callb2_get_file_infoon the large file ID after an ambiguous finish; treatfile_not_presenton a repeated delete as success only when the first outcome was unknown. - In
Compose, do not cancel after an unknown-outcome finish. - Keep current retries for
b2_upload_file,b2_upload_partand reads.
This works best with a method-aware base.Action, so base can tell callers which failures are safe to retry for each method.
Out of scope
- Unrelated work outside the components and behavior described in In scope.
Verification
Run the repository regression checks with live credentials unset:
unset B2_APPLICATION_KEY_ID B2_APPLICATION_KEY B2_ACCOUNT_ID B2_ACCOUNT_KEY
go build ./...
go vet ./...
go test -race -count=1 ./b2/... ./base
- Script an applied non-idempotent write followed by 500/502/504 or an ambiguous transport failure. Assert the literal request count, unknown-outcome error/identifiers, and absence of cancel after ambiguous finish.
- Prove pre-send retry and read-retry controls still behave as specified; restore each independent regression after its red proof.
- Expected result: the checks above succeed and the observable outcomes in Acceptance criteria are demonstrated. If a check cannot run, report
Could not run because: ...; if it starts but fails, reportCould not complete because: .... A skipped check is not a pass.
Links
Backblaze/blazer#167 (merged, closes #165) added the 408, 502 and 504 retries described above. Backblaze/blazer#101 (open) covers re-authentication on 401 unauthorized, not 5xx or transport retries. Backblaze/blazer#145 (merged, closes #144) synchronized authorization state; a request is still sent again once after a 401. Backblaze/blazer#129 added Compose, and Backblaze/blazer#171 (merged, closes #164) added Compose options and kept its cancel after a failed finish.
- Lingua principale
- Go
- Stelle
- 66
- Fork
- 18
- Merge medio
- 1g 10h
- PR unite (30g)
- 132
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di Backblaze/blazer
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
I maintainer di solito rispondono entro 1 giorno
-
Bucket.Attrs never returns ReplicationConfig: replication configuration is write-onlyForse di nuovo libera Una pull request per questa issue è stata chiusa senza essere unita. Apertabug
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
test: skip the fuzz non-vacuity guard when replaying a single seedForse già presa @ffumero2003 l’ha presa 5 giorni fa. Apertatesting
Backblaze/blazer#330 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di Backblaze/blazer
Issue simili
-
needs-triage typo
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
llm-d-incubation/llm-d-scaling-manager#122 ·
I maintainer di solito rispondono entro 1 giorno
-
good first issue severity: low tech-debt
Difficoltà 2/5 Mezza giornata Idoneità per principianti 75/100
TMHSDigital/subenum#329 ·
I maintainer di solito rispondono entro 1 giorno
-
[Bug] Backend summarize min() / max() panics when a field has no numeric valuesForse già presa @weng5e l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 64/100
grafana/grafana-infinity-datasource#1832 ·
I maintainer di solito rispondono entro 1 giorno
-
tech-debt
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
GillMacedo/godebugdiff#13 ·