Change Future identifier type from `int` to `uint`. (#228)

* Change Future identifier type from `int` to `uint64`.

* Fix compilation error and tests.

* Add integer overflow test, to avoid confusion with next Nim versions, we expect that unsigned integers do not raise any exceptions if overflow happens.

* Switch from `uint64` to `uint` type.

* Add `uint` overflow tests.
This commit is contained in:
Eugene Kabanov 2021-10-21 17:22:11 +03:00 committed by GitHub
parent 2e57ddb455
commit 3bc0bc36f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 81 additions and 38 deletions

View File

@ -9,6 +9,7 @@
# MIT license (LICENSE-MIT) # MIT license (LICENSE-MIT)
import std/[os, tables, strutils, heapqueue, options, deques, sequtils] import std/[os, tables, strutils, heapqueue, options, deques, sequtils]
import stew/base10
import ./srcloc import ./srcloc
export srcloc export srcloc
@ -38,7 +39,7 @@ type
state*: FutureState state*: FutureState
error*: ref CatchableError ## Stored exception error*: ref CatchableError ## Stored exception
mustCancel*: bool mustCancel*: bool
id*: int id*: uint64
when defined(chronosStackTrace): when defined(chronosStackTrace):
errorStackTrace*: StackTrace errorStackTrace*: StackTrace
@ -75,10 +76,10 @@ type
FutureList* = object FutureList* = object
head*: FutureBase head*: FutureBase
tail*: FutureBase tail*: FutureBase
count*: int count*: uint
var currentID* {.threadvar.}: int var currentID* {.threadvar.}: uint64
currentID = 0 currentID = 0'u64
when defined(chronosFutureTracking): when defined(chronosFutureTracking):
var futureList* {.threadvar.}: FutureList var futureList* {.threadvar.}: FutureList
@ -86,12 +87,12 @@ when defined(chronosFutureTracking):
template setupFutureBase(loc: ptr SrcLoc) = template setupFutureBase(loc: ptr SrcLoc) =
new(result) new(result)
currentID.inc()
result.state = FutureState.Pending result.state = FutureState.Pending
when defined(chronosStackTrace): when defined(chronosStackTrace):
result.stackTrace = getStackTrace() result.stackTrace = getStackTrace()
result.id = currentID result.id = currentID
result.location[LocCreateIndex] = loc result.location[LocCreateIndex] = loc
currentID.inc()
when defined(chronosFutureTracking): when defined(chronosFutureTracking):
result.next = nil result.next = nil
@ -198,7 +199,7 @@ proc checkFinished(future: FutureBase, loc: ptr SrcLoc) =
var msg = "" var msg = ""
msg.add("An attempt was made to complete a Future more than once. ") msg.add("An attempt was made to complete a Future more than once. ")
msg.add("Details:") msg.add("Details:")
msg.add("\n Future ID: " & $future.id) msg.add("\n Future ID: " & Base10.toString(future.id))
msg.add("\n Creation location:") msg.add("\n Creation location:")
msg.add("\n " & $future.location[LocCreateIndex]) msg.add("\n " & $future.location[LocCreateIndex])
msg.add("\n First completion location:") msg.add("\n First completion location:")

View File

@ -1112,7 +1112,7 @@ when defined(chronosFutureTracking):
yield slider yield slider
slider = slider.next slider = slider.next
proc pendingFuturesCount*(): int = proc pendingFuturesCount*(): uint =
## Returns number of pending Futures (Future[T] objects which not yet ## Returns number of pending Futures (Future[T] objects which not yet
## completed, cancelled or failed). ## completed, cancelled or failed).
futureList.count futureList.count

View File

@ -12,6 +12,9 @@
import ./asyncloop import ./asyncloop
export asyncloop export asyncloop
when defined(chronosFutureTracking):
import stew/base10
const const
AllFutureStates* = {FutureState.Pending, FutureState.Cancelled, AllFutureStates* = {FutureState.Pending, FutureState.Cancelled,
FutureState.Finished, FutureState.Failed} FutureState.Finished, FutureState.Failed}
@ -28,7 +31,7 @@ proc dumpPendingFutures*(filter = AllFutureStates): string =
## not yet finished). ## not yet finished).
## 2. Future[T] objects with ``FutureState.Finished/Cancelled/Failed`` state ## 2. Future[T] objects with ``FutureState.Finished/Cancelled/Failed`` state
## which callbacks are scheduled, but not yet fully processed. ## which callbacks are scheduled, but not yet fully processed.
var count = 0 var count = 0'u
var res = "" var res = ""
when defined(chronosFutureTracking): when defined(chronosFutureTracking):
for item in pendingFutures(): for item in pendingFutures():
@ -41,13 +44,16 @@ proc dumpPendingFutures*(filter = AllFutureStates): string =
"\"unspecified\"" "\"unspecified\""
else: else:
"\"" & procedure & "\"" "\"" & procedure & "\""
let item = "Future[" & $item.id & "] with name " & $procname & let item = "Future[" & Base10.toString(item.id) & "] with name " &
" created at " & "<" & filename & ":" & $loc.line & ">" & $procname & " created at " & "<" & filename & ":" &
Base10.toString(uint(loc.line)) & ">" &
" and state = " & $item.state & "\n" " and state = " & $item.state & "\n"
res.add(item) res.add(item)
result = $count & " pending Future[T] objects found:\n" & $res Base10.toString(count) & " pending Future[T] objects found:\n" & $res
else:
"0 pending Future[T] objects found\n"
proc pendingFuturesCount*(filter: set[FutureState]): int = proc pendingFuturesCount*(filter: set[FutureState]): uint =
## Returns number of `pending` Future[T] objects which satisfy the ``filter`` ## Returns number of `pending` Future[T] objects which satisfy the ``filter``
## condition. ## condition.
## ##
@ -57,10 +63,10 @@ proc pendingFuturesCount*(filter: set[FutureState]): int =
if filter == AllFutureStates: if filter == AllFutureStates:
pendingFuturesCount() pendingFuturesCount()
else: else:
var res = 0 var res = 0'u
for item in pendingFutures(): for item in pendingFutures():
if item.state in filter: if item.state in filter:
inc(res) inc(res)
res res
else: else:
0 0'u

View File

@ -1315,4 +1315,39 @@ suite "Future[T] behavior test suite":
f2.finished() f2.finished()
f3.finished() f3.finished()
test "Unsigned integer overflow test":
check:
0xFFFF_FFFF_FFFF_FFFF'u64 + 1'u64 == 0'u64
0xFFFF_FFFF'u32 + 1'u32 == 0'u32
when sizeof(uint) == 8:
check 0xFFFF_FFFF_FFFF_FFFF'u + 1'u == 0'u
else:
check 0xFFFF_FFFF'u + 1'u == 0'u
var v1_64 = 0xFFFF_FFFF_FFFF_FFFF'u64
var v2_64 = 0xFFFF_FFFF_FFFF_FFFF'u64
var v1_32 = 0xFFFF_FFFF'u32
var v2_32 = 0xFFFF_FFFF'u32
inc(v1_64)
inc(v1_32)
check:
v1_64 == 0'u64
v2_64 + 1'u64 == 0'u64
v1_32 == 0'u32
v2_32 + 1'u32 == 0'u32
when sizeof(uint) == 8:
var v1_u = 0xFFFF_FFFF_FFFF_FFFF'u
var v2_u = 0xFFFF_FFFF_FFFF_FFFF'u
inc(v1_u)
check:
v1_u == 0'u
v2_u + 1'u == 0'u
else:
var v1_u = 0xFFFF_FFFF'u
var v2_u = 0xFFFF_FFFF'u
inc(v1_u)
check:
v1_u == 0'u
v2_u + 1'u == 0'u

View File

@ -12,19 +12,20 @@ when defined(nimHasUsed): {.used.}
suite "Asynchronous utilities test suite": suite "Asynchronous utilities test suite":
when defined(chronosFutureTracking): when defined(chronosFutureTracking):
proc getCount(): int = proc getCount(): uint =
# This procedure counts number of Future[T] in double-linked list via list # This procedure counts number of Future[T] in double-linked list via list
# iteration. # iteration.
result = 0 var res = 0'u
for item in pendingFutures(): for item in pendingFutures():
inc(result) inc(res)
res
test "Future clean and leaks test": test "Future clean and leaks test":
when defined(chronosFutureTracking): when defined(chronosFutureTracking):
if pendingFuturesCount(WithoutFinished) == 0: if pendingFuturesCount(WithoutFinished) == 0'u:
if pendingFuturesCount(OnlyFinished) > 0: if pendingFuturesCount(OnlyFinished) > 0'u:
poll() poll()
check pendingFuturesCount() == 0 check pendingFuturesCount() == 0'u
else: else:
echo dumpPendingFutures() echo dumpPendingFutures()
check false check false
@ -35,31 +36,31 @@ suite "Asynchronous utilities test suite":
when defined(chronosFutureTracking): when defined(chronosFutureTracking):
var fut1 = newFuture[void]() var fut1 = newFuture[void]()
check: check:
getCount() == 1 getCount() == 1'u
pendingFuturesCount() == 1 pendingFuturesCount() == 1'u
var fut2 = newFuture[void]() var fut2 = newFuture[void]()
check: check:
getCount() == 2 getCount() == 2'u
pendingFuturesCount() == 2 pendingFuturesCount() == 2'u
var fut3 = newFuture[void]() var fut3 = newFuture[void]()
check: check:
getCount() == 3 getCount() == 3'u
pendingFuturesCount() == 3 pendingFuturesCount() == 3'u
fut1.complete() fut1.complete()
poll() poll()
check: check:
getCount() == 2 getCount() == 2'u
pendingFuturesCount() == 2 pendingFuturesCount() == 2'u
fut2.fail(newException(ValueError, "")) fut2.fail(newException(ValueError, ""))
poll() poll()
check: check:
getCount() == 1 getCount() == 1'u
pendingFuturesCount() == 1 pendingFuturesCount() == 1'u
fut3.cancel() fut3.cancel()
poll() poll()
check: check:
getCount() == 0 getCount() == 0'u64
pendingFuturesCount() == 0 pendingFuturesCount() == 0'u64
else: else:
skip() skip()
@ -70,17 +71,17 @@ suite "Asynchronous utilities test suite":
var fut = simpleProc() var fut = simpleProc()
check: check:
getCount() == 2 getCount() == 2'u64
pendingFuturesCount() == 2 pendingFuturesCount() == 2'u64
waitFor fut waitFor fut
check: check:
getCount() == 1 getCount() == 1'u64
pendingFuturesCount() == 1 pendingFuturesCount() == 1'u64
poll() poll()
check: check:
getCount() == 0 getCount() == 0'u64
pendingFuturesCount() == 0 pendingFuturesCount() == 0'u64
else: else:
skip() skip()