• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bedrock-kv / bedrock / 328efbb5eb46b499a90d7a22c413595246d91647
82%

Build:
DEFAULT BRANCH: develop
Ran 26 Aug 2026 04:29PM UTC
Jobs 1
Files 217
Run time 1min
Badge
Embed ▾
README BADGES
x

If you need to use a raster PNG badge, change the '.svg' to '.png' in the link

Markdown

Textile

RDoc

HTML

Rst

26 Aug 2026 04:09PM UTC coverage: 78.625% (-0.01%) from 78.637%
328efbb5eb46b499a90d7a22c413595246d91647

push

github

web-flow
Publish local objects atomically, never claim a key with a partial one (#215)

Closes bedrock-ghd.

Readers of the object store are written against S3's contract: an object
is complete or absent, and `put_if_not_exists` claims a key only by
publishing a whole object. The local backend did not honour that —
unlike the S3 backend, which uses one `put_object` with `if_none_match:
"*"`.

`put_if_not_exists` opened the key with `:exclusive` and wrote to it as
a **second step**, so between the two the key existed at zero length. A
crash there claimed the key permanently: every later attempt gets
`:eexist`, and the data-plane callers (`Demux.ShardServer`,
`ChunkWriter`, `Snapshot`) read `{:error, :already_exists}` as success —
so a short object would report success to everyone forever and could
never be rewritten. `olivine/logic.ex` already warned that
put-if-not-exists *"makes the poisoned bundle permanent"*; the backend
could manufacture one on its own.

### Approach
Write to a scratch file in the target's own directory, fsync, publish in
one step — `rename` for `put/4`, `link` for `put_if_not_exists/4`
(rename clobbers, so it cannot express create-only). Failures remove the
scratch and leave the key unclaimed, and write errors are returned
rather than raised — the old `:ok = :file.write(fd, data)` turned ENOSPC
into a MatchError with a poisoned key left behind.

### Two things the audit caught that I'd got wrong
- **The scratch name must be kernel-arbitrated.**
`:erlang.unique_integer/1` is unique only *within* a node, and the
default root is derived from the system tmp dir with nothing
node-specific in it. Two nodes sharing a root could pick the same
scratch name, interleave into one inode, and publish the splice — the
exact failure this change exists to prevent. The auditor reproduced it:
878 spurious `{:error, :not_found}` results from a *PUT* in one paired
run, which would `CaseClauseError` in `Discovery.write_bootstrap/4`.
Scratch files are now creat... (continued)

30 of 39 new or added lines in 2 files covered. (76.92%)

6827 of 8683 relevant lines covered (78.62%)

1028.99 hits per line

Uncovered Changes

Lines Coverage ∆ File
8
85.56
-5.35% lib/bedrock/object_storage/local_filesystem.ex
1
95.16
0.33% lib/bedrock/object_storage/chunk.ex
Jobs
ID Job ID Ran Files Coverage
1 328efbb5eb46b499a90d7a22c413595246d91647.1 26 Aug 2026 04:29PM UTC 217
78.62
GitHub Action Run
Source Files on build 328efbb5eb46b499a90d7a22c413595246d91647
  • Tree
  • List 217
  • Changed 2
  • Source Changed 0
  • Coverage Changed 2
Coverage ∆ File Lines Relevant Covered Missed Hits/Line
  • Back to Repo
  • 328efbb5 on github
  • Prev Build on develop (#91C9F360...)
  • Next Build on develop (#026C4C55...)
  • Delete
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc