// Block-lazy atom resolution over a SectionFetch: the first consumer-shaped // slice of the working-set machinery lazy.go defers. An AtomReader keeps a // small resident router (one byte offset per block of atoms) and range-fetches // one block per lookup, so a skeleton parse (SkeletonParseOptions) plus an // AtomReader serves traversal with a hot atom set without ever holding the // atom table resident. RCPG v1 stores atoms as contiguous length-prefixed // strings with no in-file offset index, so the router is discovered by // scanning length prefixes forward, incrementally, at most once per file.
package rcpg import ( ) const ( // atomBlockLen is the number of atoms per router block. LCSH-scale // tables (~500k atoms, ~90B mean) route with ~4KB resident and decode // ~90KB per block fetch. atomBlockLen = 1024 // atomScanChunk is the fetch granularity of the forward prefix scan // that discovers block boundaries. atomScanChunk = 512 << 10 // atomCacheBlocks bounds the decoded blocks kept resident (FIFO). atomCacheBlocks = 32 ) // AtomReader resolves atom ids on demand through a SectionFetch, without // materializing the atom table. Lookups route through a resident block // index (built incrementally by scanning the section's length prefixes up // to the highest block touched so far) and fetch + decode one block of // atoms, kept in a small FIFO cache. Structural bounds are validated during // the scan; UTF-8 validity is checked per decoded block, like the eager // decoder but only for atoms actually read. // // An AtomReader is safe for concurrent use; lookups that miss the cache // serialize on the underlying fetch. type AtomReader struct { f SectionFetch off uint64 // file offset of the atoms section body size uint64 // atoms section length n uint32 // atom count (the section's leading u32) blockLen uint32 scanChunk uint64 cacheCap int mu sync.Mutex starts []uint64 // starts[b]: body-relative offset of atom b*blockLen frontier uint64 // body-relative offset of the first unscanned atom scanned uint32 // atoms whose extents are known cache map[uint32][]string fifo []uint32 } // NewAtomReader opens block-lazy atom resolution over f, locating the atoms // section through dir and fetching only its 4-byte count header. Errors wrap // ErrCorrupt when the directory has no atoms section or the count cannot fit // the section. func ( SectionFetch, *Directory) (*AtomReader, error) { return newAtomReader(, , atomBlockLen, atomScanChunk, atomCacheBlocks) } // newAtomReader is NewAtomReader with the tuning knobs exposed for tests // (small blocks and chunks exercise routing, spanning, and eviction on // small inputs). func newAtomReader( SectionFetch, *Directory, uint32, uint64, int) (*AtomReader, error) { // Last matching entry wins, mirroring the eager path (each decode // overwrites, so a duplicate section's final occurrence is the one a // full parse materializes). var , DirEntry , := false, false for , := range .Entries() { switch .ID { case sectionAtoms: , = , true case sectionAtomIndex: , = , true } } if ! { return nil, corruptf("no atoms section in directory") } if .Length < 4 { return nil, corruptf("atoms section too short for its count (%d bytes)", .Length) } , := .Fetch(.Offset, 4) if != nil { return nil, } := binary.LittleEndian.Uint32() if uint64() > .Length-4 { return nil, corruptf("declared atom count %d exceeds section body (%d bytes)", , .Length-4) } // Floors keep the machinery sound at any tuning: a chunk must hold at // least a length prefix (the stall detector relies on it) and the FIFO // needs one slot to bound residency. := &AtomReader{ f: , off: .Offset, size: .Length, n: , blockLen: max(, 1), scanChunk: max(, 16), cacheCap: max(, 1), starts: []uint64{4}, frontier: 4, cache: map[uint32][]string{}, } if { if := .adoptIndex(, ); != nil { return nil, } } return , nil } // adoptIndex consumes a present section-7 atom block index: the router is // the file's own offsets (at the file's block length), so no forward // prefix scan ever runs -- the exact O(1) routing the index exists for. A // present index must be structurally sound; any violation is corrupt, never // a silent fall-back to the scan path (the fall-back would quietly // re-transfer the whole atoms section). Per-block content is still // validated when a block decodes, exactly like the scan path. func ( *AtomReader) ( DirEntry, uint32) error { , := .f.Fetch(.Offset, .Length) if != nil { return } , := decodeAtomIndex() if != nil { return } if .BlockLen == 0 { return corruptf("atom index: block_len is zero") } := (int() + int(.BlockLen) - 1) / int(.BlockLen) if len(.Offsets) != { return corruptf("atom index: offset count does not match the atom count") } if > 0 { if .Offsets[0] != 4 { return corruptf("atom index: entry 0 is not 4") } if .Offsets[len(.Offsets)-1] >= .size { return corruptf("atom index: offset past the atoms section (%d >= %d)", .Offsets[len(.Offsets)-1], .size) } } .blockLen = .BlockLen .starts = .Offsets .scanned = .frontier = .size return nil } // Count is the number of atoms in the table. func ( *AtomReader) () uint32 { return .n } // Atom resolves one atom id, fetching and decoding its block on a cache // miss. Ids at or past Count error (a conforming file never references // them), wrapping ErrCorrupt. func ( *AtomReader) ( uint32) (string, error) { if >= .n { return "", corruptf("atom id %d out of range (%d atoms)", , .n) } := / .blockLen .mu.Lock() defer .mu.Unlock() , := .cache[] if ! { var error if , = .loadBlock(); != nil { return "", } } return [%.blockLen], nil } // loadBlock fetches and decodes block b, caching it under the FIFO bound. // Caller holds r.mu. func ( *AtomReader) ( uint32) ([]string, error) { := uint32(min(uint64(+1)*uint64(.blockLen), uint64(.n))) if := .ensureScanned(); != nil { return nil, } := .starts[] := .frontier if int()+1 < len(.starts) { = .starts[+1] } , := .f.Fetch(.off+, -) if != nil { return nil, } := newCursor() := make([]string, -*.blockLen) for := range { if [], = .str(); != nil { return nil, } } if .remaining() != 0 { return nil, corruptf("atom block %d decoded short: %d trailing bytes", , .remaining()) } if len(.cache) >= .cacheCap { delete(.cache, .fifo[0]) .fifo = .fifo[1:] } .cache[] = .fifo = append(.fifo, ) return , nil } // ensureScanned advances the forward prefix scan until the extents of the // first target atoms are known, recording a router entry at every block // boundary it crosses. A string spanning the current chunk raises the next // fetch to cover it whole, so every round either consumes at least one atom // or errors -- the scan cannot stall. Caller holds r.mu. func ( *AtomReader) ( uint32) error { := .scanChunk for .scanned < { := .size - .frontier if == 0 { return corruptf("atoms section truncated: %d of %d atoms present", .scanned, .n) } , := .f.Fetch(.off+.frontier, min(, )) if != nil { return } = .scanChunk := uint64(0) for .scanned < && +4 <= uint64(len()) { := uint64(binary.LittleEndian.Uint32([:])) if .frontier++4+ > .size { return corruptf("atom %d (len %d) extends past its section (%d bytes)", .scanned, , .size) } if +4+ > uint64(len()) { = 4 + break } += 4 + .scanned++ if .scanned%.blockLen == 0 { .starts = append(.starts, .frontier+) } } if == 0 && == .scanChunk { return corruptf("atoms section truncated: %d of %d atoms present", .scanned, .n) } .frontier += } return nil }