sonar

Scan files at memory bandwidth speed.
Log | Files | Refs

tree.odin (8645B)


      1 package scan
      2 
      3 import "core:mem"
      4 import "core:mem/virtual"
      5 import "core:strings"
      6 import "core:sync"
      7 import "core:unicode/utf16"
      8 import "core:unicode/utf8"
      9 
     10 // A file or directory, in the form every reader reduces to.
     11 Node :: struct {
     12 	parent: u32, // index of the holding directory; a root points at itself
     13 	name:   string, // UTF-8, owned by the tree
     14 	size:   u64, // logical length
     15 	disk:   u64, // bytes actually allocated
     16 	flags:  Node_Flags,
     17 }
     18 
     19 Node_Flag  :: enum u8 {
     20 	Used, // written by a reader; an untouched slot has this clear
     21 	// The parent link is final. A reader that learns of a child before its holding
     22 	// directory leaves this clear until the parent turns up, because until then the
     23 	// node cannot be told apart from one that has no parent at all.
     24 	Settled,
     25 	Directory,
     26 	Reparse, // junction, symlink or cloud placeholder: its target is counted elsewhere
     27 	Extra_Name, // another name for a node already counted, so rolling up must skip it
     28 }
     29 Node_Flags :: distinct bit_set[Node_Flag;u8]
     30 
     31 // Nodes live in fixed blocks, so growing never moves one out from under a worker
     32 // that is writing it. The outer array is sized once for the same reason.
     33 @(private)
     34 BLOCK_SHIFT :: 16
     35 @(private)
     36 BLOCK_NODES :: 1 << BLOCK_SHIFT
     37 @(private)
     38 MAX_BLOCKS :: 8192
     39 
     40 @(private)
     41 Block :: [BLOCK_NODES]Node
     42 
     43 /*
     44 What every reader writes into, and the only thing above this line reads.
     45 
     46 A reader that knows its size up front calls `reserve` and indexes by its own
     47 identifier. One that discovers as it goes calls `claim` for a run of slots. Either
     48 way a slot belongs to one worker, so filling it needs no lock.
     49 */
     50 Tree :: struct {
     51 	blocks:     [MAX_BLOCKS]^Block,
     52 	count:      u32, // slots handed out
     53 	committed:  u32, // slots backed by an allocated block
     54 	mutex:      sync.Mutex, // guards block allocation only
     55 	arenas:     []virtual.Arena, // one per writer; names live here for the tree's life
     56 	root:       u32,
     57 	// Polled by a UI rather than pushed to it, so sampling costs a reader nothing.
     58 	nodes_done: u64,
     59 	bytes_done: u64,
     60 	cancel:     b32,
     61 	restated:   b32,
     62 	allocator:  mem.Allocator,
     63 }
     64 
     65 // One writer per worker. Holding two in the same thread, or one in two, is the
     66 // mistake this type exists to make visible.
     67 Writer :: struct {
     68 	tree:  ^Tree,
     69 	arena: ^virtual.Arena,
     70 }
     71 
     72 tree_init :: proc(t: ^Tree, writers := 1, allocator := context.allocator) -> Error {
     73 	t.allocator = allocator
     74 	arenas, err := make([]virtual.Arena, max(writers, 1), allocator)
     75 	if err != nil {
     76 		return .Out_Of_Memory
     77 	}
     78 	t.arenas = arenas
     79 	for &a in t.arenas {
     80 		if virtual.arena_init_growing(&a) != nil {
     81 			tree_destroy(t)
     82 			return .Out_Of_Memory
     83 		}
     84 	}
     85 	return .None
     86 }
     87 
     88 tree_destroy :: proc(t: ^Tree) {
     89 	for i in 0 ..< t.committed >> BLOCK_SHIFT {
     90 		free(t.blocks[i], t.allocator)
     91 	}
     92 	for &a in t.arenas {
     93 		virtual.arena_destroy(&a)
     94 	}
     95 	delete(t.arenas, t.allocator)
     96 	t^ = {}
     97 }
     98 
     99 writer :: proc(t: ^Tree, index := 0) -> Writer {
    100 	return {tree = t, arena = &t.arenas[index]}
    101 }
    102 
    103 // Make `n` slots addressable at once, for a reader that already knows how many it
    104 // has and wants to index by its own numbering.
    105 reserve :: proc(t: ^Tree, n: u32) -> Error {
    106 	if !grow(t, n) {
    107 		return .Out_Of_Memory
    108 	}
    109 	t.count = max(t.count, n)
    110 	return .None
    111 }
    112 
    113 // Take the next `n` slots. They belong to this writer until the scan ends.
    114 claim :: proc(w: ^Writer, n: u32) -> (first: u32, err: Error) {
    115 	first = sync.atomic_add(&w.tree.count, n)
    116 	if !grow(w.tree, first + n) {
    117 		return 0, .Out_Of_Memory
    118 	}
    119 	return first, .None
    120 }
    121 
    122 node :: proc(t: ^Tree, i: u32) -> ^Node {
    123 	return &t.blocks[i >> BLOCK_SHIFT][i & (BLOCK_NODES - 1)]
    124 }
    125 
    126 /*
    127 Finish a node, once everything else about it is written.
    128 
    129 Flags go last and atomically, so a reader that sees them is looking at a node whose
    130 name, sizes and parent are already in place. Without that order a watcher could charge
    131 a node for a size that had not been stored yet.
    132 */
    133 node_publish :: proc(n: ^Node, flags: Node_Flags) {
    134 	sync.atomic_store((^u8)(&n.flags), transmute(u8)flags)
    135 }
    136 
    137 // A node's flags, read as a watcher must. Pairs with `node_publish`: an untouched slot
    138 // reads back empty, and a written one reads back whole.
    139 node_flags :: proc(n: ^Node) -> Node_Flags {
    140 	return transmute(Node_Flags)sync.atomic_load((^u8)(&n.flags))
    141 }
    142 
    143 // Copy a name into this writer's arena, where it stays valid for the tree's life.
    144 intern :: proc(w: ^Writer, name: string) -> string {
    145 	buf, err := make([]byte, len(name), virtual.arena_allocator(w.arena))
    146 	if err != nil {
    147 		return ""
    148 	}
    149 	copy(buf, name)
    150 	return string(buf)
    151 }
    152 
    153 // The same, for a name that arrives as UTF-16 as it does on Windows.
    154 intern_utf16 :: proc(w: ^Writer, name: []u16) -> string {
    155 	runes: [256]rune
    156 	n := utf16.decode(runes[:], name)
    157 	total := 0
    158 	for r in runes[:n] {
    159 		total += utf8.rune_size(r)
    160 	}
    161 	buf, err := make([]byte, total, virtual.arena_allocator(w.arena))
    162 	if err != nil {
    163 		return ""
    164 	}
    165 	off := 0
    166 	for r in runes[:n] {
    167 		enc, size := utf8.encode_rune(r)
    168 		copy(buf[off:], enc[:size])
    169 		off += size
    170 	}
    171 	return string(buf)
    172 }
    173 
    174 // Workers worth starting for `items` units of work, never more than `most`. A reader
    175 // that discovers work in generations calls this per generation, since an early one
    176 // may hold a single directory.
    177 width_for :: proc(items, most: int) -> int {
    178 	return max(min(items, most), 1)
    179 }
    180 
    181 /*
    182 Slots that can be addressed right now.
    183 
    184 `claim` takes its range before allocating the block that backs it, so the count alone
    185 runs ahead of what exists. Whichever is smaller is the part a watcher may look at; a
    186 slot claimed but not yet written reads back with no flags set and is simply skipped.
    187 */
    188 slots :: proc(t: ^Tree) -> u32 {
    189 	return min(sync.atomic_load(&t.count), sync.atomic_load(&t.committed))
    190 }
    191 
    192 // A reader checks this between units of work so a UI can call the scan off.
    193 cancelled :: proc(t: ^Tree) -> bool {
    194 	return bool(sync.atomic_load(&t.cancel))
    195 }
    196 
    197 cancel :: proc(t: ^Tree) {
    198 	sync.atomic_store(&t.cancel, true)
    199 }
    200 
    201 /*
    202 Say that what was read out of this tree before may be wrong.
    203 
    204 A reader that can only learn a node's final size late has to write some of them twice,
    205 and anything derived from the first writing has to be thrown away rather than added
    206 to. Set once the restatement is complete, so whoever acts on it sees the finished
    207 tree rather than one mid-rewrite.
    208 
    209 This is the only thing above the readers that has to be understood about any of them.
    210 */
    211 restate :: proc(t: ^Tree) {
    212 	sync.atomic_store(&t.restated, true)
    213 }
    214 
    215 // Whether the tree has been restated since this was last asked, clearing it.
    216 taken_restated :: proc(t: ^Tree) -> bool {
    217 	return bool(sync.atomic_exchange(&t.restated, false))
    218 }
    219 
    220 progress :: proc(t: ^Tree, nodes, bytes: u64) {
    221 	sync.atomic_add(&t.nodes_done, nodes)
    222 	sync.atomic_add(&t.bytes_done, bytes)
    223 }
    224 
    225 @(private)
    226 grow :: proc(t: ^Tree, needed: u32) -> bool {
    227 	if needed <= sync.atomic_load(&t.committed) {
    228 		return true
    229 	}
    230 	sync.guard(&t.mutex)
    231 	for t.committed < needed {
    232 		i := t.committed >> BLOCK_SHIFT
    233 		if int(i) >= MAX_BLOCKS {
    234 			return false
    235 		}
    236 		b, err := new(Block, t.allocator)
    237 		if err != nil {
    238 			return false
    239 		}
    240 		t.blocks[i] = b
    241 		sync.atomic_store(&t.committed, t.committed + BLOCK_NODES)
    242 	}
    243 	return true
    244 }
    245 
    246 // Paths are written for the machine showing them rather than for the filesystem they
    247 // came from, so an NTFS volume read from unix still prints something the shell there
    248 // would take back.
    249 @(private)
    250 SEPARATOR :: `\` when ODIN_OS == .Windows else "/"
    251 
    252 /*
    253 Rebuild a node's path by following parents to a root.
    254 
    255 A root points at itself, which is also how an orphan is marked: a node whose real
    256 parent could not be resolved is rooted at itself and its path starts at its own name.
    257 Depth is capped because a corrupt or racing source can produce a cycle.
    258 */
    259 path :: proc(t: ^Tree, i: u32, allocator := context.allocator) -> string {
    260 	chain: [64]u32
    261 	depth := 0
    262 	cur := i
    263 	for depth < len(chain) {
    264 		chain[depth] = cur
    265 		depth += 1
    266 		n := node(t, cur)
    267 		if n.parent == cur || n.parent >= slots(t) {
    268 			break
    269 		}
    270 		cur = n.parent
    271 	}
    272 
    273 	sb := strings.builder_make(allocator)
    274 	for j := depth - 1; j >= 0; j -= 1 {
    275 		// Reading the flags first is what makes the name safe to read at all.
    276 		if .Used not_in node_flags(node(t, chain[j])) {
    277 			continue
    278 		}
    279 		name := node(t, chain[j]).name
    280 		if name == "" {
    281 			continue
    282 		}
    283 		// The root carries its own separator, so joining blindly would double it.
    284 		if strings.builder_len(sb) > 0 && !strings.has_suffix(strings.to_string(sb), SEPARATOR) {
    285 			strings.write_string(&sb, SEPARATOR)
    286 		}
    287 		strings.write_string(&sb, name)
    288 	}
    289 	if strings.builder_len(sb) == 0 {
    290 		strings.write_string(&sb, SEPARATOR)
    291 	}
    292 	return strings.to_string(sb)
    293 }