@@ -102,14 +102,9 @@ Creates or opens:
102102let data = b " Hello, world!" ;
103103let hash = blake3 :: hash (data );
104104
105- // Put will compress and deduplicate
106- let was_new = store . put (& hash , data )? ;
107-
108- if was_new {
109- println! (" New blob stored" );
110- } else {
111- println! (" Blob already exists (deduplicated)" );
112- }
105+ // put_if_absent will compress and deduplicate
106+ let entry = store . put_if_absent (* hash . as_bytes (), data )? ;
107+ println! (" Stored: offset={}, raw_len={}" , entry . offset, entry . raw_len);
113108```
114109
115110** Compression:**
@@ -121,13 +116,13 @@ if was_new {
121116``` rust
122117let hash : [u8 ; 32 ] = /* ... */ ;
123118
124- match store . get (& hash )? {
125- Some (data ) => println! (" Found: {} bytes" , data . len ()),
126- None => println! (" Blob not found" ),
127- }
119+ let data = store . get (& hash )? ; // Returns Result<Vec<u8>>
120+ println! (" Found: {} bytes" , data . len ());
128121```
129122
130- Returns decompressed bytes.
123+ Returns decompressed bytes or a ` NotFound ` error. Reads use ` pread ` (positional
124+ read) via a separate read-only file handle, so ` get() ` takes ` &self ` and
125+ multiple threads can read concurrently without contention.
131126
132127### Checking Existence
133128
@@ -147,14 +142,15 @@ The blob store automatically deduplicates identical content:
147142let data1 = b " foo" ;
148143let hash1 = blake3 :: hash (data1 );
149144
150- store . put ( & hash1 , data1 )? ; // Writes to pack file
151- store . put ( & hash1 , data1 )? ; // No-op (already exists)
145+ store . put_if_absent ( * hash1 . as_bytes () , data1 )? ; // Writes to pack file
146+ store . put_if_absent ( * hash1 . as_bytes () , data1 )? ; // No-op (already exists)
152147```
153148
154149** Thread safety:**
155- - Uses sharded locks (16 shards by hash prefix)
156- - Double-checked locking: check index, acquire lock, check again, write if missing
157- - Safe under concurrent writes
150+
151+ - Reads (` get ` , ` contains ` , ` raw_len ` ) take ` &self ` and are safe under concurrent access
152+ - Writes (` put_if_absent ` ) take ` &mut self ` and are serialized by the caller's write lock
153+ - Deduplication check is O(1) in the in-memory hash index
158154
159155## Compression
160156
@@ -194,15 +190,19 @@ let hash = blake3::hash(data); // Hash before compression
194190let compressed = zstd :: encode (data , 3 )? ;
195191
196192// Store with original hash
197- store . put ( & hash , data )? ; // Handles compression internally
193+ store . put_if_absent ( * hash . as_bytes () , data )? ; // Handles compression internally
198194```
199195
200196** Why BLAKE3?**
201197- Fast: 3-4x faster than SHA-256
202198- Secure: 256-bit output, collision-resistant
203199- Deterministic: Same input always produces same hash
204200
205- ## Crash Recovery
201+ ## Durability and Crash Recovery
202+
203+ All writes (` put_if_absent ` ) are followed by ` sync_all() ` (fsync) on both the
204+ pack file and the index file. This ensures data is on stable storage before the
205+ caller is notified of success.
206206
207207On startup, the store:
208208
@@ -212,6 +212,7 @@ On startup, the store:
2122124 . Rebuilds if necessary
213213
214214** CRC verification:**
215+
215216- Each blob record has a CRC-32
216217- On read, CRC is verified
217218- Corrupted blobs return error (caller must handle)
@@ -247,7 +248,7 @@ use blob_store::{BlobStore, BlobCodec};
247248use blake3;
248249
249250fn main () -> Result <(), Box <dyn std :: error :: Error >> {
250- // Open store
251+ // Open store (mut needed for writes, reads only need &self)
251252 let mut store = BlobStore :: open (Path :: new (" ./data/blobs" ))? ;
252253
253254 // Prepare data
@@ -260,13 +261,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
260261 // Compute hash (before compression)
261262 let hash = blake3 :: hash (& msgpack_bytes );
262263
263- // Store (will compress internally)
264- let was_new = store . put ( & hash , & msgpack_bytes )? ;
265- println! (" Stored: was_new={} " , was_new );
264+ // Store (will compress internally, requires &mut self )
265+ let _entry = store . put_if_absent ( * hash . as_bytes () , & msgpack_bytes )? ;
266+ println! (" Stored blob " );
266267
267- // Retrieve
268- let retrieved = store . get (& hash )?
269- . expect (" Blob should exist" );
268+ // Retrieve (uses pread, only needs &self)
269+ let retrieved = store . get (hash . as_bytes ())? ;
270270
271271 assert_eq! (retrieved , msgpack_bytes );
272272 println! (" Retrieved: {} bytes" , retrieved . len ());
0 commit comments