github.com/ethersphere/bee/v2@v2.2.0/pkg/sharky/slots.go (about) 1 // Copyright 2021 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package sharky 6 7 import ( 8 "io" 9 "sync" 10 ) 11 12 type slots struct { 13 data []byte // byteslice serving as bitvector: i-t bit set <> 14 size uint32 // number of slots 15 head uint32 // the first free slot 16 file sharkyFile // file to persist free slots across sessions 17 in chan uint32 // incoming channel for free slots, 18 out chan uint32 // outgoing channel for free slots 19 wg *sync.WaitGroup // count started write operations 20 limboWG sync.WaitGroup // wait for the limbo writes to in chan after the quit is closed 21 } 22 23 func newSlots(file sharkyFile, wg *sync.WaitGroup) *slots { 24 return &slots{ 25 file: file, 26 in: make(chan uint32), 27 out: make(chan uint32), 28 wg: wg, 29 } 30 } 31 32 // load inits the slots from file, called after init 33 func (sl *slots) load() (err error) { 34 sl.data, err = io.ReadAll(sl.file) 35 if err != nil { 36 return err 37 } 38 sl.size = uint32(len(sl.data) * 8) 39 sl.head = sl.next(0) 40 return err 41 } 42 43 // save persists the free slot bitvector on disk (without closing) 44 func (sl *slots) save() error { 45 if err := sl.file.Truncate(0); err != nil { 46 return err 47 } 48 if _, err := sl.file.Seek(0, 0); err != nil { 49 return err 50 } 51 if _, err := sl.file.Write(sl.data); err != nil { 52 return err 53 } 54 return sl.file.Sync() 55 } 56 57 // extend adapts the slots to an extended size shard 58 // extensions are bytewise: can only be multiples of 8 bits 59 func (sl *slots) extend(n int) { 60 sl.size += uint32(n) * 8 61 for i := 0; i < n; i++ { 62 sl.data = append(sl.data, 0xff) 63 } 64 } 65 66 // next returns the lowest free slot after start. 67 func (sl *slots) next(start uint32) uint32 { 68 for i := start; i < sl.size; i++ { 69 if sl.data[i/8]&(1<<(i%8)) > 0 { 70 return i 71 } 72 } 73 return sl.size 74 } 75 76 // push inserts a free slot. 77 func (sl *slots) push(i uint32) { 78 if sl.head > i { 79 sl.head = i 80 } 81 sl.data[i/8] |= 1 << (i % 8) 82 } 83 84 // pop returns the lowest available free slot. 85 func (sl *slots) pop() uint32 { 86 head := sl.head 87 if head == sl.size { 88 sl.extend(1) 89 } 90 sl.data[head/8] &= ^(1 << (head % 8)) 91 sl.head = sl.next(head + 1) 92 return head 93 } 94 95 // forever loop processing. 96 func (sl *slots) process(quit chan struct{}) { 97 var head uint32 // the currently pending next free slots 98 var out chan uint32 // nullable output channel, need to pop a free slot when nil 99 for { 100 // if out is nil, need to pop a new head unless quitting 101 if out == nil && quit != nil { 102 // if read a free slot to head, switch on case 0 by assigning out channel 103 head = sl.pop() 104 out = sl.out 105 } 106 107 select { 108 // listen to released slots and append one to the slots 109 case slot, more := <-sl.in: 110 if !more { 111 return 112 } 113 sl.push(slot) 114 115 // let out channel capture the free slot and set out to nil to pop a new free slot 116 case out <- head: 117 out = nil 118 119 // quit is effective only after all initiated releases are received 120 case <-quit: 121 if out != nil { 122 sl.push(head) 123 out = nil 124 } 125 quit = nil 126 sl.wg.Add(1) 127 go func() { 128 defer sl.wg.Done() 129 sl.limboWG.Wait() 130 close(sl.in) 131 }() 132 } 133 } 134 }