diff --git a/internal/sys/file_table.go b/internal/descriptor/table.go similarity index 51% rename from internal/sys/file_table.go rename to internal/descriptor/table.go index 0c85cf19..0ed376f2 100644 --- a/internal/sys/file_table.go +++ b/internal/descriptor/table.go @@ -1,26 +1,26 @@ -package sys +package descriptor import "math/bits" -// FileTable is a data structure mapping 32 bit file descriptor to file objects. +// Table is a data structure mapping 32 bit descriptor to items. // // The data structure optimizes for memory density and lookup performance, // trading off compute at insertion time. This is a useful compromise for the -// use cases we employ it with: files are usually read or written a lot more -// often than they are opened, each operation requires a table lookup so we are -// better off spending extra compute to insert files in the table in order to +// use cases we employ it with: items are usually accessed a lot more often +// than they are inserted, each operation requires a table lookup so we are +// better off spending extra compute to insert items in the table in order to // get cheaper lookups. Memory efficiency is also crucial to support scaling -// with programs that open thousands of files: having a high or non-linear +// with programs that maintain thousands of items: having a high or non-linear // memory-to-item ratio could otherwise be used as an attack vector by malicous // applications attempting to damage performance of the host. -type FileTable struct { +type Table[Key ~uint32, Item any] struct { masks []uint64 - files []*FileEntry + items []Item } -// Len returns the number of files stored in the table. -func (t *FileTable) Len() (n int) { - // We could make this a O(1) operation if we cached the number of files in +// Len returns the number of items stored in the table. +func (t *Table[Key, Item]) Len() (n int) { + // We could make this a O(1) operation if we cached the number of items in // the table. More state usually means more problems, so until we have a // clear need for this, the simple implementation may be a better trade off. for _, mask := range t.masks { @@ -29,9 +29,9 @@ func (t *FileTable) Len() (n int) { return n } -// Grow ensures that t has enough room for n files, potentially reallocating the -// internal buffers if their capacity was too small to hold this many files. -func (t *FileTable) Grow(n int) { +// Grow ensures that t has enough room for n items, potentially reallocating the +// internal buffers if their capacity was too small to hold this many items. +func (t *Table[Key, Item]) Grow(n int) { // Round up to a multiple of 64 since this is the smallest increment due to // using 64 bits masks. n = (n*64 + 63) / 64 @@ -40,20 +40,20 @@ func (t *FileTable) Grow(n int) { masks := make([]uint64, n) copy(masks, t.masks) - files := make([]*FileEntry, n*64) - copy(files, t.files) + items := make([]Item, n*64) + copy(items, t.items) t.masks = masks - t.files = files + t.items = items } } -// Insert inserts the given file to the table, returning the fd that it is +// Insert inserts the given item to the table, returning the key that it is // mapped to. // -// The method does not perform deduplication, it is possible for the same file -// to be inserted multiple times, each insertion will return a different fd. -func (t *FileTable) Insert(file *FileEntry) (fd uint32) { +// The method does not perform deduplication, it is possible for the same item +// to be inserted multiple times, each insertion will return a different key. +func (t *Table[Key, Item]) Insert(item Item) (key Key) { offset := 0 insert: // Note: this loop could be made a lot more efficient using vectorized @@ -63,10 +63,10 @@ insert: if ^mask != 0 { // not full? shift := bits.TrailingZeros64(^mask) index += offset - fd = uint32(index)*64 + uint32(shift) - t.files[fd] = file + key = Key(index)*64 + Key(shift) + t.items[key] = item t.masks[index] = mask | uint64(1<= 0 && i < len(t.files) { - index := uint(fd) / 64 - shift := uint(fd) % 64 +// Lookup returns the item associated with the given key (may be nil). +func (t *Table[Key, Item]) Lookup(key Key) (item Item, found bool) { + if i := int(key); i >= 0 && i < len(t.items) { + index := uint(key) / 64 + shift := uint(key) % 64 if (t.masks[index] & (1 << shift)) != 0 { - file, found = t.files[i], true + item, found = t.items[i], true } } return } -// InsertAt inserts the given `file` at the file descriptor `fd`. -func (t *FileTable) InsertAt(file *FileEntry, fd uint32) { - if diff := int(fd) - t.Len(); diff > 0 { +// InsertAt inserts the given `item` at the item descriptor `key`. +func (t *Table[Key, Item]) InsertAt(item Item, key Key) { + if diff := int(key) - t.Len(); diff > 0 { t.Grow(diff) } - index := uint(fd) / 64 - shift := uint(fd) % 64 + index := uint(key) / 64 + shift := uint(key) % 64 t.masks[index] |= 1 << shift - t.files[fd] = file + t.items[key] = item } -// Delete deletes the file stored at the given fd from the table. -func (t *FileTable) Delete(fd uint32) { - if index, shift := fd/64, fd%64; int(index) < len(t.masks) { +// Delete deletes the item stored at the given key from the table. +func (t *Table[Key, Item]) Delete(key Key) { + if index, shift := key/64, key%64; int(index) < len(t.masks) { mask := t.masks[index] if (mask & (1 << shift)) != 0 { - t.files[fd] = nil + var zero Item + t.items[key] = zero t.masks[index] = mask & ^uint64(1<