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  }