01
Identify the request condition
Capture the status and operation without exposing signed URLs or private object data. Inspect the request options that produced it. A generation precondition differs from an authentication rejection, timeout, or permission failure.
The local fixture first accepted a create-only upload. Sending a second upload to that name with if_generation_match=0 produced PreconditionFailed, and the fixture retained the first payload. The test checked SDK request construction and error mapping against the documented service contract.
02
Use a create-only write when replacement is unwanted
For an operation that must not replace an existing live object, supply the create-only condition. A separate existence check followed by an unconditional upload introduces a gap in which another writer can create the destination.
The helper below accepts a blob already scoped to the intended destination. In a real application, credentials, bucket access, and naming are configured separately. The existence of a helper does not make an arbitrary destination safe to overwrite.
def create_report(blob, payload):
blob.upload_from_string(
payload,
content_type="text/plain",
if_generation_match=0,
)03
Handle a collision according to the business operation
A 412 after a retry can mean the object was created earlier, but that conclusion requires evidence. Inspect the existing object through an authorized path and compare the expected identity or content. Do not treat every collision as a successful duplicate.
For a genuinely different report, choose a distinct name under the application’s naming policy. For an idempotent operation, verify that the existing result belongs to that operation. Avoid deleting the existing object merely to make the next upload succeed.
04
Replace only the generation you reviewed
If replacement is intentional, compute it from the object version you actually inspected and retain that generation as the write condition. Reading a newer generation number independently of the content can defeat the decision you meant to protect.
Our fixture accepted a later deliberate replacement only when the supplied condition matched its current generation. If another writer changes the real object after your review, reevaluate the operation rather than removing the condition.
def replace_reviewed_report(blob, replacement, reviewed_generation):
blob.upload_from_string(
replacement,
content_type="text/plain",
if_generation_match=int(reviewed_generation),
)05
Verify data and classify retries
Check the intended object and result through an authorized read. Retain its generation with the verification record. A response from a different object name or a changed retry path does not explain the original conflict.
Use the client library’s documented retry behavior for the operation and keep its conditions intact. Blindly retrying a permanently false precondition does not resolve a competing write. The local fixture tests do not establish managed-service consistency or all production retry outcomes.
Know the limits
Tested with google-cloud-storage 3.17.1 against a minimal local HTTP contract fixture. The actual SDK sent generation conditions and mapped a fixture 412 to PreconditionFailed; the fixture preserved earlier bytes. This is not a Cloud Storage emulator, live concurrency test, or IAM verification.
Sources & further reading
A technical publication by CoreTrailKit.
Browse all articles