diff --git a/chronos.nimble b/chronos.nimble index a4638314..55196c08 100644 --- a/chronos.nimble +++ b/chronos.nimble @@ -1,5 +1,5 @@ packageName = "chronos" -version = "2.2.1" +version = "2.2.2" author = "Status Research & Development GmbH" description = "Chronos" license = "Apache License 2.0 or MIT" @@ -9,26 +9,45 @@ skipDirs = @["tests"] requires "nim > 0.18.0" -task test, "Run all tests": - for tfile in @[ - "testsync", - "testsoon", - "testtime", - "testfut", - "testsignal", - "testaddress", - "testdatagram", - "teststream", - "testserver", - "testbugs", - ]: - for cmd in @[ - "nim c -r -d:useSysAssert -d:useGcAssert tests/" & tfile, - "nim c -r tests/" & tfile, - #"nim c -r --gc:markAndSweep tests/" & tfile, - "nim c -r -d:release tests/" & tfile - ]: - echo "\n" & cmd - exec cmd - rmFile("tests/" & tfile.toExe()) +import ospaths +task test, "Run all tests": + + var testFiles = @[ + "testsync", + "testsoon", + "testtime", + "testfut", + "testsignal", + "testaddress", + "testdatagram", + "teststream", + "testserver", + "testbugs", + ] + + var testCommands = @[ + "nim c -r -d:useSysAssert -d:useGcAssert", + "nim c -r", + "nim c -r -d:release" + ] + + var timerCommands = @[ + " -d:asyncTimer=system", + " -d:asyncTimer=mono" + ] + + for tfile in testFiles: + if tfile == "testtime": + for cmd in testCommands: + for def in timerCommands: + var commandLine = (cmd & def & " tests") / tfile + echo "\n" & commandLine + exec commandLine + rmFile("tests" / tfile.toExe()) + else: + for cmd in testCommands: + var commandLine = (cmd & " tests") / tfile + echo "\n" & commandLine + exec commandLine + rmFile("tests" / tfile.toExe()) diff --git a/chronos/asyncloop.nim b/chronos/asyncloop.nim index 1eeed641..da1e1433 100644 --- a/chronos/asyncloop.nim +++ b/chronos/asyncloop.nim @@ -165,6 +165,13 @@ export asyncfutures2, timer # TODO: Check if yielded future is nil and throw a more meaningful exception +when defined(windows): + import winlean, sets, hashes +else: + import selectors + from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK, + MSG_NOSIGNAL + type AsyncError* = object of Exception ## Generic async exception @@ -172,7 +179,7 @@ type ## Timeout exception TimerCallback* = object - finishAt*: uint64 + finishAt*: Moment function*: AsyncCallback PDispatcherBase = ref object of RootRef @@ -188,6 +195,24 @@ proc initCallSoonProc() = if asyncfutures2.getCallSoonProc().isNil: asyncfutures2.setCallSoonProc(callSoon) +func getAsyncTimestamp*(a: Duration): auto {.inline.} = + ## Return rounded up value of duration with milliseconds resolution. + ## + ## This function also take care on int32 overflow, because Linux and Windows + ## accepts signed 32bit integer as timeout. + let milsec = Millisecond.nanoseconds() + let nansec = a.nanoseconds() + var res = nansec div milsec + let mid = nansec mod milsec + when defined(windows): + res = min(cast[int64](high(int32) - 1), res) + result = cast[DWORD](res) + result += DWORD(min(1'i32, cast[int32](mid))) + else: + res = min(cast[int64](high(int32) - 1), res) + result = cast[int32](res) + result += min(1, cast[int32](mid)) + template processTimersGetTimeout(loop, timeout: untyped) = var count = len(loop.timers) if count > 0: @@ -199,10 +224,7 @@ template processTimersGetTimeout(loop, timeout: untyped) = loop.callbacks.addLast(loop.timers.pop().function) dec(count) if count > 0: - when defined(windows): - timeout = DWORD(lastFinish - curTime) - else: - timeout = int(lastFinish - curTime) + timeout = (lastFinish - curTime).getAsyncTimestamp() if timeout == 0: if len(loop.callbacks) == 0: @@ -215,7 +237,7 @@ template processTimersGetTimeout(loop, timeout: untyped) = timeout = 0 template processTimers(loop: untyped) = - var curTime = fastEpochTime() + var curTime = Moment.now() var count = len(loop.timers) if count > 0: while count > 0: @@ -238,7 +260,6 @@ template processCallbacks(loop: untyped) = callable.function(callable.udata) when defined(windows) or defined(nimdoc): - import winlean, sets, hashes type WSAPROC_TRANSMITFILE = proc(hSocket: SocketHandle, hFile: Handle, nNumberOfBytesToWrite: DWORD, @@ -316,7 +337,7 @@ when defined(windows) or defined(nimdoc): proc poll*() = ## Perform single asynchronous step. let loop = getGlobalDispatcher() - var curTime = fastEpochTime() + var curTime = Moment.now() var curTimeout = DWORD(0) # Moving expired timers to `loop.callbacks` and calculate timeout @@ -328,8 +349,10 @@ when defined(windows) or defined(nimdoc): var customOverlapped: PtrCustomOverlapped let res = getQueuedCompletionStatus( - loop.ioPort, addr lpNumberOfBytesTransferred, addr lpCompletionKey, - cast[ptr POVERLAPPED](addr customOverlapped), curTimeout).bool + loop.ioPort, addr lpNumberOfBytesTransferred, + addr lpCompletionKey, cast[ptr POVERLAPPED](addr customOverlapped), + curTimeout).bool + if res: customOverlapped.data.bytesCount = lpNumberOfBytesTransferred customOverlapped.data.errCode = OSErrorCode(-1) @@ -428,9 +451,6 @@ when defined(windows) or defined(nimdoc): return fd in disp.handles else: - import selectors - from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK, - MSG_NOSIGNAL type AsyncFD* = distinct cint @@ -611,7 +631,7 @@ else: proc poll*() = ## Perform single asynchronous step. let loop = getGlobalDispatcher() - var curTime = fastEpochTime() + var curTime = Moment.now() var curTimeout = 0 when ioselSupportedPlatform: @@ -655,7 +675,7 @@ else: proc initAPI() = discard getGlobalDispatcher() -proc addTimer*(at: uint64, cb: CallbackFunc, udata: pointer = nil) = +proc addTimer*(at: Moment, cb: CallbackFunc, udata: pointer = nil) = ## Arrange for the callback ``cb`` to be called at the given absolute ## timestamp ``at``. You can also pass ``udata`` to callback. let loop = getGlobalDispatcher() @@ -663,7 +683,11 @@ proc addTimer*(at: uint64, cb: CallbackFunc, udata: pointer = nil) = function: AsyncCallback(function: cb, udata: udata)) loop.timers.push(tcb) -proc removeTimer*(at: uint64, cb: CallbackFunc, udata: pointer = nil) = +proc addTimer*(at: int64, cb: CallbackFunc, udata: pointer = nil) {. + inline, deprecated: "Use addTimer(Duration, cb, udata)".} = + addTimer(Moment.init(at, Millisecond), cb, udata) + +proc removeTimer*(at: Moment, cb: CallbackFunc, udata: pointer = nil) = ## Remove timer callback ``cb`` with absolute timestamp ``at`` from waiting ## queue. let loop = getGlobalDispatcher() @@ -677,18 +701,25 @@ proc removeTimer*(at: uint64, cb: CallbackFunc, udata: pointer = nil) = if index != -1: loop.timers.del(index) -proc sleepAsync*(ms: int): Future[void] = +proc removeTimer*(at: int64, cb: CallbackFunc, udata: pointer = nil) {. + inline, deprecated: "Use removeTimer(Duration, cb, udata)".} = + removeTimer(Moment.init(at, Millisecond), cb, udata) + +proc sleepAsync*(ms: Duration): Future[void] = ## Suspends the execution of the current async procedure for the next ## ``ms`` milliseconds. var retFuture = newFuture[void]("sleepAsync") proc completion(data: pointer) = if not retFuture.finished: retFuture.complete() - addTimer(fastEpochTime() + uint64(ms), - completion, cast[pointer](retFuture)) + addTimer(Moment.fromNow(ms), completion, cast[pointer](retFuture)) return retFuture -proc withTimeout*[T](fut: Future[T], timeout: int): Future[bool] = +proc sleepAsync*(ms: int): Future[void] {. + inline, deprecated: "Use sleepAsync(Duration)".} = + result = sleepAsync(ms.milliseconds()) + +proc withTimeout*[T](fut: Future[T], timeout: Duration): Future[bool] = ## Returns a future which will complete once ``fut`` completes or after ## ``timeout`` milliseconds has elapsed. ## @@ -704,11 +735,15 @@ proc withTimeout*[T](fut: Future[T], timeout: int): Future[bool] = else: if not retFuture.finished: retFuture.complete(true) - addTimer(fastEpochTime() + uint64(timeout), continuation, nil) + addTimer(Moment.fromNow(timeout), continuation, nil) fut.addCallback(continuation) return retFuture -proc wait*[T](fut: Future[T], timeout = -1): Future[T] = +proc withTimeout*[T](fut: Future[T], timeout: int): Future[bool] {. + inline, deprecated: "Use withTimeout(Future[T], Duration)".} = + result = withTimeout(fut, timeout.milliseconds()) + +proc wait*[T](fut: Future[T], timeout = InfiniteDuration): Future[T] = ## Returns a future which will complete once future ``fut`` completes ## or if timeout of ``timeout`` milliseconds has been expired. ## @@ -726,9 +761,9 @@ proc wait*[T](fut: Future[T], timeout = -1): Future[T] = retFuture.fail(fut.error) else: retFuture.complete(fut.read()) - if timeout == -1: + if timeout.isInfinite(): retFuture = fut - elif timeout == 0: + elif timeout.isZero(): if fut.finished: if fut.failed: retFuture.fail(fut.error) @@ -737,10 +772,19 @@ proc wait*[T](fut: Future[T], timeout = -1): Future[T] = else: retFuture.fail(newException(AsyncTimeoutError, "")) else: - addTimer(fastEpochTime() + uint64(timeout), continuation, nil) + addTimer(Moment.fromNow(timeout), continuation, nil) fut.addCallback(continuation) return retFuture +proc wait*[T](fut: Future[T], timeout = -1): Future[T] {. + inline, deprecated: "Use wait(Future[T], Duration)".} = + if timeout == -1: + wait(fut, InfiniteDuration) + elif timeout == 0: + wait(fut, ZeroDuration) + else: + wait(fut, timeout.milliseconds()) + include asyncmacro2 proc callSoon(cbproc: CallbackFunc, data: pointer = nil) = diff --git a/chronos/timer.nim b/chronos/timer.nim index 84105049..62f9034d 100644 --- a/chronos/timer.nim +++ b/chronos/timer.nim @@ -8,45 +8,437 @@ # Apache License, version 2.0, (LICENSE-APACHEv2) # MIT license (LICENSE-MIT) - ## This module implements cross-platform system timer with ## milliseconds resolution. +## +## Timer supports two types of clocks: +## ``system`` uses the most fast OS primitive to obtain wall clock time. +## ``mono`` uses monotonic clock time (default). +## +## ``system`` clock is affected by discontinuous jumps in the system time. This +## clock is significantly faster then ``mono`` clock in most of the cases. +## +## ``mono`` clock is not affected by discontinuous jumps in the system time. +## This clock is slower then ``system`` clock. +## +## You can specify which timer you want to use ``-d:asyncTimer=``. +const asyncTimer* {.strdefine.} = "mono" when defined(windows): + from winlean import DWORD - from winlean import DWORD, getSystemTimeAsFileTime, FILETIME + when asyncTimer == "system": + from winlean import getSystemTimeAsFileTime, FILETIME - proc fastEpochTime*(): uint64 {.inline.} = - var t: FILETIME - getSystemTimeAsFileTime(t) - result = ((uint64(t.dwHighDateTime) shl 32) or - uint64(t.dwLowDateTime)) div 10_000 + proc fastEpochTime*(): uint64 {. + inline, deprecated: "Use Moment.now()".} = + ## Timer resolution is millisecond. + var t: FILETIME + getSystemTimeAsFileTime(t) + result = ((cast[uint64](t.dwHighDateTime) shl 32) or + cast[uint64](t.dwLowDateTime)) div 10_000 + + proc fastEpochTimeNano(): uint64 {.inline.} = + ## Timer resolution is nanosecond. + var t: FILETIME + getSystemTimeAsFileTime(t) + result = ((cast[uint64](t.dwHighDateTime) shl 32) or + cast[uint64](t.dwLowDateTime)) * 100 + + else: + proc QueryPerformanceCounter(res: var uint64) {. + importc: "QueryPerformanceCounter", stdcall, dynlib: "kernel32".} + proc QueryPerformanceFrequency(res: var uint64) {. + importc: "QueryPerformanceFrequency", stdcall, dynlib: "kernel32".} + + var queryFrequencyM: uint64 + var queryFrequencyN: uint64 + + proc fastEpochTimeNano(): uint64 {.inline.} = + ## Procedure's resolution is nanosecond. + var res: uint64 + QueryPerformanceCounter(res) + result = res * queryFrequencyN + + proc fastEpochTime*(): uint64 {. + inline, deprecated: "Use Moment.now()".} = + ## Procedure's resolution is millisecond. + var res: uint64 + QueryPerformanceCounter(res) + result = res div queryFrequencyM + + proc setupQueryFrequence() = + var freq: uint64 + QueryPerformanceFrequency(freq) + if freq < 1000: + queryFrequencyM = freq + else: + queryFrequencyM = freq div 1_000 + queryFrequencyN = 1_000_000_000'u64 div freq + + setupQueryFrequence() elif defined(macosx): - from posix import Timeval + when asyncTimer == "system": + from posix import Timeval - proc posix_gettimeofday(tp: var Timeval, unused: pointer = nil) {. - importc: "gettimeofday", header: "".} + proc posix_gettimeofday(tp: var Timeval, unused: pointer = nil) {. + importc: "gettimeofday", header: "".} - proc fastEpochTime*(): uint64 {.inline.} = - var t: Timeval - posix_gettimeofday(t) - result = (uint64(t.tv_sec) * 1_000 + uint64(t.tv_usec) div 1_000) + proc fastEpochTime*(): uint64 {. + inline, deprecated: "Use Moment.now()".} = + ## Procedure's resolution is millisecond. + var t: Timeval + posix_gettimeofday(t) + result = (cast[uint64](t.tv_sec) * 1_000 + + cast[uint64](t.tv_usec) div 1_000) + + proc fastEpochTimeNano(): uint64 {.inline.} = + ## Procedure's resolution is nanosecond. + var t: Timeval + posix_gettimeofday(t) + result = (cast[uint64](t.tv_sec) * 1_000_000_000 + + cast[uint64](t.tv_usec) * 1_000) + else: + from posix import clock_gettime, Timespec, CLOCK_MONOTONIC + + proc fastEpochTime*(): uint64 {. + inline, deprecated: "Use Moment.now()".} = + ## Procedure's resolution is millisecond. + var t: Timespec + discard clock_gettime(CLOCK_MONOTONIC, t) + result = ((cast[uint64](t.tv_sec) * 1_000) + + (cast[uint64](t.tv_nsec) div 1_000_000)) + + proc fastEpochTimeNano(): uint64 {.inline.} = + ## Procedure's resolution is nanosecond. + var t: Timespec + discard clock_gettime(CLOCK_MONOTONIC, t) + result = cast[uint64](t.tv_sec) * 1_000_000_000'u64 + + cast[uint64](t.tv_nsec) elif defined(posix): + from posix import clock_gettime, Timespec, CLOCK_REALTIME, CLOCK_MONOTONIC - from posix import clock_gettime, Timespec, CLOCK_REALTIME + when asyncTimer == "system": + proc fastEpochTime*(): uint64 {. + inline, deprecated: "Use Moment.now()".} = + ## Procedure's resolution is millisecond. + var t: Timespec + discard clock_gettime(CLOCK_REALTIME, t) + result = (cast[uint64](t.tv_sec) * 1_000 + + (cast[uint64](t.tv_nsec) div 1_000_000)) - proc fastEpochTime*(): uint64 {.inline.} = - var t: Timespec - discard clock_gettime(CLOCK_REALTIME, t) - result = (uint64(t.tv_sec) * 1_000 + uint64(t.tv_nsec) div 1_000_000) + proc fastEpochTimeNano(): uint64 {.inline.} = + ## Procedure's resolution is nanosecond. + var t: Timespec + discard clock_gettime(CLOCK_REALTIME, t) + result = cast[uint64](t.tv_sec) * 1_000_000_000'u64 + + cast[uint64](t.tv_nsec) + + else: + proc fastEpochTime*(): uint64 {. + inline, deprecated: "Use Moment.now()".} = + ## Procedure's resolution is millisecond. + var t: Timespec + discard clock_gettime(CLOCK_MONOTONIC, t) + result = (cast[uint64](t.tv_sec) * 1_000 + + (cast[uint64](t.tv_nsec) div 1_000_000)) + + proc fastEpochTimeNano(): uint64 {.inline.} = + ## Procedure's resolution is nanosecond. + var t: Timespec + discard clock_gettime(CLOCK_MONOTONIC, t) + result = cast[uint64](t.tv_sec) * 1_000_000_000'u64 + + cast[uint64](t.tv_nsec) elif defined(nimdoc): - proc fastEpochTime*(): uint64 + proc fastEpochTime*(): uint64 {.deprecated: "Use Moment.now()".} ## Returns system's timer in milliseconds. else: error("Sorry, your operation system is not yet supported!") + +type + Moment* = object + ## A Moment in time. Its value has no direct meaning, but can be compared + ## with other Moments. Moments are captured using a monotonically + ## non-decreasing clock (by default). + value: int64 + + Duration* = object + ## A Duration is the interval between to points in time. + value: int64 + +when sizeof(int) == 4: + type SomeIntegerI64* = SomeSignedInt|uint|uint8|uint16|uint32 +else: + type SomeIntegerI64* = SomeSignedInt|uint8|uint16|uint32 + +func `+`*(a: Duration, b: Duration): Duration {.inline.} = + ## Duration + Duration = Duration + result.value = a.value + b.value + +func `+`*(a: Duration, b: Moment): Moment {.inline.} = + ## Duration + Moment = Moment + result.value = a.value + b.value + +func `+`*(a: Moment, b: Duration): Moment {.inline.} = + ## Moment + Duration = Moment + result.value = a.value + b.value + +func `+=`*(a: var Moment, b: Duration) {.inline.} = + ## Moment += Duration + a.value += b.value + +func `+=`*(a: var Duration, b: Duration) {.inline.} = + ## Duration += Duration + a.value += b.value + +func `-`*(a, b: Moment): Duration {.inline.} = + ## Moment - Moment = Duration + ## + ## Note: Duration can't be negative. + result.value = if a.value >= b.value: a.value - b.value else: 0'i64 + +func `-`*(a: Moment, b: Duration): Moment {.inline.} = + ## Moment - Duration = Moment + ## + ## Note: Moment can be negative + result.value = a.value - b.value + +func `-`*(a: Duration, b: Duration): Duration {.inline.} = + ## Duration - Duration = Duration + ## + ## Note: Duration can't be negative. + result.value = if a.value >= b.value: a.value - b.value else: 0'i64 + +func `-=`*(a: var Duration, b: Duration): Duration {.inline.} = + ## Duration -= Duration + a.value = if a.value >= b.value: a.value - b.value else: 0'i64 + +func `-=`*(a: var Moment, b: Duration): Moment {.inline.} = + ## Moment -= Duration + a.value -= b.value + +func `==`*(a, b: Duration): bool {.inline.} = + ## Returns ``true`` if ``a`` equal to ``b``. + result = (a.value == b.value) + +func `==`*(a, b: Moment): bool {.inline.} = + ## Returns ``true`` if ``a`` equal to ``b``. + result = (a.value == b.value) + +func `<`*(a, b: Duration): bool {.inline.} = + ## Returns ``true`` if ``a`` less then ``b``. + result = (a.value < b.value) + +func `<`*(a, b: Moment): bool {.inline.} = + ## Returns ``true`` if ``a`` less then ``b``. + result = (a.value < b.value) + +func `<=`*(a, b: Duration): bool {.inline.} = + ## Returns ``true`` if ``a`` less or equal ``b``. + result = (a.value <= b.value) + +func `<=`*(a, b: Moment): bool {.inline.} = + ## Returns ``true`` if ``a`` less or equal ``b``. + result = (a.value <= b.value) + +func `>`*(a, b: Duration): bool {.inline.} = + ## Returns ``true`` if ``a`` bigger then ``b``. + result = (a.value > b.value) + +func `>`*(a, b: Moment): bool {.inline.} = + ## Returns ``true`` if ``a`` bigger then ``b``. + result = (a.value > b.value) + +func `>=`*(a, b: Duration): bool {.inline.} = + ## Returns ``true`` if ``a`` bigger or equal ``b``. + result = (a.value >= b.value) + +func `>=`*(a, b: Moment): bool {.inline.} = + ## Returns ``true`` if ``a`` bigger or equal ``b``. + result = (a.value >= b.value) + +func `*`*(a: Duration, b: SomeIntegerI64): Duration {.inline.} = + ## Returns Duration multiplied by scalar integer. + result.value = a.value * cast[int64](b) + +func `*`*(a: SomeIntegerI64, b: Duration): Duration {.inline.} = + ## Returns Duration multiplied by scalar integer. + result.value = cast[int64](a) * b.value + +func `div`*(a: Duration, b: SomeIntegerI64): Duration {.inline.} = + ## Returns Duration which is result of dividing a Duration by scalar integer. + result.value = a.value div cast[int64](b) + +const + Nanosecond* = Duration(value: 1'i64) + Microsecond* = Nanosecond * 1_000'i64 + Millisecond* = Microsecond * 1_000'i64 + Second* = Millisecond * 1_000'i64 + Minute* = Second * 60'i64 + Hour* = Minute * 60'i64 + Day* = Hour * 24'i64 + Week* = Day * 7'i64 + + ZeroDuration* = Duration(value: 0'i64) + InfiniteDuration* = Duration(value: high(int64)) + +func nanoseconds*(v: SomeIntegerI64): Duration {.inline.} = + ## Initialize Duration with nanoseconds value ``v``. + result.value = cast[int64](v) + +func microseconds*(v: SomeIntegerI64): Duration {.inline.} = + ## Initialize Duration with microseconds value ``v``. + result.value = cast[int64](v) * Microsecond.value + +func milliseconds*(v: SomeIntegerI64): Duration {.inline.} = + ## Initialize Duration with milliseconds value ``v``. + result.value = cast[int64](v) * Millisecond.value + +func seconds*(v: SomeIntegerI64): Duration {.inline.} = + ## Initialize Duration with seconds value ``v``. + result.value = cast[int64](v) * Second.value + +func minutes*(v: SomeIntegerI64): Duration {.inline.} = + ## Initialize Duration with minutes value ``v``. + result.value = Minute.value * cast[int64](v) + +func hours*(v: SomeIntegerI64): Duration {.inline.} = + ## Initialize Duration with hours value ``v``. + result.value = cast[int64](v) * Hour.value + +func days*(v: SomeIntegerI64): Duration {.inline.} = + ## Initialize Duration with days value ``v``. + result.value = cast[int64](v) * Day.value + +func weeks*(v: SomeIntegerI64): Duration {.inline.} = + ## Initialize Duration with weeks value ``v``. + result.value = cast[int64](v) * Week.value + +func nanoseconds*(v: Duration): int64 {.inline.} = + ## Round Duration ``v`` to nanoseconds. + result = v.value + +func microseconds*(v: Duration): int64 {.inline.} = + ## Round Duration ``v`` to microseconds. + result = v.value div Microsecond.value + +func milliseconds*(v: Duration): int64 {.inline.} = + ## Round Duration ``v`` to milliseconds. + result = v.value div Millisecond.value + +func seconds*(v: Duration): int64 {.inline.} = + ## Round Duration ``v`` to seconds. + result = v.value div Second.value + +func minutes*(v: Duration): int64 {.inline.} = + ## Round Duration ``v`` to minutes. + result = v.value div Minute.value + +func hours*(v: Duration): int64 {.inline.} = + ## Round Duration ``v`` to hours. + result = v.value div Hour.value + +func days*(v: Duration): int64 {.inline.} = + ## Round Duration ``v`` to days. + result = v.value div Day.value + +func weeks*(v: Duration): int64 {.inline.} = + ## Round Duration ``v`` to weeks. + result = v.value div Week.value + +func nanos*(v: SomeIntegerI64): Duration {.inline.} = + result = nanoseconds(v) + +func micros*(v: SomeIntegerI64): Duration {.inline.} = + result = microseconds(v) + +func millis*(v: SomeIntegerI64): Duration {.inline.} = + result = milliseconds(v) + +func secs*(v: SomeIntegerI64): Duration {.inline.} = + result = seconds(v) + +func nanos*(v: Duration): int64 {.inline.} = + result = nanoseconds(v) + +func micros*(v: Duration): int64 {.inline.} = + result = microseconds(v) + +func millis*(v: Duration): int64 {.inline.} = + result = milliseconds(v) + +func secs*(v: Duration): int64 {.inline.} = + result = seconds(v) + +func `$`*(a: Duration): string {.inline.} = + ## Returns string representation of Duration ``a`` as nanoseconds value. + var v = a.value + if v >= Week.value: + result = $(v div Week.value) & "w" + v = v mod Week.value + if v >= Day.value: + result &= $(v div Day.value) & "d" + v = v mod Day.value + if v >= Hour.value: + result &= $(v div Hour.value) & "h" + v = v mod Hour.value + if v >= Minute.value: + result &= $(v div Minute.value) & "m" + v = v mod Minute.value + if v >= Second.value: + result &= $(v div Second.value) & "s" + v = v mod Second.value + if v >= Millisecond.value: + result &= $(v div Millisecond.value) & "ms" + v = v mod Millisecond.value + if v >= Microsecond.value: + result &= $(v div Microsecond.value) & "us" + v = v mod Microsecond.value + if v >= Nanosecond.value: + result &= $(v div Nanosecond.value) & "ns" + +func `$`*(a: Moment): string {.inline.} = + ## Returns string representation of Moment ``a`` as nanoseconds value. + result = $(a.value) + result.add("ns") + +func isZero*(a: Duration): bool {.inline.} = + ## Returns ``true`` if Duration ``a`` is ``0``. + result = (a.value == 0) + +func isInfinite*(a: Duration): bool {.inline.} = + ## Returns ``true`` if Duration ``a`` is infinite. + result = (a.value == InfiniteDuration.value) + +proc now*(t: typedesc[Moment]): Moment {.inline.} = + ## Returns current moment in time as Moment. + result.value = cast[int64](fastEpochTimeNano()) + +func init*(t: typedesc[Moment], value: int64, precision: Duration): Moment = + ## Initialize Moment with absolute time value ``value`` with precision + ## ``precision``. + result.value = value * precision.value + +proc fromNow*(t: typedesc[Moment], a: Duration): Moment {.inline.} = + ## Returns moment in time which is equal to current moment + Duration. + result = Moment.now() + a + +when defined(posix): + from posix import Time, Suseconds, Timeval, Timespec + + func toTimeval*(a: Duration): Timeval = + ## Convert Duration ``a`` to ``Timeval`` object. + let m = a.value mod Second.value + result.tv_sec = cast[Time](a.value div Second.value) + result.tv_usec = cast[Suseconds](m div Microsecond.value) + + func toTimespec*(a: Duration): Timespec = + ## Convert Duration ``a`` to ``Timespec`` object. + result.tv_sec = cast[Time](a.value div Second.value) + result.tv_nsec = cast[int](a.value mod Second.value) diff --git a/chronos/transports/stream.nim b/chronos/transports/stream.nim index 1dd81e36..5165301f 100644 --- a/chronos/transports/stream.nim +++ b/chronos/transports/stream.nim @@ -582,7 +582,7 @@ when defined(windows): if pipeHandle == INVALID_HANDLE_VALUE: let err = osLastError() if int32(err) == ERROR_PIPE_BUSY: - addTimer(fastEpochTime() + 50, pipeContinuation, nil) + addTimer(Moment.fromNow(50.milliseconds), pipeContinuation, nil) else: retFuture.fail(getTransportOsError(err)) else: diff --git a/tests/testdatagram.nim b/tests/testdatagram.nim index f2b7997f..955ddec6 100644 --- a/tests/testdatagram.nim +++ b/tests/testdatagram.nim @@ -444,7 +444,7 @@ proc testConnReset(): Future[bool] {.async.} = var dgram2 = newDatagramTransport(clientMark) var data = "MESSAGE" asyncCheck dgram2.sendTo(ta, data) - await sleepAsync(1000) + await sleepAsync(2000.milliseconds) result = (counter == 0) dgram2.close() await dgram2.join() diff --git a/tests/testfut.nim b/tests/testfut.nim index bafdbfe2..53b715ef 100644 --- a/tests/testfut.nim +++ b/tests/testfut.nim @@ -10,7 +10,7 @@ import unittest import ../chronos proc testFuture1(): Future[int] {.async.} = - await sleepAsync(100) + await sleepAsync(1.milliseconds) proc testFuture2(): Future[int] {.async.} = return 1 @@ -18,11 +18,14 @@ proc testFuture2(): Future[int] {.async.} = proc testFuture3(): Future[int] {.async.} = result = await testFuture2() +proc testFuture100(): Future[int] {.async.} = + await sleepAsync(100.milliseconds) + proc testFuture4(): Future[int] {.async.} = ## Test for not immediately completed future and timeout = -1 result = 0 try: - var res = await wait(testFuture1(), -1) + var res = await wait(testFuture1(), InfiniteDuration) result = 1 except: result = 0 @@ -33,7 +36,7 @@ proc testFuture4(): Future[int] {.async.} = ## Test for immediately completed future and timeout = -1 result = 0 try: - var res = await wait(testFuture2(), -1) + var res = await wait(testFuture2(), InfiniteDuration) result = 2 except: result = 0 @@ -44,7 +47,7 @@ proc testFuture4(): Future[int] {.async.} = ## Test for not immediately completed future and timeout = 0 result = 0 try: - var res = await wait(testFuture1(), 0) + var res = await wait(testFuture1(), 0.milliseconds) except AsyncTimeoutError: result = 3 @@ -54,7 +57,7 @@ proc testFuture4(): Future[int] {.async.} = ## Test for immediately completed future and timeout = 0 result = 0 try: - var res = await wait(testFuture2(), 0) + var res = await wait(testFuture2(), 0.milliseconds) result = 4 except: result = 0 @@ -65,7 +68,7 @@ proc testFuture4(): Future[int] {.async.} = ## Test for future which cannot be completed in timeout period result = 0 try: - var res = await wait(testFuture1(), 50) + var res = await wait(testFuture100(), 50.milliseconds) except AsyncTimeoutError: result = 5 @@ -74,7 +77,7 @@ proc testFuture4(): Future[int] {.async.} = ## Test for future which will be completed before timeout exceeded. try: - var res = await wait(testFuture1(), 300) + var res = await wait(testFuture100(), 500.milliseconds) result = 6 except: result = -6 @@ -83,6 +86,8 @@ proc test1(): bool = var fut = testFuture1() poll() poll() + if not fut.finished: + poll() result = fut.finished proc test2(): bool = @@ -138,106 +143,106 @@ proc testAllVarargs(): int = var completedFutures = 0 proc vlient1() {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) proc vlient2() {.async.} = - await sleepAsync(200) + await sleepAsync(200.milliseconds) inc(completedFutures) proc vlient3() {.async.} = - await sleepAsync(300) + await sleepAsync(300.milliseconds) inc(completedFutures) proc vlient4() {.async.} = - await sleepAsync(400) + await sleepAsync(400.milliseconds) inc(completedFutures) proc vlient5() {.async.} = - await sleepAsync(500) + await sleepAsync(500.milliseconds) inc(completedFutures) proc vlient1f() {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc vlient2f() {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc vlient3f() {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc vlient4f() {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc vlient5f() {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc client1(): Future[int] {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) result = 1 proc client2(): Future[int] {.async.} = - await sleepAsync(200) + await sleepAsync(200.milliseconds) inc(completedFutures) result = 1 proc client3(): Future[int] {.async.} = - await sleepAsync(300) + await sleepAsync(300.milliseconds) inc(completedFutures) result = 1 proc client4(): Future[int] {.async.} = - await sleepAsync(400) + await sleepAsync(400.milliseconds) inc(completedFutures) result = 1 proc client5(): Future[int] {.async.} = - await sleepAsync(500) + await sleepAsync(500.milliseconds) inc(completedFutures) result = 1 proc client1f(): Future[int] {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc client2f(): Future[int] {.async.} = - await sleepAsync(200) + await sleepAsync(200.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc client3f(): Future[int] {.async.} = - await sleepAsync(300) + await sleepAsync(300.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc client4f(): Future[int] {.async.} = - await sleepAsync(400) + await sleepAsync(400.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc client5f(): Future[int] {.async.} = - await sleepAsync(500) + await sleepAsync(500.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") @@ -284,106 +289,106 @@ proc testAllSeq(): int = var nfutures = newSeq[Future[int]]() proc vlient1() {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) proc vlient2() {.async.} = - await sleepAsync(200) + await sleepAsync(200.milliseconds) inc(completedFutures) proc vlient3() {.async.} = - await sleepAsync(300) + await sleepAsync(300.milliseconds) inc(completedFutures) proc vlient4() {.async.} = - await sleepAsync(400) + await sleepAsync(400.milliseconds) inc(completedFutures) proc vlient5() {.async.} = - await sleepAsync(500) + await sleepAsync(500.milliseconds) inc(completedFutures) proc vlient1f() {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc vlient2f() {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc vlient3f() {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc vlient4f() {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc vlient5f() {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc client1(): Future[int] {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) result = 1 proc client2(): Future[int] {.async.} = - await sleepAsync(200) + await sleepAsync(200.milliseconds) inc(completedFutures) result = 1 proc client3(): Future[int] {.async.} = - await sleepAsync(300) + await sleepAsync(300.milliseconds) inc(completedFutures) result = 1 proc client4(): Future[int] {.async.} = - await sleepAsync(400) + await sleepAsync(400.milliseconds) inc(completedFutures) result = 1 proc client5(): Future[int] {.async.} = - await sleepAsync(500) + await sleepAsync(500.milliseconds) inc(completedFutures) result = 1 proc client1f(): Future[int] {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc client2f(): Future[int] {.async.} = - await sleepAsync(200) + await sleepAsync(200.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc client3f(): Future[int] {.async.} = - await sleepAsync(300) + await sleepAsync(300.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc client4f(): Future[int] {.async.} = - await sleepAsync(400) + await sleepAsync(400.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc client5f(): Future[int] {.async.} = - await sleepAsync(500) + await sleepAsync(500.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") @@ -464,51 +469,51 @@ proc testAsyncDiscard(): int = var completedFutures = 0 proc client1() {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) proc client2() {.async.} = - await sleepAsync(200) + await sleepAsync(200.milliseconds) inc(completedFutures) proc client3() {.async.} = - await sleepAsync(300) + await sleepAsync(300.milliseconds) inc(completedFutures) proc client4() {.async.} = - await sleepAsync(400) + await sleepAsync(400.milliseconds) inc(completedFutures) proc client5() {.async.} = - await sleepAsync(500) + await sleepAsync(500.milliseconds) inc(completedFutures) proc client1f() {.async.} = - await sleepAsync(100) + await sleepAsync(100.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc client2f() {.async.} = - await sleepAsync(200) + await sleepAsync(200.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc client3f() {.async.} = - await sleepAsync(300) + await sleepAsync(300.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc client4f() {.async.} = - await sleepAsync(400) + await sleepAsync(400.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") proc client5f() {.async.} = - await sleepAsync(500) + await sleepAsync(500.milliseconds) inc(completedFutures) if true: raise newException(ValueError, "") @@ -524,7 +529,7 @@ proc testAsyncDiscard(): int = asyncDiscard client5() asyncDiscard client5f() - waitFor(sleepAsync(2000)) + waitFor(sleepAsync(2000.milliseconds)) result = completedFutures proc testAllZero(): bool = diff --git a/tests/testsignal.nim b/tests/testsignal.nim index 05b912af..9a2f4b8e 100644 --- a/tests/testsignal.nim +++ b/tests/testsignal.nim @@ -20,7 +20,7 @@ when not defined(windows): removeSignal(int(cdata.fd)) proc asyncProc() {.async.} = - await sleepAsync(500) + await sleepAsync(500.milliseconds) proc test(signal, value: int): bool = discard addSignal(signal, signalProc, cast[pointer](value)) diff --git a/tests/testsoon.nim b/tests/testsoon.nim index 488ae3f3..0842a91f 100644 --- a/tests/testsoon.nim +++ b/tests/testsoon.nim @@ -41,7 +41,7 @@ proc test1(): uint = proc testProc() {.async.} = for i in 1..CallSoonTests: - await sleepAsync(100) + await sleepAsync(100.milliseconds) timeoutsTest1 += 1 proc callbackProc(udata: pointer) {.gcsafe.} = diff --git a/tests/testtime.nim b/tests/testtime.nim index a5b1d1fe..b4b6d259 100644 --- a/tests/testtime.nim +++ b/tests/testtime.nim @@ -6,16 +6,16 @@ # Apache License, version 2.0, (LICENSE-APACHEv2) # MIT license (LICENSE-MIT) -import unittest +import os, unittest import ../chronos, ../chronos/timer const TimersCount = 10 -proc timeWorker(time: int): Future[int] {.async.} = - var st = fastEpochTime() +proc timeWorker(time: Duration): Future[Duration] {.async.} = + var st = Moment.now() await sleepAsync(time) - var et = fastEpochTime() - result = int(et - st) + var et = Moment.now() + result = et - st proc waitAll[T](futs: seq[Future[T]]): Future[void] = var counter = len(futs) @@ -28,25 +28,34 @@ proc waitAll[T](futs: seq[Future[T]]): Future[void] = fut.addCallback(cb) return retFuture -proc test(timeout: int): Future[int64] {.async.} = - var workers = newSeq[Future[int]](TimersCount) +proc test(timeout: Duration): Future[Duration] {.async.} = + var workers = newSeq[Future[Duration]](TimersCount) for i in 0..= 1000.milliseconds) and (d <= 2_000.milliseconds) + when isMainModule: suite "Asynchronous timers test suite": + test "Timer reliability test [" & asyncTimer & "]": + check testTimer() == true test $TimersCount & " timers with 10ms timeout": - var res = waitFor(test(10)) - check (res >= 10) and (res <= 100) + var res = waitFor(test(10.milliseconds)) + check (res >= 10.milliseconds) and (res <= 100.milliseconds) test $TimersCount & " timers with 100ms timeout": - var res = waitFor(test(100)) - check (res >= 100) and (res <= 1000) + var res = waitFor(test(100.milliseconds)) + check (res >= 100.milliseconds) and (res <= 1000.milliseconds) test $TimersCount & " timers with 1000ms timeout": - var res = waitFor(test(1000)) - check (res >= 1000) and (res <= 5000) + var res = waitFor(test(1000.milliseconds)) + check (res >= 1000.milliseconds) and (res <= 5000.milliseconds)