Fix defect in uTP buffer (#493)

This commit is contained in:
KonradStaniec 2022-03-24 15:56:00 +01:00 committed by GitHub
parent e794c149f5
commit f79b79f826
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 78 additions and 29 deletions

View File

@ -19,32 +19,32 @@ export options
type type
GrowableCircularBuffer*[A] = object GrowableCircularBuffer*[A] = object
items: seq[Option[A]] items: seq[Option[A]]
mask: int mask: uint32
# provided size will always be adjusted to next power of two # provided size will always be adjusted to next power of two
proc init*[A](T: type GrowableCircularBuffer[A], size: Natural = 16): T = proc init*[A](T: type GrowableCircularBuffer[A], size: uint32 = 16): T =
let powOfTwoSize = nextPowerOfTwo(size) let powOfTwoSize = nextPowerOfTwo(int(size))
T( T(
items: newSeq[Option[A]](size), items: newSeq[Option[A]](powOfTwoSize),
mask: powOfTwoSize - 1 mask: uint32(powOfTwoSize - 1)
) )
proc get*[A](buff: GrowableCircularBuffer[A], i: Natural): Option[A] = proc get*[A](buff: GrowableCircularBuffer[A], i: uint32): Option[A] =
buff.items[i and buff.mask] buff.items[i and buff.mask]
proc putImpl[A](buff: var GrowableCircularBuffer[A], i: Natural, elem: Option[A]) = proc putImpl[A](buff: var GrowableCircularBuffer[A], i: uint32, elem: Option[A]) =
buff.items[i and buff.mask] = elem buff.items[i and buff.mask] = elem
proc put*[A](buff: var GrowableCircularBuffer[A], i: Natural, elem: A) = proc put*[A](buff: var GrowableCircularBuffer[A], i: uint32, elem: A) =
buff.putImpl(i, some(elem)) buff.putImpl(i, some(elem))
proc delete*[A](buff: var GrowableCircularBuffer[A], i: Natural) = proc delete*[A](buff: var GrowableCircularBuffer[A], i: uint32) =
buff.putImpl(i, none[A]()) buff.putImpl(i, none[A]())
proc hasKey*[A](buff: GrowableCircularBuffer[A], i: Natural): bool = proc hasKey*[A](buff: GrowableCircularBuffer[A], i: uint32): bool =
buff.get(i).isSome() buff.get(i).isSome()
proc exists*[A](buff: GrowableCircularBuffer[A], i: Natural, check: proc (x: A): bool): bool = proc exists*[A](buff: GrowableCircularBuffer[A], i: uint32, check: proc (x: A): bool): bool =
let maybeElem = buff.get(i) let maybeElem = buff.get(i)
if (maybeElem.isSome()): if (maybeElem.isSome()):
let elem = maybeElem.unsafeGet() let elem = maybeElem.unsafeGet()
@ -52,32 +52,33 @@ proc exists*[A](buff: GrowableCircularBuffer[A], i: Natural, check: proc (x: A):
else: else:
false false
proc `[]`*[A](buff: var GrowableCircularBuffer[A], i: Natural): var A = proc `[]`*[A](buff: var GrowableCircularBuffer[A], i: uint32): var A =
## Returns contents of the `var GrowableCircularBuffer`. If it is not set, then an exception ## Returns contents of the `var GrowableCircularBuffer`. If it is not set, then an exception
## is thrown. ## is thrown.
buff.items[i and buff.mask].get() buff.items[i and buff.mask].get()
proc len*[A](buff: GrowableCircularBuffer[A]): int = proc len*[A](buff: GrowableCircularBuffer[A]): int =
buff.mask + 1 int(buff.mask) + 1
proc calculateNextMask(currentMask: uint32, index:uint32): uint32 =
# Increase mask,so that index will fit in buffer size i.e mask + 1
if currentMask == uint32.high:
return currentMask
var newSize = currentMask + 1
while true:
newSize = newSize * 2
if newSize == 0 or index < newSize:
break
return newSize - 1
# Item contains the element we want to make space for # Item contains the element we want to make space for
# index is the index in the list. # index is the index in the list.
proc ensureSize*[A](buff: var GrowableCircularBuffer[A], item: Natural, index: Natural) = proc ensureSize*[A](buff: var GrowableCircularBuffer[A], item: uint32, index: uint32) =
# Increase size until is next power of 2 which consists given index
proc getNextSize(currentSize: int, index: int): int =
var newSize = currentSize
while true:
newSize = newSize * 2
if not (index >= newSize):
break
newSize
if (index > buff.mask): if (index > buff.mask):
let currentSize = buff.mask + 1 let newMask = calculateNextMask(buff.mask, index)
let newSize = getNextSize(currentSize, index) var newSeq = newSeq[Option[A]](int(newMask) + 1)
let newMask = newSize - 1 var i = 0'u32
var newSeq = newSeq[Option[A]](newSize)
var i = 0
while i <= buff.mask: while i <= buff.mask:
let idx = item - index + i let idx = item - index + i
newSeq[idx and newMask] = buff.get(idx) newSeq[idx and newMask] = buff.get(idx)

View File

@ -22,6 +22,18 @@ suite "Utp ring buffer":
buff.len() == 4 buff.len() == 4
buff.get(0).isNone() buff.get(0).isNone()
test "Buffer should be initialised to next power of two":
var elemsCounter = 0
let buff = GrowableCircularBuffer[int].init(size = 17)
check:
buff.len() == 32
for i in buff.items:
inc elemsCounter
check:
elemsCounter == 32
test "Adding elements to buffer": test "Adding elements to buffer":
var buff = GrowableCircularBuffer[int].init(size = 4) var buff = GrowableCircularBuffer[int].init(size = 4)
buff.put(11, 11) buff.put(11, 11)
@ -53,7 +65,7 @@ suite "Utp ring buffer":
test "Checking if element exists and has some properties": test "Checking if element exists and has some properties":
var buff = GrowableCircularBuffer[TestObj].init(size = 4) var buff = GrowableCircularBuffer[TestObj].init(size = 4)
let text = "test" let text = "test"
let textIdx = 11 let textIdx = 11'u32
check: check:
not buff.exists(textIdx, x => x.foo == text) not buff.exists(textIdx, x => x.foo == text)
@ -124,3 +136,39 @@ suite "Utp ring buffer":
buff.get(15) == none[int]() buff.get(15) == none[int]()
buff.get(16) == none[int]() buff.get(16) == none[int]()
buff.get(17) == some(17) buff.get(17) == some(17)
test "Ensuring proper wrap around when adding elements to buffer":
var buff = GrowableCircularBuffer[int].init(size = 2)
buff.ensureSize(65535, 0)
buff.put(65535, 65535)
buff.ensureSize(0, 1)
buff.put(0, 0)
# index 2 will not fit in buffer of size 2 so it will need to be expanded to 4
buff.ensureSize(1, 2)
buff.put(1, 1)
check:
buff.len() == 4
buff.ensureSize(2, 3)
buff.put(2, 2)
# index 4 will not fit in buffer size 4 so it will need to expanded
buff.ensureSize(3, 4)
buff.put(3, 3)
# all elements should be available thorugh old indexes
check:
buff.get(65535) == some(65535)
buff.get(0) == some(0)
buff.get(1) == some(1)
buff.get(2) == some(2)
buff.get(3) == some(3)
buff.len() == 8
var elemsCounter = 0
for elem in buff.items:
inc elemsCounter
check:
elemsCounter == 8