fix: resolve BlobStorage compression race condition causing data corruption (v5.7.5)
CRITICAL BUG FIX - VFS file corruption since v5.0.0 Root cause: BlobStorage.initCompression() is async but called without await in constructor, creating race window where write() happens before zstd loads. Result: Data written uncompressed, metadata says "compressed" → read attempts decompression → garbage → hash mismatch → "Blob integrity check failed" error. Impact: VFS files (Workshop) written in first 100-500ms after BlobStorage creation are permanently corrupted and unreadable. Data loss for users. Fix (3 changes in BlobStorage.ts): 1. Added compressionReady flag to track init state (line 108) 2. Added ensureCompressionReady() to await init before write (lines 162-170) 3. Store ACTUAL compression state in metadata, not intended (line 227) This ensures: - Compression fully initialized before first write - Metadata accurately reflects what actually happened - No corruption even if zstd fails to load Tests: All 30 BlobStorage unit tests pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
0e0661d05d
commit
9fc89a7258
1 changed files with 19 additions and 1 deletions
|
|
@ -105,6 +105,7 @@ export class BlobStorage {
|
|||
// Compression (lazily loaded)
|
||||
private zstdCompress?: (data: Buffer) => Promise<Buffer>
|
||||
private zstdDecompress?: (data: Buffer) => Promise<Buffer>
|
||||
private compressionReady = false
|
||||
|
||||
// Configuration
|
||||
private readonly CACHE_MAX_SIZE = 100 * 1024 * 1024 // 100MB default
|
||||
|
|
@ -158,6 +159,16 @@ export class BlobStorage {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* v5.7.5: Ensure compression is ready before write operations
|
||||
* Fixes race condition where write happens before async compression init completes
|
||||
*/
|
||||
private async ensureCompressionReady(): Promise<void> {
|
||||
if (this.compressionReady) return
|
||||
await this.initCompression()
|
||||
this.compressionReady = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute SHA-256 hash of data
|
||||
*
|
||||
|
|
@ -194,6 +205,10 @@ export class BlobStorage {
|
|||
return hash
|
||||
}
|
||||
|
||||
// v5.7.5: Ensure compression is initialized before writing
|
||||
// Fixes race condition where write happens before async init completes
|
||||
await this.ensureCompressionReady()
|
||||
|
||||
// Determine compression strategy
|
||||
const compression = this.selectCompression(data, options)
|
||||
|
||||
|
|
@ -207,11 +222,14 @@ export class BlobStorage {
|
|||
}
|
||||
|
||||
// Create metadata
|
||||
// v5.7.5: Store ACTUAL compression state, not intended
|
||||
// Prevents corruption if compression failed to initialize
|
||||
const actualCompression = finalData === data ? 'none' : compression
|
||||
const metadata: BlobMetadata = {
|
||||
hash,
|
||||
size: data.length,
|
||||
compressedSize,
|
||||
compression,
|
||||
compression: actualCompression,
|
||||
type: options.type || 'blob', // CRITICAL FIX: Use 'blob' default to match storage prefix
|
||||
createdAt: Date.now(),
|
||||
refCount: 1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue