// 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 rcpgimport ()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.typeAtomReaderstruct { 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) {returnnewAtomReader(, , 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, falsefor , := range .Entries() {switch .ID {casesectionAtoms: , = , truecasesectionAtomIndex: , = , true } }if ! {returnnil, corruptf("no atoms section in directory") }if .Length < 4 {returnnil, corruptf("atoms section too short for its count (%d bytes)", .Length) } , := .Fetch(.Offset, 4)if != nil {returnnil, } := binary.LittleEndian.Uint32()ifuint64() > .Length-4 {returnnil, 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 {returnnil, } }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 {returncorruptf("atom index: block_len is zero") } := (int() + int(.BlockLen) - 1) / int(.BlockLen)iflen(.Offsets) != {returncorruptf("atom index: offset count does not match the atom count") }if > 0 {if .Offsets[0] != 4 {returncorruptf("atom index: entry 0 is not 4") }if .Offsets[len(.Offsets)-1] >= .size {returncorruptf("atom index: offset past the atoms section (%d >= %d)", .Offsets[len(.Offsets)-1], .size) } } .blockLen = .BlockLen .starts = .Offsets .scanned = .frontier = .sizereturnnil}// 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 ! {varerrorif , = .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 {returnnil, } := .starts[] := .frontierifint()+1 < len(.starts) { = .starts[+1] } , := .f.Fetch(.off+, -)if != nil {returnnil, } := newCursor() := make([]string, -*.blockLen)for := range {if [], = .str(); != nil {returnnil, } }if .remaining() != 0 {returnnil, corruptf("atom block %d decoded short: %d trailing bytes", , .remaining()) }iflen(.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 { := .scanChunkfor .scanned < { := .size - .frontierif == 0 {returncorruptf("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 {returncorruptf("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 {returncorruptf("atoms section truncated: %d of %d atoms present", .scanned, .n) } .frontier += }returnnil}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.