introduce asyncraises in transports/asyncsync (#470)

With these fixes, `transports`/`asyncsync` correctly propagate and document their raises information - generally, most transport functions (send etc) raise `TransportError` and `CancelledError` - `closeWait` is special in that it generally doesn't fail.

This PR introduces the syntax `Future[void].Raises([types])` to create the `InternalRaisesFuture` type with the correct encoding for the types - this allows it to be used in user code while retaining the possibility to change the internal representation down the line.

* introduce raising constraints on stream callbacks - these constraints now give a warning when called with a callback that can raise exceptions (raising callbacks would crash 
* fix fail and its tests, which wasn't always given a good generic match
* work around nim bugs related to macro expansion of generic types
* make sure transports raise only `TransportError`-derived exceptions (and `CancelledError`)
This commit is contained in:
Jacek Sieka 2023-11-15 09:38:48 +01:00 committed by GitHub
parent 24be151cf3
commit f5ff9e32ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1574 additions and 1103 deletions

View File

@ -28,7 +28,7 @@ type
## is blocked in ``acquire()`` is being processed. ## is blocked in ``acquire()`` is being processed.
locked: bool locked: bool
acquired: bool acquired: bool
waiters: seq[Future[void]] waiters: seq[Future[void].Raising([CancelledError])]
AsyncEvent* = ref object of RootRef AsyncEvent* = ref object of RootRef
## A primitive event object. ## A primitive event object.
@ -41,7 +41,7 @@ type
## state to be signaled, when event get fired, then all coroutines ## state to be signaled, when event get fired, then all coroutines
## continue proceeds in order, they have entered waiting state. ## continue proceeds in order, they have entered waiting state.
flag: bool flag: bool
waiters: seq[Future[void]] waiters: seq[Future[void].Raising([CancelledError])]
AsyncQueue*[T] = ref object of RootRef AsyncQueue*[T] = ref object of RootRef
## A queue, useful for coordinating producer and consumer coroutines. ## A queue, useful for coordinating producer and consumer coroutines.
@ -50,8 +50,8 @@ type
## infinite. If it is an integer greater than ``0``, then "await put()" ## infinite. If it is an integer greater than ``0``, then "await put()"
## will block when the queue reaches ``maxsize``, until an item is ## will block when the queue reaches ``maxsize``, until an item is
## removed by "await get()". ## removed by "await get()".
getters: seq[Future[void]] getters: seq[Future[void].Raising([CancelledError])]
putters: seq[Future[void]] putters: seq[Future[void].Raising([CancelledError])]
queue: Deque[T] queue: Deque[T]
maxsize: int maxsize: int
@ -69,7 +69,7 @@ type
EventQueueReader* = object EventQueueReader* = object
key: EventQueueKey key: EventQueueKey
offset: int offset: int
waiter: Future[void] waiter: Future[void].Raising([CancelledError])
overflow: bool overflow: bool
AsyncEventQueue*[T] = ref object of RootObj AsyncEventQueue*[T] = ref object of RootObj
@ -90,17 +90,14 @@ proc newAsyncLock*(): AsyncLock =
## The ``release()`` procedure changes the state to unlocked and returns ## The ``release()`` procedure changes the state to unlocked and returns
## immediately. ## immediately.
# Workaround for callSoon() not worked correctly before AsyncLock()
# getThreadDispatcher() call.
discard getThreadDispatcher()
AsyncLock(waiters: newSeq[Future[void]](), locked: false, acquired: false)
proc wakeUpFirst(lock: AsyncLock): bool {.inline.} = proc wakeUpFirst(lock: AsyncLock): bool {.inline.} =
## Wake up the first waiter if it isn't done. ## Wake up the first waiter if it isn't done.
var i = 0 var i = 0
var res = false var res = false
while i < len(lock.waiters): while i < len(lock.waiters):
var waiter = lock.waiters[i] let waiter = lock.waiters[i]
inc(i) inc(i)
if not(waiter.finished()): if not(waiter.finished()):
waiter.complete() waiter.complete()
@ -120,7 +117,7 @@ proc checkAll(lock: AsyncLock): bool {.inline.} =
return false return false
return true return true
proc acquire*(lock: AsyncLock) {.async.} = proc acquire*(lock: AsyncLock) {.async: (raises: [CancelledError]).} =
## Acquire a lock ``lock``. ## Acquire a lock ``lock``.
## ##
## This procedure blocks until the lock ``lock`` is unlocked, then sets it ## This procedure blocks until the lock ``lock`` is unlocked, then sets it
@ -129,7 +126,7 @@ proc acquire*(lock: AsyncLock) {.async.} =
lock.acquired = true lock.acquired = true
lock.locked = true lock.locked = true
else: else:
var w = newFuture[void]("AsyncLock.acquire") let w = Future[void].Raising([CancelledError]).init("AsyncLock.acquire")
lock.waiters.add(w) lock.waiters.add(w)
await w await w
lock.acquired = true lock.acquired = true
@ -165,13 +162,10 @@ proc newAsyncEvent*(): AsyncEvent =
## procedure and reset to `false` with the `clear()` procedure. ## procedure and reset to `false` with the `clear()` procedure.
## The `wait()` procedure blocks until the flag is `true`. The flag is ## The `wait()` procedure blocks until the flag is `true`. The flag is
## initially `false`. ## initially `false`.
AsyncEvent()
# Workaround for callSoon() not worked correctly before proc wait*(event: AsyncEvent): Future[void] {.
# getThreadDispatcher() call. async: (raw: true, raises: [CancelledError]).} =
discard getThreadDispatcher()
AsyncEvent(waiters: newSeq[Future[void]](), flag: false)
proc wait*(event: AsyncEvent): Future[void] =
## Block until the internal flag of ``event`` is `true`. ## Block until the internal flag of ``event`` is `true`.
## If the internal flag is `true` on entry, return immediately. Otherwise, ## If the internal flag is `true` on entry, return immediately. Otherwise,
## block until another task calls `fire()` to set the flag to `true`, ## block until another task calls `fire()` to set the flag to `true`,
@ -210,20 +204,15 @@ proc isSet*(event: AsyncEvent): bool =
proc newAsyncQueue*[T](maxsize: int = 0): AsyncQueue[T] = proc newAsyncQueue*[T](maxsize: int = 0): AsyncQueue[T] =
## Creates a new asynchronous queue ``AsyncQueue``. ## Creates a new asynchronous queue ``AsyncQueue``.
# Workaround for callSoon() not worked correctly before
# getThreadDispatcher() call.
discard getThreadDispatcher()
AsyncQueue[T]( AsyncQueue[T](
getters: newSeq[Future[void]](),
putters: newSeq[Future[void]](),
queue: initDeque[T](), queue: initDeque[T](),
maxsize: maxsize maxsize: maxsize
) )
proc wakeupNext(waiters: var seq[Future[void]]) {.inline.} = proc wakeupNext(waiters: var seq) {.inline.} =
var i = 0 var i = 0
while i < len(waiters): while i < len(waiters):
var waiter = waiters[i] let waiter = waiters[i]
inc(i) inc(i)
if not(waiter.finished()): if not(waiter.finished()):
@ -250,6 +239,24 @@ proc empty*[T](aq: AsyncQueue[T]): bool {.inline.} =
## Return ``true`` if the queue is empty, ``false`` otherwise. ## Return ``true`` if the queue is empty, ``false`` otherwise.
(len(aq.queue) == 0) (len(aq.queue) == 0)
proc addFirstImpl[T](aq: AsyncQueue[T], item: T) =
aq.queue.addFirst(item)
aq.getters.wakeupNext()
proc addLastImpl[T](aq: AsyncQueue[T], item: T) =
aq.queue.addLast(item)
aq.getters.wakeupNext()
proc popFirstImpl[T](aq: AsyncQueue[T]): T =
let res = aq.queue.popFirst()
aq.putters.wakeupNext()
res
proc popLastImpl[T](aq: AsyncQueue[T]): T =
let res = aq.queue.popLast()
aq.putters.wakeupNext()
res
proc addFirstNoWait*[T](aq: AsyncQueue[T], item: T) {. proc addFirstNoWait*[T](aq: AsyncQueue[T], item: T) {.
raises: [AsyncQueueFullError].}= raises: [AsyncQueueFullError].}=
## Put an item ``item`` to the beginning of the queue ``aq`` immediately. ## Put an item ``item`` to the beginning of the queue ``aq`` immediately.
@ -257,8 +264,7 @@ proc addFirstNoWait*[T](aq: AsyncQueue[T], item: T) {.
## If queue ``aq`` is full, then ``AsyncQueueFullError`` exception raised. ## If queue ``aq`` is full, then ``AsyncQueueFullError`` exception raised.
if aq.full(): if aq.full():
raise newException(AsyncQueueFullError, "AsyncQueue is full!") raise newException(AsyncQueueFullError, "AsyncQueue is full!")
aq.queue.addFirst(item) aq.addFirstImpl(item)
aq.getters.wakeupNext()
proc addLastNoWait*[T](aq: AsyncQueue[T], item: T) {. proc addLastNoWait*[T](aq: AsyncQueue[T], item: T) {.
raises: [AsyncQueueFullError].}= raises: [AsyncQueueFullError].}=
@ -267,8 +273,7 @@ proc addLastNoWait*[T](aq: AsyncQueue[T], item: T) {.
## If queue ``aq`` is full, then ``AsyncQueueFullError`` exception raised. ## If queue ``aq`` is full, then ``AsyncQueueFullError`` exception raised.
if aq.full(): if aq.full():
raise newException(AsyncQueueFullError, "AsyncQueue is full!") raise newException(AsyncQueueFullError, "AsyncQueue is full!")
aq.queue.addLast(item) aq.addLastImpl(item)
aq.getters.wakeupNext()
proc popFirstNoWait*[T](aq: AsyncQueue[T]): T {. proc popFirstNoWait*[T](aq: AsyncQueue[T]): T {.
raises: [AsyncQueueEmptyError].} = raises: [AsyncQueueEmptyError].} =
@ -277,9 +282,7 @@ proc popFirstNoWait*[T](aq: AsyncQueue[T]): T {.
## If queue ``aq`` is empty, then ``AsyncQueueEmptyError`` exception raised. ## If queue ``aq`` is empty, then ``AsyncQueueEmptyError`` exception raised.
if aq.empty(): if aq.empty():
raise newException(AsyncQueueEmptyError, "AsyncQueue is empty!") raise newException(AsyncQueueEmptyError, "AsyncQueue is empty!")
let res = aq.queue.popFirst() aq.popFirstImpl()
aq.putters.wakeupNext()
res
proc popLastNoWait*[T](aq: AsyncQueue[T]): T {. proc popLastNoWait*[T](aq: AsyncQueue[T]): T {.
raises: [AsyncQueueEmptyError].} = raises: [AsyncQueueEmptyError].} =
@ -288,65 +291,63 @@ proc popLastNoWait*[T](aq: AsyncQueue[T]): T {.
## If queue ``aq`` is empty, then ``AsyncQueueEmptyError`` exception raised. ## If queue ``aq`` is empty, then ``AsyncQueueEmptyError`` exception raised.
if aq.empty(): if aq.empty():
raise newException(AsyncQueueEmptyError, "AsyncQueue is empty!") raise newException(AsyncQueueEmptyError, "AsyncQueue is empty!")
let res = aq.queue.popLast() aq.popLastImpl()
aq.putters.wakeupNext()
res
proc addFirst*[T](aq: AsyncQueue[T], item: T) {.async.} = proc addFirst*[T](aq: AsyncQueue[T], item: T) {.async: (raises: [CancelledError]).} =
## Put an ``item`` to the beginning of the queue ``aq``. If the queue is full, ## Put an ``item`` to the beginning of the queue ``aq``. If the queue is full,
## wait until a free slot is available before adding item. ## wait until a free slot is available before adding item.
while aq.full(): while aq.full():
var putter = newFuture[void]("AsyncQueue.addFirst") let putter = Future[void].Raising([CancelledError]).init("AsyncQueue.addFirst")
aq.putters.add(putter) aq.putters.add(putter)
try: try:
await putter await putter
except CatchableError as exc: except CancelledError as exc:
if not(aq.full()) and not(putter.cancelled()): if not(aq.full()) and not(putter.cancelled()):
aq.putters.wakeupNext() aq.putters.wakeupNext()
raise exc raise exc
aq.addFirstNoWait(item) aq.addFirstImpl(item)
proc addLast*[T](aq: AsyncQueue[T], item: T) {.async.} = proc addLast*[T](aq: AsyncQueue[T], item: T) {.async: (raises: [CancelledError]).} =
## Put an ``item`` to the end of the queue ``aq``. If the queue is full, ## Put an ``item`` to the end of the queue ``aq``. If the queue is full,
## wait until a free slot is available before adding item. ## wait until a free slot is available before adding item.
while aq.full(): while aq.full():
var putter = newFuture[void]("AsyncQueue.addLast") let putter = Future[void].Raising([CancelledError]).init("AsyncQueue.addLast")
aq.putters.add(putter) aq.putters.add(putter)
try: try:
await putter await putter
except CatchableError as exc: except CancelledError as exc:
if not(aq.full()) and not(putter.cancelled()): if not(aq.full()) and not(putter.cancelled()):
aq.putters.wakeupNext() aq.putters.wakeupNext()
raise exc raise exc
aq.addLastNoWait(item) aq.addLastImpl(item)
proc popFirst*[T](aq: AsyncQueue[T]): Future[T] {.async.} = proc popFirst*[T](aq: AsyncQueue[T]): Future[T] {.async: (raises: [CancelledError]).} =
## Remove and return an ``item`` from the beginning of the queue ``aq``. ## Remove and return an ``item`` from the beginning of the queue ``aq``.
## If the queue is empty, wait until an item is available. ## If the queue is empty, wait until an item is available.
while aq.empty(): while aq.empty():
var getter = newFuture[void]("AsyncQueue.popFirst") let getter = Future[void].Raising([CancelledError]).init("AsyncQueue.popFirst")
aq.getters.add(getter) aq.getters.add(getter)
try: try:
await getter await getter
except CatchableError as exc: except CancelledError as exc:
if not(aq.empty()) and not(getter.cancelled()): if not(aq.empty()) and not(getter.cancelled()):
aq.getters.wakeupNext() aq.getters.wakeupNext()
raise exc raise exc
return aq.popFirstNoWait() aq.popFirstImpl()
proc popLast*[T](aq: AsyncQueue[T]): Future[T] {.async.} = proc popLast*[T](aq: AsyncQueue[T]): Future[T] {.async: (raises: [CancelledError]).} =
## Remove and return an ``item`` from the end of the queue ``aq``. ## Remove and return an ``item`` from the end of the queue ``aq``.
## If the queue is empty, wait until an item is available. ## If the queue is empty, wait until an item is available.
while aq.empty(): while aq.empty():
var getter = newFuture[void]("AsyncQueue.popLast") let getter = Future[void].Raising([CancelledError]).init("AsyncQueue.popLast")
aq.getters.add(getter) aq.getters.add(getter)
try: try:
await getter await getter
except CatchableError as exc: except CancelledError as exc:
if not(aq.empty()) and not(getter.cancelled()): if not(aq.empty()) and not(getter.cancelled()):
aq.getters.wakeupNext() aq.getters.wakeupNext()
raise exc raise exc
return aq.popLastNoWait() aq.popLastImpl()
proc putNoWait*[T](aq: AsyncQueue[T], item: T) {. proc putNoWait*[T](aq: AsyncQueue[T], item: T) {.
raises: [AsyncQueueFullError].} = raises: [AsyncQueueFullError].} =
@ -358,11 +359,13 @@ proc getNoWait*[T](aq: AsyncQueue[T]): T {.
## Alias of ``popFirstNoWait()``. ## Alias of ``popFirstNoWait()``.
aq.popFirstNoWait() aq.popFirstNoWait()
proc put*[T](aq: AsyncQueue[T], item: T): Future[void] {.inline.} = proc put*[T](aq: AsyncQueue[T], item: T): Future[void] {.
async: (raw: true, raises: [CancelledError]).} =
## Alias of ``addLast()``. ## Alias of ``addLast()``.
aq.addLast(item) aq.addLast(item)
proc get*[T](aq: AsyncQueue[T]): Future[T] {.inline.} = proc get*[T](aq: AsyncQueue[T]): Future[T] {.
async: (raw: true, raises: [CancelledError]).} =
## Alias of ``popFirst()``. ## Alias of ``popFirst()``.
aq.popFirst() aq.popFirst()
@ -416,7 +419,7 @@ proc contains*[T](aq: AsyncQueue[T], item: T): bool {.inline.} =
## via the ``in`` operator. ## via the ``in`` operator.
for e in aq.queue.items(): for e in aq.queue.items():
if e == item: return true if e == item: return true
return false false
proc `$`*[T](aq: AsyncQueue[T]): string = proc `$`*[T](aq: AsyncQueue[T]): string =
## Turn an async queue ``aq`` into its string representation. ## Turn an async queue ``aq`` into its string representation.
@ -452,8 +455,7 @@ proc compact(ab: AsyncEventQueue) {.raises: [].} =
else: else:
ab.queue.clear() ab.queue.clear()
proc getReaderIndex(ab: AsyncEventQueue, key: EventQueueKey): int {. proc getReaderIndex(ab: AsyncEventQueue, key: EventQueueKey): int =
raises: [].} =
for index, value in ab.readers.pairs(): for index, value in ab.readers.pairs():
if value.key == key: if value.key == key:
return index return index
@ -507,7 +509,7 @@ proc close*(ab: AsyncEventQueue) {.raises: [].} =
ab.readers.reset() ab.readers.reset()
ab.queue.clear() ab.queue.clear()
proc closeWait*(ab: AsyncEventQueue): Future[void] {.raises: [].} = proc closeWait*(ab: AsyncEventQueue): Future[void] {.async: (raw: true, raises: []).} =
let retFuture = newFuture[void]("AsyncEventQueue.closeWait()", let retFuture = newFuture[void]("AsyncEventQueue.closeWait()",
{FutureFlag.OwnCancelSchedule}) {FutureFlag.OwnCancelSchedule})
proc continuation(udata: pointer) {.gcsafe.} = proc continuation(udata: pointer) {.gcsafe.} =
@ -528,7 +530,7 @@ template readerOverflow*(ab: AsyncEventQueue,
reader: EventQueueReader): bool = reader: EventQueueReader): bool =
ab.limit + (reader.offset - ab.offset) <= len(ab.queue) ab.limit + (reader.offset - ab.offset) <= len(ab.queue)
proc emit*[T](ab: AsyncEventQueue[T], data: T) {.raises: [].} = proc emit*[T](ab: AsyncEventQueue[T], data: T) =
if len(ab.readers) > 0: if len(ab.readers) > 0:
# We enqueue `data` only if there active reader present. # We enqueue `data` only if there active reader present.
var changesPresent = false var changesPresent = false
@ -565,7 +567,8 @@ proc emit*[T](ab: AsyncEventQueue[T], data: T) {.raises: [].} =
proc waitEvents*[T](ab: AsyncEventQueue[T], proc waitEvents*[T](ab: AsyncEventQueue[T],
key: EventQueueKey, key: EventQueueKey,
eventsCount = -1): Future[seq[T]] {.async.} = eventsCount = -1): Future[seq[T]] {.
async: (raises: [AsyncEventQueueFullError, CancelledError]).} =
## Wait for events ## Wait for events
var var
events: seq[T] events: seq[T]
@ -595,7 +598,8 @@ proc waitEvents*[T](ab: AsyncEventQueue[T],
doAssert(length >= ab.readers[index].offset) doAssert(length >= ab.readers[index].offset)
if length == ab.readers[index].offset: if length == ab.readers[index].offset:
# We are at the end of queue, it means that we should wait for new events. # We are at the end of queue, it means that we should wait for new events.
let waitFuture = newFuture[void]("AsyncEventQueue.waitEvents") let waitFuture = Future[void].Raising([CancelledError]).init(
"AsyncEventQueue.waitEvents")
ab.readers[index].waiter = waitFuture ab.readers[index].waiter = waitFuture
resetFuture = true resetFuture = true
await waitFuture await waitFuture
@ -626,4 +630,4 @@ proc waitEvents*[T](ab: AsyncEventQueue[T],
if (eventsCount <= 0) or (len(events) == eventsCount): if (eventsCount <= 0) or (len(events) == eventsCount):
break break
return events events

View File

@ -16,7 +16,9 @@ import stew/base10
import ./[asyncengine, raisesfutures] import ./[asyncengine, raisesfutures]
import ../[config, futures] import ../[config, futures]
export raisesfutures.InternalRaisesFuture export
raisesfutures.Raising, raisesfutures.InternalRaisesFuture,
raisesfutures.init, raisesfutures.error, raisesfutures.readError
when chronosStackTrace: when chronosStackTrace:
import std/strutils import std/strutils
@ -109,7 +111,7 @@ template newInternalRaisesFuture*[T, E](fromProc: static[string] = ""): auto =
## that this future belongs to, is a good habit as it helps with debugging. ## that this future belongs to, is a good habit as it helps with debugging.
newInternalRaisesFutureImpl[T, E](getSrcLocation(fromProc)) newInternalRaisesFutureImpl[T, E](getSrcLocation(fromProc))
template newFutureSeq*[A, B](fromProc: static[string] = ""): FutureSeq[A, B] = template newFutureSeq*[A, B](fromProc: static[string] = ""): FutureSeq[A, B] {.deprecated.} =
## Create a new future which can hold/preserve GC sequence until future will ## Create a new future which can hold/preserve GC sequence until future will
## not be completed. ## not be completed.
## ##
@ -117,7 +119,7 @@ template newFutureSeq*[A, B](fromProc: static[string] = ""): FutureSeq[A, B] =
## that this future belongs to, is a good habit as it helps with debugging. ## that this future belongs to, is a good habit as it helps with debugging.
newFutureSeqImpl[A, B](getSrcLocation(fromProc)) newFutureSeqImpl[A, B](getSrcLocation(fromProc))
template newFutureStr*[T](fromProc: static[string] = ""): FutureStr[T] = template newFutureStr*[T](fromProc: static[string] = ""): FutureStr[T] {.deprecated.} =
## Create a new future which can hold/preserve GC string until future will ## Create a new future which can hold/preserve GC string until future will
## not be completed. ## not be completed.
## ##
@ -205,7 +207,8 @@ template complete*(future: Future[void]) =
## Completes a void ``future``. ## Completes a void ``future``.
complete(future, getSrcLocation()) complete(future, getSrcLocation())
proc fail(future: FutureBase, error: ref CatchableError, loc: ptr SrcLoc) = proc failImpl(
future: FutureBase, error: ref CatchableError, loc: ptr SrcLoc) =
if not(future.cancelled()): if not(future.cancelled()):
checkFinished(future, loc) checkFinished(future, loc)
future.internalError = error future.internalError = error
@ -216,10 +219,16 @@ proc fail(future: FutureBase, error: ref CatchableError, loc: ptr SrcLoc) =
getStackTrace(error) getStackTrace(error)
future.finish(FutureState.Failed) future.finish(FutureState.Failed)
template fail*( template fail*[T](
future: FutureBase, error: ref CatchableError, warn: static bool = false) = future: Future[T], error: ref CatchableError, warn: static bool = false) =
## Completes ``future`` with ``error``. ## Completes ``future`` with ``error``.
fail(future, error, getSrcLocation()) failImpl(future, error, getSrcLocation())
template fail*[T, E](
future: InternalRaisesFuture[T, E], error: ref CatchableError,
warn: static bool = true) =
checkRaises(future, E, error, warn)
failImpl(future, error, getSrcLocation())
template newCancelledError(): ref CancelledError = template newCancelledError(): ref CancelledError =
(ref CancelledError)(msg: "Future operation cancelled!") (ref CancelledError)(msg: "Future operation cancelled!")
@ -377,8 +386,6 @@ proc futureContinue*(fut: FutureBase) {.raises: [], gcsafe.} =
{.pop.} {.pop.}
when chronosStackTrace: when chronosStackTrace:
import std/strutils
template getFilenameProcname(entry: StackTraceEntry): (string, string) = template getFilenameProcname(entry: StackTraceEntry): (string, string) =
when compiles(entry.filenameStr) and compiles(entry.procnameStr): when compiles(entry.filenameStr) and compiles(entry.procnameStr):
# We can't rely on "entry.filename" and "entry.procname" still being valid # We can't rely on "entry.filename" and "entry.procname" still being valid
@ -462,31 +469,36 @@ proc internalCheckComplete*(fut: FutureBase) {.raises: [CatchableError].} =
injectStacktrace(fut.internalError) injectStacktrace(fut.internalError)
raise fut.internalError raise fut.internalError
macro internalCheckComplete*(f: InternalRaisesFuture): untyped = macro internalCheckComplete*(fut: InternalRaisesFuture, raises: typed) =
# For InternalRaisesFuture[void, (ValueError, OSError), will do: # For InternalRaisesFuture[void, (ValueError, OSError), will do:
# {.cast(raises: [ValueError, OSError]).}: # {.cast(raises: [ValueError, OSError]).}:
# if isNil(f.error): discard # if isNil(f.error): discard
# else: raise f.error # else: raise f.error
let e = getTypeInst(f)[2] # TODO https://github.com/nim-lang/Nim/issues/22937
let types = getType(e) # we cannot `getTypeInst` on the `fut` - when aliases are involved, the
# generics are lost - so instead, we pass the raises list explicitly
let types = getRaisesTypes(raises)
if isNoRaises(types): if isNoRaises(types):
return quote do: return quote do:
if not(isNil(`f`.internalError)): if not(isNil(`fut`.internalError)):
raiseAssert("Unhandled future exception: " & `f`.error.msg) # This would indicate a bug in which `error` was set via the non-raising
# base type
raiseAssert("Error set on a non-raising future: " & `fut`.internalError.msg)
expectKind(types, nnkBracketExpr) expectKind(types, nnkBracketExpr)
expectKind(types[0], nnkSym) expectKind(types[0], nnkSym)
assert types[0].strVal == "tuple" assert types[0].strVal == "tuple"
let ifRaise = nnkIfExpr.newTree( let ifRaise = nnkIfExpr.newTree(
nnkElifExpr.newTree( nnkElifExpr.newTree(
quote do: isNil(`f`.internalError), quote do: isNil(`fut`.internalError),
quote do: discard quote do: discard
), ),
nnkElseExpr.newTree( nnkElseExpr.newTree(
nnkRaiseStmt.newNimNode(lineInfoFrom=f).add( nnkRaiseStmt.newNimNode(lineInfoFrom=fut).add(
quote do: (`f`.internalError) quote do: (`fut`.internalError)
) )
) )
) )
@ -1118,7 +1130,7 @@ proc one*[F: SomeFuture](futs: varargs[F]): Future[F] {.
return retFuture return retFuture
proc race*(futs: varargs[FutureBase]): Future[FutureBase] {. proc race*(futs: varargs[FutureBase]): Future[FutureBase] {.
async: (raw: true, raises: [CancelledError]).} = async: (raw: true, raises: [ValueError, CancelledError]).} =
## Returns a future which will complete and return completed FutureBase, ## Returns a future which will complete and return completed FutureBase,
## when one of the futures in ``futs`` will be completed, failed or canceled. ## when one of the futures in ``futs`` will be completed, failed or canceled.
## ##
@ -1488,12 +1500,6 @@ when defined(windows):
{.pop.} # Automatically deduced raises from here onwards {.pop.} # Automatically deduced raises from here onwards
template fail*[T, E](
future: InternalRaisesFuture[T, E], error: ref CatchableError,
warn: static bool = true) =
checkRaises(future, error, warn)
fail(future, error, getSrcLocation())
proc waitFor*[T, E](fut: InternalRaisesFuture[T, E]): T = # {.raises: [E]} proc waitFor*[T, E](fut: InternalRaisesFuture[T, E]): T = # {.raises: [E]}
## **Blocks** the current thread until the specified future finishes and ## **Blocks** the current thread until the specified future finishes and
## reads it, potentially raising an exception if the future failed or was ## reads it, potentially raising an exception if the future failed or was
@ -1512,7 +1518,7 @@ proc read*[T: not void, E](future: InternalRaisesFuture[T, E]): lent T = # {.rai
# TODO: Make a custom exception type for this? # TODO: Make a custom exception type for this?
raise newException(ValueError, "Future still in progress.") raise newException(ValueError, "Future still in progress.")
internalCheckComplete(future) internalCheckComplete(future, E)
future.internalValue future.internalValue
proc read*[E](future: InternalRaisesFuture[void, E]) = # {.raises: [E, CancelledError].} proc read*[E](future: InternalRaisesFuture[void, E]) = # {.raises: [E, CancelledError].}

View File

@ -497,7 +497,7 @@ proc asyncSingleProc(prc, params: NimNode): NimNode {.compileTime.} =
prc prc
template await*[T](f: Future[T]): untyped = template await*[T](f: Future[T]): T =
when declared(chronosInternalRetFuture): when declared(chronosInternalRetFuture):
chronosInternalRetFuture.internalChild = f chronosInternalRetFuture.internalChild = f
# `futureContinue` calls the iterator generated by the `async` # `futureContinue` calls the iterator generated by the `async`
@ -512,6 +512,21 @@ template await*[T](f: Future[T]): untyped =
else: else:
unsupported "await is only available within {.async.}" unsupported "await is only available within {.async.}"
template await*[T, E](f: InternalRaisesFuture[T, E]): T =
when declared(chronosInternalRetFuture):
chronosInternalRetFuture.internalChild = f
# `futureContinue` calls the iterator generated by the `async`
# transformation - `yield` gives control back to `futureContinue` which is
# responsible for resuming execution once the yielded future is finished
yield chronosInternalRetFuture.internalChild
# `child` released by `futureContinue`
cast[type(f)](chronosInternalRetFuture.internalChild).internalCheckComplete(E)
when T isnot void:
cast[type(f)](chronosInternalRetFuture.internalChild).value()
else:
unsupported "await is only available within {.async.}"
template awaitne*[T](f: Future[T]): Future[T] = template awaitne*[T](f: Future[T]): Future[T] =
when declared(chronosInternalRetFuture): when declared(chronosInternalRetFuture):
chronosInternalRetFuture.internalChild = f chronosInternalRetFuture.internalChild = f

View File

@ -1,5 +1,5 @@
import import
std/macros, std/[macros, sequtils],
../futures ../futures
type type
@ -18,6 +18,45 @@ proc makeNoRaises*(): NimNode {.compileTime.} =
ident"void" ident"void"
macro Raising*[T](F: typedesc[Future[T]], E: varargs[typedesc]): untyped =
## Given a Future type instance, return a type storing `{.raises.}`
## information
##
## Note; this type may change in the future
E.expectKind(nnkBracket)
let raises = if E.len == 0:
makeNoRaises()
else:
nnkTupleConstr.newTree(E.mapIt(it))
nnkBracketExpr.newTree(
ident "InternalRaisesFuture",
nnkDotExpr.newTree(F, ident"T"),
raises
)
template init*[T, E](
F: type InternalRaisesFuture[T, E], fromProc: static[string] = ""): F =
## Creates a new pending future.
##
## Specifying ``fromProc``, which is a string specifying the name of the proc
## that this future belongs to, is a good habit as it helps with debugging.
let res = F()
internalInitFutureBase(res, getSrcLocation(fromProc), FutureState.Pending, {})
res
template init*[T, E](
F: type InternalRaisesFuture[T, E], fromProc: static[string] = "",
flags: static[FutureFlags]): F =
## Creates a new pending future.
##
## Specifying ``fromProc``, which is a string specifying the name of the proc
## that this future belongs to, is a good habit as it helps with debugging.
let res = F()
internalInitFutureBase(
res, getSrcLocation(fromProc), FutureState.Pending, flags)
res
proc isNoRaises*(n: NimNode): bool {.compileTime.} = proc isNoRaises*(n: NimNode): bool {.compileTime.} =
n.eqIdent("void") n.eqIdent("void")
@ -78,21 +117,15 @@ macro union*(tup0: typedesc[tuple], tup1: typedesc[tuple]): typedesc =
if result.len == 0: if result.len == 0:
result = makeNoRaises() result = makeNoRaises()
proc getRaises*(future: NimNode): NimNode {.compileTime.} = proc getRaisesTypes*(raises: NimNode): NimNode =
# Given InternalRaisesFuture[T, (A, B, C)], returns (A, B, C) let typ = getType(raises)
let types = getType(getTypeInst(future)[2]) case typ.typeKind
if isNoRaises(types): of ntyTypeDesc: typ[1]
nnkBracketExpr.newTree(newEmptyNode()) else: typ
else:
expectKind(types, nnkBracketExpr)
expectKind(types[0], nnkSym)
assert types[0].strVal == "tuple"
assert types.len >= 1
types
macro checkRaises*[T: CatchableError]( macro checkRaises*[T: CatchableError](
future: InternalRaisesFuture, error: ref T, warn: static bool = true): untyped = future: InternalRaisesFuture, raises: typed, error: ref T,
warn: static bool = true): untyped =
## Generate code that checks that the given error is compatible with the ## Generate code that checks that the given error is compatible with the
## raises restrictions of `future`. ## raises restrictions of `future`.
## ##
@ -100,11 +133,18 @@ macro checkRaises*[T: CatchableError](
## information available at compile time - in particular, if the raises ## information available at compile time - in particular, if the raises
## inherit from `error`, we end up with the equivalent of a downcast which ## inherit from `error`, we end up with the equivalent of a downcast which
## raises a Defect if it fails. ## raises a Defect if it fails.
let raises = getRaises(future) let
raises = getRaisesTypes(raises)
expectKind(getTypeInst(error), nnkRefTy) expectKind(getTypeInst(error), nnkRefTy)
let toMatch = getTypeInst(error)[0] let toMatch = getTypeInst(error)[0]
if isNoRaises(raises):
error(
"`fail`: `" & repr(toMatch) & "` incompatible with `raises: []`", future)
return
var var
typeChecker = ident"false" typeChecker = ident"false"
maybeChecker = ident"false" maybeChecker = ident"false"
@ -134,3 +174,15 @@ macro checkRaises*[T: CatchableError](
else: else:
`warning` `warning`
assert(`runtimeChecker`, `errorMsg`) assert(`runtimeChecker`, `errorMsg`)
proc error*[T](future: InternalRaisesFuture[T, void]): ref CatchableError {.
raises: [].} =
static:
warning("No exceptions possible with this operation, `error` always returns nil")
nil
proc readError*[T](future: InternalRaisesFuture[T, void]): ref CatchableError {.
raises: [ValueError].} =
static:
warning("No exceptions possible with this operation, `readError` always raises")
raise newException(ValueError, "No error in future.")

View File

@ -113,6 +113,8 @@ type
## Transport's capability not supported exception ## Transport's capability not supported exception
TransportUseClosedError* = object of TransportError TransportUseClosedError* = object of TransportError
## Usage after transport close exception ## Usage after transport close exception
TransportUseEofError* = object of TransportError
## Usage after transport half-close exception
TransportTooManyError* = object of TransportError TransportTooManyError* = object of TransportError
## Too many open file descriptors exception ## Too many open file descriptors exception
TransportAbortedError* = object of TransportError TransportAbortedError* = object of TransportError
@ -567,11 +569,11 @@ template checkClosed*(t: untyped, future: untyped) =
template checkWriteEof*(t: untyped, future: untyped) = template checkWriteEof*(t: untyped, future: untyped) =
if (WriteEof in (t).state): if (WriteEof in (t).state):
future.fail(newException(TransportError, future.fail(newException(TransportUseEofError,
"Transport connection is already dropped!")) "Transport connection is already dropped!"))
return future return future
template getError*(t: untyped): ref CatchableError = template getError*(t: untyped): ref TransportError =
var err = (t).error var err = (t).error
(t).error = nil (t).error = nil
err err

View File

@ -27,7 +27,10 @@ type
DatagramCallback* = proc(transp: DatagramTransport, DatagramCallback* = proc(transp: DatagramTransport,
remote: TransportAddress): Future[void] {. remote: TransportAddress): Future[void] {.
gcsafe, raises: [].} async: (raises: []).}
UnsafeDatagramCallback* = proc(transp: DatagramTransport,
remote: TransportAddress): Future[void] {.async.}
DatagramTransport* = ref object of RootRef DatagramTransport* = ref object of RootRef
fd*: AsyncFD # File descriptor fd*: AsyncFD # File descriptor
@ -35,7 +38,7 @@ type
flags: set[ServerFlags] # Flags flags: set[ServerFlags] # Flags
buffer: seq[byte] # Reading buffer buffer: seq[byte] # Reading buffer
buflen: int # Reading buffer effective size buflen: int # Reading buffer effective size
error: ref CatchableError # Current error error: ref TransportError # Current error
queue: Deque[GramVector] # Writer queue queue: Deque[GramVector] # Writer queue
local: TransportAddress # Local address local: TransportAddress # Local address
remote: TransportAddress # Remote address remote: TransportAddress # Remote address
@ -599,6 +602,41 @@ proc close*(transp: DatagramTransport) =
transp.state.incl({WriteClosed, ReadClosed}) transp.state.incl({WriteClosed, ReadClosed})
closeSocket(transp.fd, continuation) closeSocket(transp.fd, continuation)
proc newDatagramTransportCommon(cbproc: UnsafeDatagramCallback,
remote: TransportAddress,
local: TransportAddress,
sock: AsyncFD,
flags: set[ServerFlags],
udata: pointer,
child: DatagramTransport,
bufferSize: int,
ttl: int,
dualstack = DualStackType.Auto
): DatagramTransport {.
raises: [TransportOsError].} =
## Create new UDP datagram transport (IPv4).
##
## ``cbproc`` - callback which will be called, when new datagram received.
## ``remote`` - bind transport to remote address (optional).
## ``local`` - bind transport to local address (to serving incoming
## datagrams, optional)
## ``sock`` - application-driven socket to use.
## ``flags`` - flags that will be applied to socket.
## ``udata`` - custom argument which will be passed to ``cbproc``.
## ``bufSize`` - size of internal buffer.
## ``ttl`` - TTL for UDP datagram packet (only usable when flags has
## ``Broadcast`` option).
proc wrap(transp: DatagramTransport,
remote: TransportAddress) {.async: (raises: []).} =
try:
cbproc(transp, remote)
except CatchableError as exc:
raiseAssert "Unexpected exception from stream server cbproc: " & exc.msg
newDatagramTransportCommon(wrap, remote, local, sock, flags, udata, child,
bufferSize, ttl, dualstack)
proc newDatagramTransport*(cbproc: DatagramCallback, proc newDatagramTransport*(cbproc: DatagramCallback,
remote: TransportAddress = AnyAddress, remote: TransportAddress = AnyAddress,
local: TransportAddress = AnyAddress, local: TransportAddress = AnyAddress,
@ -689,7 +727,102 @@ proc newDatagramTransport6*[T](cbproc: DatagramCallback,
cast[pointer](udata), child, bufSize, ttl, cast[pointer](udata), child, bufSize, ttl,
dualstack) dualstack)
proc join*(transp: DatagramTransport): Future[void] = proc newDatagramTransport*(cbproc: UnsafeDatagramCallback,
remote: TransportAddress = AnyAddress,
local: TransportAddress = AnyAddress,
sock: AsyncFD = asyncInvalidSocket,
flags: set[ServerFlags] = {},
udata: pointer = nil,
child: DatagramTransport = nil,
bufSize: int = DefaultDatagramBufferSize,
ttl: int = 0,
dualstack = DualStackType.Auto
): DatagramTransport {.
raises: [TransportOsError],
deprecated: "Callback must not raise exceptions, annotate with {.async: (raises: []).}".} =
## Create new UDP datagram transport (IPv4).
##
## ``cbproc`` - callback which will be called, when new datagram received.
## ``remote`` - bind transport to remote address (optional).
## ``local`` - bind transport to local address (to serving incoming
## datagrams, optional)
## ``sock`` - application-driven socket to use.
## ``flags`` - flags that will be applied to socket.
## ``udata`` - custom argument which will be passed to ``cbproc``.
## ``bufSize`` - size of internal buffer.
## ``ttl`` - TTL for UDP datagram packet (only usable when flags has
## ``Broadcast`` option).
newDatagramTransportCommon(cbproc, remote, local, sock, flags, udata, child,
bufSize, ttl, dualstack)
proc newDatagramTransport*[T](cbproc: UnsafeDatagramCallback,
udata: ref T,
remote: TransportAddress = AnyAddress,
local: TransportAddress = AnyAddress,
sock: AsyncFD = asyncInvalidSocket,
flags: set[ServerFlags] = {},
child: DatagramTransport = nil,
bufSize: int = DefaultDatagramBufferSize,
ttl: int = 0,
dualstack = DualStackType.Auto
): DatagramTransport {.
raises: [TransportOsError],
deprecated: "Callback must not raise exceptions, annotate with {.async: (raises: []).}".} =
var fflags = flags + {GCUserData}
GC_ref(udata)
newDatagramTransportCommon(cbproc, remote, local, sock, fflags,
cast[pointer](udata), child, bufSize, ttl,
dualstack)
proc newDatagramTransport6*(cbproc: UnsafeDatagramCallback,
remote: TransportAddress = AnyAddress6,
local: TransportAddress = AnyAddress6,
sock: AsyncFD = asyncInvalidSocket,
flags: set[ServerFlags] = {},
udata: pointer = nil,
child: DatagramTransport = nil,
bufSize: int = DefaultDatagramBufferSize,
ttl: int = 0,
dualstack = DualStackType.Auto
): DatagramTransport {.
raises: [TransportOsError],
deprecated: "Callback must not raise exceptions, annotate with {.async: (raises: []).}".} =
## Create new UDP datagram transport (IPv6).
##
## ``cbproc`` - callback which will be called, when new datagram received.
## ``remote`` - bind transport to remote address (optional).
## ``local`` - bind transport to local address (to serving incoming
## datagrams, optional)
## ``sock`` - application-driven socket to use.
## ``flags`` - flags that will be applied to socket.
## ``udata`` - custom argument which will be passed to ``cbproc``.
## ``bufSize`` - size of internal buffer.
## ``ttl`` - TTL for UDP datagram packet (only usable when flags has
## ``Broadcast`` option).
newDatagramTransportCommon(cbproc, remote, local, sock, flags, udata, child,
bufSize, ttl, dualstack)
proc newDatagramTransport6*[T](cbproc: UnsafeDatagramCallback,
udata: ref T,
remote: TransportAddress = AnyAddress6,
local: TransportAddress = AnyAddress6,
sock: AsyncFD = asyncInvalidSocket,
flags: set[ServerFlags] = {},
child: DatagramTransport = nil,
bufSize: int = DefaultDatagramBufferSize,
ttl: int = 0,
dualstack = DualStackType.Auto
): DatagramTransport {.
raises: [TransportOsError],
deprecated: "Callback must not raise exceptions, annotate with {.async: (raises: []).}".} =
var fflags = flags + {GCUserData}
GC_ref(udata)
newDatagramTransportCommon(cbproc, remote, local, sock, fflags,
cast[pointer](udata), child, bufSize, ttl,
dualstack)
proc join*(transp: DatagramTransport): Future[void] {.
async: (raw: true, raises: [CancelledError]).} =
## Wait until the transport ``transp`` will be closed. ## Wait until the transport ``transp`` will be closed.
var retFuture = newFuture[void]("datagram.transport.join") var retFuture = newFuture[void]("datagram.transport.join")
@ -707,14 +840,15 @@ proc join*(transp: DatagramTransport): Future[void] =
return retFuture return retFuture
proc closeWait*(transp: DatagramTransport): Future[void] = proc closeWait*(transp: DatagramTransport): Future[void] {.
async: (raw: true, raises: []).} =
## Close transport ``transp`` and release all resources. ## Close transport ``transp`` and release all resources.
const FutureName = "datagram.transport.closeWait" let retFuture = newFuture[void](
"datagram.transport.closeWait", {FutureFlag.OwnCancelSchedule})
if {ReadClosed, WriteClosed} * transp.state != {}: if {ReadClosed, WriteClosed} * transp.state != {}:
return Future.completed(FutureName) retFuture.complete()
return retFuture
let retFuture = newFuture[void](FutureName, {FutureFlag.OwnCancelSchedule})
proc continuation(udata: pointer) {.gcsafe.} = proc continuation(udata: pointer) {.gcsafe.} =
retFuture.complete() retFuture.complete()
@ -733,7 +867,8 @@ proc closeWait*(transp: DatagramTransport): Future[void] =
retFuture retFuture
proc send*(transp: DatagramTransport, pbytes: pointer, proc send*(transp: DatagramTransport, pbytes: pointer,
nbytes: int): Future[void] = nbytes: int): Future[void] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
## Send buffer with pointer ``pbytes`` and size ``nbytes`` using transport ## Send buffer with pointer ``pbytes`` and size ``nbytes`` using transport
## ``transp`` to remote destination address which was bounded on transport. ## ``transp`` to remote destination address which was bounded on transport.
var retFuture = newFuture[void]("datagram.transport.send(pointer)") var retFuture = newFuture[void]("datagram.transport.send(pointer)")
@ -751,22 +886,21 @@ proc send*(transp: DatagramTransport, pbytes: pointer,
return retFuture return retFuture
proc send*(transp: DatagramTransport, msg: sink string, proc send*(transp: DatagramTransport, msg: sink string,
msglen = -1): Future[void] = msglen = -1): Future[void] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
## Send string ``msg`` using transport ``transp`` to remote destination ## Send string ``msg`` using transport ``transp`` to remote destination
## address which was bounded on transport. ## address which was bounded on transport.
var retFuture = newFutureStr[void]("datagram.transport.send(string)") var retFuture = newFuture[void]("datagram.transport.send(string)")
transp.checkClosed(retFuture) transp.checkClosed(retFuture)
when declared(shallowCopy):
if not(isLiteral(msg)):
shallowCopy(retFuture.gcholder, msg)
else:
retFuture.gcholder = msg
else:
retFuture.gcholder = msg
let length = if msglen <= 0: len(msg) else: msglen let length = if msglen <= 0: len(msg) else: msglen
let vector = GramVector(kind: WithoutAddress, buf: addr retFuture.gcholder[0], var localCopy = msg
retFuture.addCallback(proc(_: pointer) = reset(localCopy))
let vector = GramVector(kind: WithoutAddress, buf: addr localCopy[0],
buflen: length, buflen: length,
writer: cast[Future[void]](retFuture)) writer: retFuture)
transp.queue.addLast(vector) transp.queue.addLast(vector)
if WritePaused in transp.state: if WritePaused in transp.state:
let wres = transp.resumeWrite() let wres = transp.resumeWrite()
@ -775,22 +909,20 @@ proc send*(transp: DatagramTransport, msg: sink string,
return retFuture return retFuture
proc send*[T](transp: DatagramTransport, msg: sink seq[T], proc send*[T](transp: DatagramTransport, msg: sink seq[T],
msglen = -1): Future[void] = msglen = -1): Future[void] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
## Send string ``msg`` using transport ``transp`` to remote destination ## Send string ``msg`` using transport ``transp`` to remote destination
## address which was bounded on transport. ## address which was bounded on transport.
var retFuture = newFutureSeq[void, T]("datagram.transport.send(seq)") var retFuture = newFuture[void]("datagram.transport.send(seq)")
transp.checkClosed(retFuture) transp.checkClosed(retFuture)
when declared(shallowCopy):
if not(isLiteral(msg)):
shallowCopy(retFuture.gcholder, msg)
else:
retFuture.gcholder = msg
else:
retFuture.gcholder = msg
let length = if msglen <= 0: (len(msg) * sizeof(T)) else: (msglen * sizeof(T)) let length = if msglen <= 0: (len(msg) * sizeof(T)) else: (msglen * sizeof(T))
let vector = GramVector(kind: WithoutAddress, buf: addr retFuture.gcholder[0], var localCopy = msg
retFuture.addCallback(proc(_: pointer) = reset(localCopy))
let vector = GramVector(kind: WithoutAddress, buf: addr localCopy[0],
buflen: length, buflen: length,
writer: cast[Future[void]](retFuture)) writer: retFuture)
transp.queue.addLast(vector) transp.queue.addLast(vector)
if WritePaused in transp.state: if WritePaused in transp.state:
let wres = transp.resumeWrite() let wres = transp.resumeWrite()
@ -799,7 +931,8 @@ proc send*[T](transp: DatagramTransport, msg: sink seq[T],
return retFuture return retFuture
proc sendTo*(transp: DatagramTransport, remote: TransportAddress, proc sendTo*(transp: DatagramTransport, remote: TransportAddress,
pbytes: pointer, nbytes: int): Future[void] = pbytes: pointer, nbytes: int): Future[void] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
## Send buffer with pointer ``pbytes`` and size ``nbytes`` using transport ## Send buffer with pointer ``pbytes`` and size ``nbytes`` using transport
## ``transp`` to remote destination address ``remote``. ## ``transp`` to remote destination address ``remote``.
var retFuture = newFuture[void]("datagram.transport.sendTo(pointer)") var retFuture = newFuture[void]("datagram.transport.sendTo(pointer)")
@ -814,22 +947,20 @@ proc sendTo*(transp: DatagramTransport, remote: TransportAddress,
return retFuture return retFuture
proc sendTo*(transp: DatagramTransport, remote: TransportAddress, proc sendTo*(transp: DatagramTransport, remote: TransportAddress,
msg: sink string, msglen = -1): Future[void] = msg: sink string, msglen = -1): Future[void] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
## Send string ``msg`` using transport ``transp`` to remote destination ## Send string ``msg`` using transport ``transp`` to remote destination
## address ``remote``. ## address ``remote``.
var retFuture = newFutureStr[void]("datagram.transport.sendTo(string)") var retFuture = newFuture[void]("datagram.transport.sendTo(string)")
transp.checkClosed(retFuture) transp.checkClosed(retFuture)
when declared(shallowCopy):
if not(isLiteral(msg)):
shallowCopy(retFuture.gcholder, msg)
else:
retFuture.gcholder = msg
else:
retFuture.gcholder = msg
let length = if msglen <= 0: len(msg) else: msglen let length = if msglen <= 0: len(msg) else: msglen
let vector = GramVector(kind: WithAddress, buf: addr retFuture.gcholder[0], var localCopy = msg
retFuture.addCallback(proc(_: pointer) = reset(localCopy))
let vector = GramVector(kind: WithAddress, buf: addr localCopy[0],
buflen: length, buflen: length,
writer: cast[Future[void]](retFuture), writer: retFuture,
address: remote) address: remote)
transp.queue.addLast(vector) transp.queue.addLast(vector)
if WritePaused in transp.state: if WritePaused in transp.state:
@ -839,20 +970,17 @@ proc sendTo*(transp: DatagramTransport, remote: TransportAddress,
return retFuture return retFuture
proc sendTo*[T](transp: DatagramTransport, remote: TransportAddress, proc sendTo*[T](transp: DatagramTransport, remote: TransportAddress,
msg: sink seq[T], msglen = -1): Future[void] = msg: sink seq[T], msglen = -1): Future[void] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
## Send sequence ``msg`` using transport ``transp`` to remote destination ## Send sequence ``msg`` using transport ``transp`` to remote destination
## address ``remote``. ## address ``remote``.
var retFuture = newFutureSeq[void, T]("datagram.transport.sendTo(seq)") var retFuture = newFuture[void]("datagram.transport.sendTo(seq)")
transp.checkClosed(retFuture) transp.checkClosed(retFuture)
when declared(shallowCopy):
if not(isLiteral(msg)):
shallowCopy(retFuture.gcholder, msg)
else:
retFuture.gcholder = msg
else:
retFuture.gcholder = msg
let length = if msglen <= 0: (len(msg) * sizeof(T)) else: (msglen * sizeof(T)) let length = if msglen <= 0: (len(msg) * sizeof(T)) else: (msglen * sizeof(T))
let vector = GramVector(kind: WithAddress, buf: addr retFuture.gcholder[0], var localCopy = msg
retFuture.addCallback(proc(_: pointer) = reset(localCopy))
let vector = GramVector(kind: WithAddress, buf: addr localCopy[0],
buflen: length, buflen: length,
writer: cast[Future[void]](retFuture), writer: cast[Future[void]](retFuture),
address: remote) address: remote)
@ -864,7 +992,7 @@ proc sendTo*[T](transp: DatagramTransport, remote: TransportAddress,
return retFuture return retFuture
proc peekMessage*(transp: DatagramTransport, msg: var seq[byte], proc peekMessage*(transp: DatagramTransport, msg: var seq[byte],
msglen: var int) {.raises: [CatchableError].} = msglen: var int) {.raises: [TransportError].} =
## Get access to internal message buffer and length of incoming datagram. ## Get access to internal message buffer and length of incoming datagram.
if ReadError in transp.state: if ReadError in transp.state:
transp.state.excl(ReadError) transp.state.excl(ReadError)
@ -876,7 +1004,7 @@ proc peekMessage*(transp: DatagramTransport, msg: var seq[byte],
msglen = transp.buflen msglen = transp.buflen
proc getMessage*(transp: DatagramTransport): seq[byte] {. proc getMessage*(transp: DatagramTransport): seq[byte] {.
raises: [CatchableError].} = raises: [TransportError].} =
## Copy data from internal message buffer and return result. ## Copy data from internal message buffer and return result.
var default: seq[byte] var default: seq[byte]
if ReadError in transp.state: if ReadError in transp.state:

View File

@ -58,6 +58,8 @@ type
done: bool] {. done: bool] {.
gcsafe, raises: [].} gcsafe, raises: [].}
ReaderFuture = Future[void].Raising([TransportError, CancelledError])
const const
StreamTransportTrackerName* = "stream.transport" StreamTransportTrackerName* = "stream.transport"
StreamServerTrackerName* = "stream.server" StreamServerTrackerName* = "stream.server"
@ -68,10 +70,10 @@ when defined(windows):
StreamTransport* = ref object of RootRef StreamTransport* = ref object of RootRef
fd*: AsyncFD # File descriptor fd*: AsyncFD # File descriptor
state: set[TransportState] # Current Transport state state: set[TransportState] # Current Transport state
reader: Future[void] # Current reader Future reader: ReaderFuture # Current reader Future
buffer: seq[byte] # Reading buffer buffer: seq[byte] # Reading buffer
offset: int # Reading buffer offset offset: int # Reading buffer offset
error: ref CatchableError # Current error error: ref TransportError # Current error
queue: Deque[StreamVector] # Writer queue queue: Deque[StreamVector] # Writer queue
future: Future[void] # Stream life future future: Future[void] # Stream life future
# Windows specific part # Windows specific part
@ -87,18 +89,18 @@ when defined(windows):
local: TransportAddress # Local address local: TransportAddress # Local address
remote: TransportAddress # Remote address remote: TransportAddress # Remote address
of TransportKind.Pipe: of TransportKind.Pipe:
todo1: int discard
of TransportKind.File: of TransportKind.File:
todo2: int discard
else: else:
type type
StreamTransport* = ref object of RootRef StreamTransport* = ref object of RootRef
fd*: AsyncFD # File descriptor fd*: AsyncFD # File descriptor
state: set[TransportState] # Current Transport state state: set[TransportState] # Current Transport state
reader: Future[void] # Current reader Future reader: ReaderFuture # Current reader Future
buffer: seq[byte] # Reading buffer buffer: seq[byte] # Reading buffer
offset: int # Reading buffer offset offset: int # Reading buffer offset
error: ref CatchableError # Current error error: ref TransportError # Current error
queue: Deque[StreamVector] # Writer queue queue: Deque[StreamVector] # Writer queue
future: Future[void] # Stream life future future: Future[void] # Stream life future
case kind*: TransportKind case kind*: TransportKind
@ -107,18 +109,23 @@ else:
local: TransportAddress # Local address local: TransportAddress # Local address
remote: TransportAddress # Remote address remote: TransportAddress # Remote address
of TransportKind.Pipe: of TransportKind.Pipe:
todo1: int discard
of TransportKind.File: of TransportKind.File:
todo2: int discard
type type
StreamCallback* = proc(server: StreamServer, StreamCallback* = proc(server: StreamServer,
client: StreamTransport): Future[void] {. client: StreamTransport) {.async: (raises: []).}
gcsafe, raises: [].}
## New remote client connection callback ## New remote client connection callback
## ``server`` - StreamServer object. ## ``server`` - StreamServer object.
## ``client`` - accepted client transport. ## ``client`` - accepted client transport.
UnsafeStreamCallback* = proc(server: StreamServer,
client: StreamTransport) {.async.}
## Connection callback that doesn't check for exceptions at compile time
## ``server`` - StreamServer object.
## ``client`` - accepted client transport.
TransportInitCallback* = proc(server: StreamServer, TransportInitCallback* = proc(server: StreamServer,
fd: AsyncFD): StreamTransport {. fd: AsyncFD): StreamTransport {.
gcsafe, raises: [].} gcsafe, raises: [].}
@ -199,7 +206,7 @@ proc completePendingWriteQueue(queue: var Deque[StreamVector],
vector.writer.complete(v) vector.writer.complete(v)
proc failPendingWriteQueue(queue: var Deque[StreamVector], proc failPendingWriteQueue(queue: var Deque[StreamVector],
error: ref CatchableError) {.inline.} = error: ref TransportError) {.inline.} =
while len(queue) > 0: while len(queue) > 0:
var vector = queue.popFirst() var vector = queue.popFirst()
if not(vector.writer.finished()): if not(vector.writer.finished()):
@ -640,7 +647,8 @@ when defined(windows):
localAddress = TransportAddress(), localAddress = TransportAddress(),
flags: set[SocketFlags] = {}, flags: set[SocketFlags] = {},
dualstack = DualStackType.Auto dualstack = DualStackType.Auto
): Future[StreamTransport] = ): Future[StreamTransport] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
## Open new connection to remote peer with address ``address`` and create ## Open new connection to remote peer with address ``address`` and create
## new transport object ``StreamTransport`` for established connection. ## new transport object ``StreamTransport`` for established connection.
## ``bufferSize`` is size of internal buffer for transport. ## ``bufferSize`` is size of internal buffer for transport.
@ -1031,7 +1039,8 @@ when defined(windows):
server.aovl.data.cb(addr server.aovl) server.aovl.data.cb(addr server.aovl)
ok() ok()
proc accept*(server: StreamServer): Future[StreamTransport] = proc accept*(server: StreamServer): Future[StreamTransport] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
var retFuture = newFuture[StreamTransport]("stream.server.accept") var retFuture = newFuture[StreamTransport]("stream.server.accept")
doAssert(server.status != ServerStatus.Running, doAssert(server.status != ServerStatus.Running,
@ -1472,7 +1481,8 @@ else:
localAddress = TransportAddress(), localAddress = TransportAddress(),
flags: set[SocketFlags] = {}, flags: set[SocketFlags] = {},
dualstack = DualStackType.Auto, dualstack = DualStackType.Auto,
): Future[StreamTransport] = ): Future[StreamTransport] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
## Open new connection to remote peer with address ``address`` and create ## Open new connection to remote peer with address ``address`` and create
## new transport object ``StreamTransport`` for established connection. ## new transport object ``StreamTransport`` for established connection.
## ``bufferSize`` - size of internal buffer for transport. ## ``bufferSize`` - size of internal buffer for transport.
@ -1658,7 +1668,8 @@ else:
transp.state.excl(WritePaused) transp.state.excl(WritePaused)
ok() ok()
proc accept*(server: StreamServer): Future[StreamTransport] = proc accept*(server: StreamServer): Future[StreamTransport] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
var retFuture = newFuture[StreamTransport]("stream.server.accept") var retFuture = newFuture[StreamTransport]("stream.server.accept")
doAssert(server.status != ServerStatus.Running, doAssert(server.status != ServerStatus.Running,
@ -1762,7 +1773,8 @@ proc stop*(server: StreamServer) {.raises: [TransportOsError].} =
let res = stop2(server) let res = stop2(server)
if res.isErr(): raiseTransportOsError(res.error()) if res.isErr(): raiseTransportOsError(res.error())
proc join*(server: StreamServer): Future[void] = proc join*(server: StreamServer): Future[void] {.
async: (raw: true, raises: [CancelledError]).} =
## Waits until ``server`` is not closed. ## Waits until ``server`` is not closed.
var retFuture = newFuture[void]("stream.transport.server.join") var retFuture = newFuture[void]("stream.transport.server.join")
@ -1785,7 +1797,8 @@ proc connect*(address: TransportAddress,
flags: set[TransportFlags], flags: set[TransportFlags],
localAddress = TransportAddress(), localAddress = TransportAddress(),
dualstack = DualStackType.Auto dualstack = DualStackType.Auto
): Future[StreamTransport] = ): Future[StreamTransport] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
# Retro compatibility with TransportFlags # Retro compatibility with TransportFlags
var mappedFlags: set[SocketFlags] var mappedFlags: set[SocketFlags]
if TcpNoDelay in flags: mappedFlags.incl(SocketFlags.TcpNoDelay) if TcpNoDelay in flags: mappedFlags.incl(SocketFlags.TcpNoDelay)
@ -1817,7 +1830,8 @@ proc close*(server: StreamServer) =
else: else:
server.sock.closeSocket(continuation) server.sock.closeSocket(continuation)
proc closeWait*(server: StreamServer): Future[void] = proc closeWait*(server: StreamServer): Future[void] {.
async: (raw: true, raises: [CancelledError]).} =
## Close server ``server`` and release all resources. ## Close server ``server`` and release all resources.
server.close() server.close()
server.join() server.join()
@ -2066,6 +2080,7 @@ proc createStreamServer*(host: TransportAddress,
sres sres
proc createStreamServer*(host: TransportAddress, proc createStreamServer*(host: TransportAddress,
cbproc: UnsafeStreamCallback,
flags: set[ServerFlags] = {}, flags: set[ServerFlags] = {},
sock: AsyncFD = asyncInvalidSocket, sock: AsyncFD = asyncInvalidSocket,
backlog: int = DefaultBacklogSize, backlog: int = DefaultBacklogSize,
@ -2074,8 +2089,30 @@ proc createStreamServer*(host: TransportAddress,
init: TransportInitCallback = nil, init: TransportInitCallback = nil,
udata: pointer = nil, udata: pointer = nil,
dualstack = DualStackType.Auto): StreamServer {. dualstack = DualStackType.Auto): StreamServer {.
raises: [CatchableError].} = raises: [TransportOsError],
createStreamServer(host, nil, flags, sock, backlog, bufferSize, deprecated: "Callback must not raise exceptions, annotate with {.async: (raises: []).}".} =
proc wrap(server: StreamServer,
client: StreamTransport) {.async: (raises: []).} =
try:
cbproc(server, client)
except CatchableError as exc:
raiseAssert "Unexpected exception from stream server cbproc: " & exc.msg
createStreamServer(
host, wrap, flags, sock, backlog, bufferSize, child, init, udata,
dualstack)
proc createStreamServer*(host: TransportAddress,
flags: set[ServerFlags] = {},
sock: AsyncFD = asyncInvalidSocket,
backlog: int = DefaultBacklogSize,
bufferSize: int = DefaultStreamBufferSize,
child: StreamServer = nil,
init: TransportInitCallback = nil,
udata: pointer = nil,
dualstack = DualStackType.Auto): StreamServer {.
raises: [TransportOsError].} =
createStreamServer(host, StreamCallback(nil), flags, sock, backlog, bufferSize,
child, init, cast[pointer](udata), dualstack) child, init, cast[pointer](udata), dualstack)
proc createStreamServer*[T](host: TransportAddress, proc createStreamServer*[T](host: TransportAddress,
@ -2088,7 +2125,24 @@ proc createStreamServer*[T](host: TransportAddress,
child: StreamServer = nil, child: StreamServer = nil,
init: TransportInitCallback = nil, init: TransportInitCallback = nil,
dualstack = DualStackType.Auto): StreamServer {. dualstack = DualStackType.Auto): StreamServer {.
raises: [CatchableError].} = raises: [TransportOsError].} =
var fflags = flags + {GCUserData}
GC_ref(udata)
createStreamServer(host, cbproc, fflags, sock, backlog, bufferSize,
child, init, cast[pointer](udata), dualstack)
proc createStreamServer*[T](host: TransportAddress,
cbproc: UnsafeStreamCallback,
flags: set[ServerFlags] = {},
udata: ref T,
sock: AsyncFD = asyncInvalidSocket,
backlog: int = DefaultBacklogSize,
bufferSize: int = DefaultStreamBufferSize,
child: StreamServer = nil,
init: TransportInitCallback = nil,
dualstack = DualStackType.Auto): StreamServer {.
raises: [TransportOsError],
deprecated: "Callback must not raise exceptions, annotate with {.async: (raises: []).}".} =
var fflags = flags + {GCUserData} var fflags = flags + {GCUserData}
GC_ref(udata) GC_ref(udata)
createStreamServer(host, cbproc, fflags, sock, backlog, bufferSize, createStreamServer(host, cbproc, fflags, sock, backlog, bufferSize,
@ -2103,10 +2157,10 @@ proc createStreamServer*[T](host: TransportAddress,
child: StreamServer = nil, child: StreamServer = nil,
init: TransportInitCallback = nil, init: TransportInitCallback = nil,
dualstack = DualStackType.Auto): StreamServer {. dualstack = DualStackType.Auto): StreamServer {.
raises: [CatchableError].} = raises: [TransportOsError].} =
var fflags = flags + {GCUserData} var fflags = flags + {GCUserData}
GC_ref(udata) GC_ref(udata)
createStreamServer(host, nil, fflags, sock, backlog, bufferSize, createStreamServer(host, StreamCallback(nil), fflags, sock, backlog, bufferSize,
child, init, cast[pointer](udata), dualstack) child, init, cast[pointer](udata), dualstack)
proc getUserData*[T](server: StreamServer): T {.inline.} = proc getUserData*[T](server: StreamServer): T {.inline.} =
@ -2157,7 +2211,8 @@ template fastWrite(transp: auto, pbytes: var ptr byte, rbytes: var int,
return retFuture return retFuture
proc write*(transp: StreamTransport, pbytes: pointer, proc write*(transp: StreamTransport, pbytes: pointer,
nbytes: int): Future[int] = nbytes: int): Future[int] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
## Write data from buffer ``pbytes`` with size ``nbytes`` using transport ## Write data from buffer ``pbytes`` with size ``nbytes`` using transport
## ``transp``. ## ``transp``.
var retFuture = newFuture[int]("stream.transport.write(pointer)") var retFuture = newFuture[int]("stream.transport.write(pointer)")
@ -2179,9 +2234,10 @@ proc write*(transp: StreamTransport, pbytes: pointer,
return retFuture return retFuture
proc write*(transp: StreamTransport, msg: sink string, proc write*(transp: StreamTransport, msg: sink string,
msglen = -1): Future[int] = msglen = -1): Future[int] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
## Write data from string ``msg`` using transport ``transp``. ## Write data from string ``msg`` using transport ``transp``.
var retFuture = newFutureStr[int]("stream.transport.write(string)") var retFuture = newFuture[int]("stream.transport.write(string)")
transp.checkClosed(retFuture) transp.checkClosed(retFuture)
transp.checkWriteEof(retFuture) transp.checkWriteEof(retFuture)
@ -2197,17 +2253,10 @@ proc write*(transp: StreamTransport, msg: sink string,
let let
written = nbytes - rbytes # In case fastWrite wrote some written = nbytes - rbytes # In case fastWrite wrote some
pbytes = var localCopy = msg
when declared(shallowCopy): retFuture.addCallback(proc(_: pointer) = reset(localCopy))
if not(isLiteral(msg)):
shallowCopy(retFuture.gcholder, msg) pbytes = cast[ptr byte](addr localCopy[written])
cast[ptr byte](addr retFuture.gcholder[written])
else:
retFuture.gcholder = msg[written ..< nbytes]
cast[ptr byte](addr retFuture.gcholder[0])
else:
retFuture.gcholder = msg[written ..< nbytes]
cast[ptr byte](addr retFuture.gcholder[0])
var vector = StreamVector(kind: DataBuffer, writer: retFuture, var vector = StreamVector(kind: DataBuffer, writer: retFuture,
buf: pbytes, buflen: rbytes, size: nbytes) buf: pbytes, buflen: rbytes, size: nbytes)
@ -2218,9 +2267,10 @@ proc write*(transp: StreamTransport, msg: sink string,
return retFuture return retFuture
proc write*[T](transp: StreamTransport, msg: sink seq[T], proc write*[T](transp: StreamTransport, msg: sink seq[T],
msglen = -1): Future[int] = msglen = -1): Future[int] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
## Write sequence ``msg`` using transport ``transp``. ## Write sequence ``msg`` using transport ``transp``.
var retFuture = newFutureSeq[int, T]("stream.transport.write(seq)") var retFuture = newFuture[int]("stream.transport.write(seq)")
transp.checkClosed(retFuture) transp.checkClosed(retFuture)
transp.checkWriteEof(retFuture) transp.checkWriteEof(retFuture)
@ -2236,17 +2286,10 @@ proc write*[T](transp: StreamTransport, msg: sink seq[T],
let let
written = nbytes - rbytes # In case fastWrite wrote some written = nbytes - rbytes # In case fastWrite wrote some
pbytes = var localCopy = msg
when declared(shallowCopy): retFuture.addCallback(proc(_: pointer) = reset(localCopy))
if not(isLiteral(msg)):
shallowCopy(retFuture.gcholder, msg) pbytes = cast[ptr byte](addr localCopy[written])
cast[ptr byte](addr retFuture.gcholder[written])
else:
retFuture.gcholder = msg[written ..< nbytes]
cast[ptr byte](addr retFuture.gcholder[0])
else:
retFuture.gcholder = msg[written ..< nbytes]
cast[ptr byte](addr retFuture.gcholder[0])
var vector = StreamVector(kind: DataBuffer, writer: retFuture, var vector = StreamVector(kind: DataBuffer, writer: retFuture,
buf: pbytes, buflen: rbytes, size: nbytes) buf: pbytes, buflen: rbytes, size: nbytes)
@ -2257,7 +2300,8 @@ proc write*[T](transp: StreamTransport, msg: sink seq[T],
return retFuture return retFuture
proc writeFile*(transp: StreamTransport, handle: int, proc writeFile*(transp: StreamTransport, handle: int,
offset: uint = 0, size: int = 0): Future[int] = offset: uint = 0, size: int = 0): Future[int] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
## Write data from file descriptor ``handle`` to transport ``transp``. ## Write data from file descriptor ``handle`` to transport ``transp``.
## ##
## You can specify starting ``offset`` in opened file and number of bytes ## You can specify starting ``offset`` in opened file and number of bytes
@ -2304,7 +2348,7 @@ template readLoop(name, body: untyped): untyped =
break break
else: else:
checkPending(transp) checkPending(transp)
var fut = newFuture[void](name) let fut = ReaderFuture.init(name)
transp.reader = fut transp.reader = fut
let res = resumeRead(transp) let res = resumeRead(transp)
if res.isErr(): if res.isErr():
@ -2328,7 +2372,8 @@ template readLoop(name, body: untyped): untyped =
await fut await fut
proc readExactly*(transp: StreamTransport, pbytes: pointer, proc readExactly*(transp: StreamTransport, pbytes: pointer,
nbytes: int) {.async.} = nbytes: int) {.
async: (raises: [TransportError, CancelledError]).} =
## Read exactly ``nbytes`` bytes from transport ``transp`` and store it to ## Read exactly ``nbytes`` bytes from transport ``transp`` and store it to
## ``pbytes``. ``pbytes`` must not be ``nil`` pointer and ``nbytes`` should ## ``pbytes``. ``pbytes`` must not be ``nil`` pointer and ``nbytes`` should
## be Natural. ## be Natural.
@ -2357,7 +2402,8 @@ proc readExactly*(transp: StreamTransport, pbytes: pointer,
(consumed: count, done: index == nbytes) (consumed: count, done: index == nbytes)
proc readOnce*(transp: StreamTransport, pbytes: pointer, proc readOnce*(transp: StreamTransport, pbytes: pointer,
nbytes: int): Future[int] {.async.} = nbytes: int): Future[int] {.
async: (raises: [TransportError, CancelledError]).} =
## Perform one read operation on transport ``transp``. ## Perform one read operation on transport ``transp``.
## ##
## If internal buffer is not empty, ``nbytes`` bytes will be transferred from ## If internal buffer is not empty, ``nbytes`` bytes will be transferred from
@ -2376,7 +2422,8 @@ proc readOnce*(transp: StreamTransport, pbytes: pointer,
return count return count
proc readUntil*(transp: StreamTransport, pbytes: pointer, nbytes: int, proc readUntil*(transp: StreamTransport, pbytes: pointer, nbytes: int,
sep: seq[byte]): Future[int] {.async.} = sep: seq[byte]): Future[int] {.
async: (raises: [TransportError, CancelledError]).} =
## Read data from the transport ``transp`` until separator ``sep`` is found. ## Read data from the transport ``transp`` until separator ``sep`` is found.
## ##
## On success, the data and separator will be removed from the internal ## On success, the data and separator will be removed from the internal
@ -2428,7 +2475,8 @@ proc readUntil*(transp: StreamTransport, pbytes: pointer, nbytes: int,
return k return k
proc readLine*(transp: StreamTransport, limit = 0, proc readLine*(transp: StreamTransport, limit = 0,
sep = "\r\n"): Future[string] {.async.} = sep = "\r\n"): Future[string] {.
async: (raises: [TransportError, CancelledError]).} =
## Read one line from transport ``transp``, where "line" is a sequence of ## Read one line from transport ``transp``, where "line" is a sequence of
## bytes ending with ``sep`` (default is "\r\n"). ## bytes ending with ``sep`` (default is "\r\n").
## ##
@ -2470,7 +2518,8 @@ proc readLine*(transp: StreamTransport, limit = 0,
(index, (state == len(sep)) or (lim == len(result))) (index, (state == len(sep)) or (lim == len(result)))
proc read*(transp: StreamTransport): Future[seq[byte]] {.async.} = proc read*(transp: StreamTransport): Future[seq[byte]] {.
async: (raises: [TransportError, CancelledError]).} =
## Read all bytes from transport ``transp``. ## Read all bytes from transport ``transp``.
## ##
## This procedure allocates buffer seq[byte] and return it as result. ## This procedure allocates buffer seq[byte] and return it as result.
@ -2481,7 +2530,8 @@ proc read*(transp: StreamTransport): Future[seq[byte]] {.async.} =
result.add(transp.buffer.toOpenArray(0, transp.offset - 1)) result.add(transp.buffer.toOpenArray(0, transp.offset - 1))
(transp.offset, false) (transp.offset, false)
proc read*(transp: StreamTransport, n: int): Future[seq[byte]] {.async.} = proc read*(transp: StreamTransport, n: int): Future[seq[byte]] {.
async: (raises: [TransportError, CancelledError]).} =
## Read all bytes (n <= 0) or exactly `n` bytes from transport ``transp``. ## Read all bytes (n <= 0) or exactly `n` bytes from transport ``transp``.
## ##
## This procedure allocates buffer seq[byte] and return it as result. ## This procedure allocates buffer seq[byte] and return it as result.
@ -2496,7 +2546,8 @@ proc read*(transp: StreamTransport, n: int): Future[seq[byte]] {.async.} =
result.add(transp.buffer.toOpenArray(0, count - 1)) result.add(transp.buffer.toOpenArray(0, count - 1))
(count, len(result) == n) (count, len(result) == n)
proc consume*(transp: StreamTransport): Future[int] {.async.} = proc consume*(transp: StreamTransport): Future[int] {.
async: (raises: [TransportError, CancelledError]).} =
## Consume all bytes from transport ``transp`` and discard it. ## Consume all bytes from transport ``transp`` and discard it.
## ##
## Return number of bytes actually consumed and discarded. ## Return number of bytes actually consumed and discarded.
@ -2507,7 +2558,8 @@ proc consume*(transp: StreamTransport): Future[int] {.async.} =
result += transp.offset result += transp.offset
(transp.offset, false) (transp.offset, false)
proc consume*(transp: StreamTransport, n: int): Future[int] {.async.} = proc consume*(transp: StreamTransport, n: int): Future[int] {.
async: (raises: [TransportError, CancelledError]).} =
## Consume all bytes (n <= 0) or ``n`` bytes from transport ``transp`` and ## Consume all bytes (n <= 0) or ``n`` bytes from transport ``transp`` and
## discard it. ## discard it.
## ##
@ -2524,7 +2576,8 @@ proc consume*(transp: StreamTransport, n: int): Future[int] {.async.} =
(count, result == n) (count, result == n)
proc readMessage*(transp: StreamTransport, proc readMessage*(transp: StreamTransport,
predicate: ReadMessagePredicate) {.async.} = predicate: ReadMessagePredicate) {.
async: (raises: [TransportError, CancelledError]).} =
## Read all bytes from transport ``transp`` until ``predicate`` callback ## Read all bytes from transport ``transp`` until ``predicate`` callback
## will not be satisfied. ## will not be satisfied.
## ##
@ -2547,7 +2600,8 @@ proc readMessage*(transp: StreamTransport,
else: else:
predicate(transp.buffer.toOpenArray(0, transp.offset - 1)) predicate(transp.buffer.toOpenArray(0, transp.offset - 1))
proc join*(transp: StreamTransport): Future[void] = proc join*(transp: StreamTransport): Future[void] {.
async: (raw: true, raises: [CancelledError]).} =
## Wait until ``transp`` will not be closed. ## Wait until ``transp`` will not be closed.
var retFuture = newFuture[void]("stream.transport.join") var retFuture = newFuture[void]("stream.transport.join")
@ -2606,14 +2660,15 @@ proc close*(transp: StreamTransport) =
elif transp.kind == TransportKind.Socket: elif transp.kind == TransportKind.Socket:
closeSocket(transp.fd, continuation) closeSocket(transp.fd, continuation)
proc closeWait*(transp: StreamTransport): Future[void] = proc closeWait*(transp: StreamTransport): Future[void] {.
async: (raw: true, raises: []).} =
## Close and frees resources of transport ``transp``. ## Close and frees resources of transport ``transp``.
const FutureName = "stream.transport.closeWait" let retFuture = newFuture[void](
"stream.transport.closeWait", {FutureFlag.OwnCancelSchedule})
if {ReadClosed, WriteClosed} * transp.state != {}: if {ReadClosed, WriteClosed} * transp.state != {}:
return Future.completed(FutureName) retFuture.complete()
return retFuture
let retFuture = newFuture[void](FutureName, {FutureFlag.OwnCancelSchedule})
proc continuation(udata: pointer) {.gcsafe.} = proc continuation(udata: pointer) {.gcsafe.} =
retFuture.complete() retFuture.complete()
@ -2631,7 +2686,8 @@ proc closeWait*(transp: StreamTransport): Future[void] =
retFuture.cancelCallback = cancellation retFuture.cancelCallback = cancellation
retFuture retFuture
proc shutdownWait*(transp: StreamTransport): Future[void] = proc shutdownWait*(transp: StreamTransport): Future[void] {.
async: (raw: true, raises: [TransportError, CancelledError]).} =
## Perform graceful shutdown of TCP connection backed by transport ``transp``. ## Perform graceful shutdown of TCP connection backed by transport ``transp``.
doAssert(transp.kind == TransportKind.Socket) doAssert(transp.kind == TransportKind.Socket)
let retFuture = newFuture[void]("stream.transport.shutdown") let retFuture = newFuture[void]("stream.transport.shutdown")

View File

@ -87,14 +87,17 @@ suite "AsyncStream test suite":
test "AsyncStream(StreamTransport) readExactly() test": test "AsyncStream(StreamTransport) readExactly() test":
proc testReadExactly(): Future[bool] {.async.} = proc testReadExactly(): Future[bool] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
await wstream.write("000000000011111111112222222222") var wstream = newAsyncStreamWriter(transp)
await wstream.finish() await wstream.write("000000000011111111112222222222")
await wstream.closeWait() await wstream.finish()
await transp.closeWait() await wstream.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var buffer = newSeq[byte](10) var buffer = newSeq[byte](10)
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
@ -117,14 +120,17 @@ suite "AsyncStream test suite":
test "AsyncStream(StreamTransport) readUntil() test": test "AsyncStream(StreamTransport) readUntil() test":
proc testReadUntil(): Future[bool] {.async.} = proc testReadUntil(): Future[bool] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
await wstream.write("0000000000NNz1111111111NNz2222222222NNz") var wstream = newAsyncStreamWriter(transp)
await wstream.finish() await wstream.write("0000000000NNz1111111111NNz2222222222NNz")
await wstream.closeWait() await wstream.finish()
await transp.closeWait() await wstream.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var buffer = newSeq[byte](13) var buffer = newSeq[byte](13)
var sep = @[byte('N'), byte('N'), byte('z')] var sep = @[byte('N'), byte('N'), byte('z')]
@ -155,14 +161,17 @@ suite "AsyncStream test suite":
test "AsyncStream(StreamTransport) readLine() test": test "AsyncStream(StreamTransport) readLine() test":
proc testReadLine(): Future[bool] {.async.} = proc testReadLine(): Future[bool] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
await wstream.write("0000000000\r\n1111111111\r\n2222222222\r\n") var wstream = newAsyncStreamWriter(transp)
await wstream.finish() await wstream.write("0000000000\r\n1111111111\r\n2222222222\r\n")
await wstream.closeWait() await wstream.finish()
await transp.closeWait() await wstream.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
serveClient, {ReuseAddr}) serveClient, {ReuseAddr})
@ -184,14 +193,17 @@ suite "AsyncStream test suite":
test "AsyncStream(StreamTransport) read() test": test "AsyncStream(StreamTransport) read() test":
proc testRead(): Future[bool] {.async.} = proc testRead(): Future[bool] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
await wstream.write("000000000011111111112222222222") var wstream = newAsyncStreamWriter(transp)
await wstream.finish() await wstream.write("000000000011111111112222222222")
await wstream.closeWait() await wstream.finish()
await transp.closeWait() await wstream.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
serveClient, {ReuseAddr}) serveClient, {ReuseAddr})
@ -211,14 +223,17 @@ suite "AsyncStream test suite":
test "AsyncStream(StreamTransport) consume() test": test "AsyncStream(StreamTransport) consume() test":
proc testConsume(): Future[bool] {.async.} = proc testConsume(): Future[bool] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
await wstream.write("0000000000111111111122222222223333333333") var wstream = newAsyncStreamWriter(transp)
await wstream.finish() await wstream.write("0000000000111111111122222222223333333333")
await wstream.closeWait() await wstream.finish()
await transp.closeWait() await wstream.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
serveClient, {ReuseAddr}) serveClient, {ReuseAddr})
@ -247,26 +262,29 @@ suite "AsyncStream test suite":
test "AsyncStream(AsyncStream) readExactly() test": test "AsyncStream(AsyncStream) readExactly() test":
proc testReadExactly2(): Future[bool] {.async.} = proc testReadExactly2(): Future[bool] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
var wstream2 = newChunkedStreamWriter(wstream) var wstream = newAsyncStreamWriter(transp)
var s1 = "00000" var wstream2 = newChunkedStreamWriter(wstream)
var s2 = "11111" var s1 = "00000"
var s3 = "22222" var s2 = "11111"
await wstream2.write("00000") var s3 = "22222"
await wstream2.write(addr s1[0], len(s1)) await wstream2.write("00000")
await wstream2.write("11111") await wstream2.write(addr s1[0], len(s1))
await wstream2.write(s2.toBytes()) await wstream2.write("11111")
await wstream2.write("22222") await wstream2.write(s2.toBytes())
await wstream2.write(addr s3[0], len(s3)) await wstream2.write("22222")
await wstream2.write(addr s3[0], len(s3))
await wstream2.finish() await wstream2.finish()
await wstream.finish() await wstream.finish()
await wstream2.closeWait() await wstream2.closeWait()
await wstream.closeWait() await wstream.closeWait()
await transp.closeWait() await transp.closeWait()
server.stop() server.stop()
server.close() server.close()
except CatchableError as exc:
raiseAssert exc.msg
var buffer = newSeq[byte](10) var buffer = newSeq[byte](10)
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
@ -299,25 +317,28 @@ suite "AsyncStream test suite":
test "AsyncStream(AsyncStream) readUntil() test": test "AsyncStream(AsyncStream) readUntil() test":
proc testReadUntil2(): Future[bool] {.async.} = proc testReadUntil2(): Future[bool] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
var wstream2 = newChunkedStreamWriter(wstream) var wstream = newAsyncStreamWriter(transp)
var s1 = "00000NNz" var wstream2 = newChunkedStreamWriter(wstream)
var s2 = "11111NNz" var s1 = "00000NNz"
var s3 = "22222NNz" var s2 = "11111NNz"
await wstream2.write("00000") var s3 = "22222NNz"
await wstream2.write(addr s1[0], len(s1)) await wstream2.write("00000")
await wstream2.write("11111") await wstream2.write(addr s1[0], len(s1))
await wstream2.write(s2) await wstream2.write("11111")
await wstream2.write("22222") await wstream2.write(s2)
await wstream2.write(s3.toBytes()) await wstream2.write("22222")
await wstream2.finish() await wstream2.write(s3.toBytes())
await wstream.finish() await wstream2.finish()
await wstream2.closeWait() await wstream.finish()
await wstream.closeWait() await wstream2.closeWait()
await transp.closeWait() await wstream.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var buffer = newSeq[byte](13) var buffer = newSeq[byte](13)
var sep = @[byte('N'), byte('N'), byte('z')] var sep = @[byte('N'), byte('N'), byte('z')]
@ -358,22 +379,25 @@ suite "AsyncStream test suite":
test "AsyncStream(AsyncStream) readLine() test": test "AsyncStream(AsyncStream) readLine() test":
proc testReadLine2(): Future[bool] {.async.} = proc testReadLine2(): Future[bool] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
var wstream2 = newChunkedStreamWriter(wstream) var wstream = newAsyncStreamWriter(transp)
await wstream2.write("00000") var wstream2 = newChunkedStreamWriter(wstream)
await wstream2.write("00000\r\n") await wstream2.write("00000")
await wstream2.write("11111") await wstream2.write("00000\r\n")
await wstream2.write("11111\r\n") await wstream2.write("11111")
await wstream2.write("22222") await wstream2.write("11111\r\n")
await wstream2.write("22222\r\n") await wstream2.write("22222")
await wstream2.finish() await wstream2.write("22222\r\n")
await wstream.finish() await wstream2.finish()
await wstream2.closeWait() await wstream.finish()
await wstream.closeWait() await wstream2.closeWait()
await transp.closeWait() await wstream.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
serveClient, {ReuseAddr}) serveClient, {ReuseAddr})
@ -405,21 +429,24 @@ suite "AsyncStream test suite":
test "AsyncStream(AsyncStream) read() test": test "AsyncStream(AsyncStream) read() test":
proc testRead2(): Future[bool] {.async.} = proc testRead2(): Future[bool] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
var wstream2 = newChunkedStreamWriter(wstream) var wstream = newAsyncStreamWriter(transp)
var s2 = "1111111111" var wstream2 = newChunkedStreamWriter(wstream)
var s3 = "2222222222" var s2 = "1111111111"
await wstream2.write("0000000000") var s3 = "2222222222"
await wstream2.write(s2) await wstream2.write("0000000000")
await wstream2.write(s3.toBytes()) await wstream2.write(s2)
await wstream2.finish() await wstream2.write(s3.toBytes())
await wstream.finish() await wstream2.finish()
await wstream2.closeWait() await wstream.finish()
await wstream.closeWait() await wstream2.closeWait()
await transp.closeWait() await wstream.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
serveClient, {ReuseAddr}) serveClient, {ReuseAddr})
@ -446,31 +473,34 @@ suite "AsyncStream test suite":
test "AsyncStream(AsyncStream) consume() test": test "AsyncStream(AsyncStream) consume() test":
proc testConsume2(): Future[bool] {.async.} = proc testConsume2(): Future[bool] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
const try:
S4 = @[byte('3'), byte('3'), byte('3'), byte('3'), byte('3')] const
var wstream = newAsyncStreamWriter(transp) S4 = @[byte('3'), byte('3'), byte('3'), byte('3'), byte('3')]
var wstream2 = newChunkedStreamWriter(wstream) var wstream = newAsyncStreamWriter(transp)
var wstream2 = newChunkedStreamWriter(wstream)
var s1 = "00000" var s1 = "00000"
var s2 = "11111".toBytes() var s2 = "11111".toBytes()
var s3 = "22222" var s3 = "22222"
await wstream2.write("00000") await wstream2.write("00000")
await wstream2.write(s1) await wstream2.write(s1)
await wstream2.write("11111") await wstream2.write("11111")
await wstream2.write(s2) await wstream2.write(s2)
await wstream2.write("22222") await wstream2.write("22222")
await wstream2.write(addr s3[0], len(s3)) await wstream2.write(addr s3[0], len(s3))
await wstream2.write("33333") await wstream2.write("33333")
await wstream2.write(S4) await wstream2.write(S4)
await wstream2.finish() await wstream2.finish()
await wstream.finish() await wstream.finish()
await wstream2.closeWait() await wstream2.closeWait()
await wstream.closeWait() await wstream.closeWait()
await transp.closeWait() await transp.closeWait()
server.stop() server.stop()
server.close() server.close()
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
serveClient, {ReuseAddr}) serveClient, {ReuseAddr})
@ -511,27 +541,30 @@ suite "AsyncStream test suite":
message = createBigMessage("ABCDEFGHIJKLMNOP", size) message = createBigMessage("ABCDEFGHIJKLMNOP", size)
proc processClient(server: StreamServer, proc processClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp)
var wbstream = newBoundedStreamWriter(wstream, uint64(size))
try: try:
check wbstream.atEof() == false var wstream = newAsyncStreamWriter(transp)
await wbstream.write(message) var wbstream = newBoundedStreamWriter(wstream, uint64(size))
check wbstream.atEof() == false try:
await wbstream.finish() check wbstream.atEof() == false
check wbstream.atEof() == true
expect AsyncStreamWriteEOFError:
await wbstream.write(message) await wbstream.write(message)
expect AsyncStreamWriteEOFError: check wbstream.atEof() == false
await wbstream.write(message) await wbstream.finish()
expect AsyncStreamWriteEOFError: check wbstream.atEof() == true
await wbstream.write(message) expect AsyncStreamWriteEOFError:
check wbstream.atEof() == true await wbstream.write(message)
await wbstream.closeWait() expect AsyncStreamWriteEOFError:
check wbstream.atEof() == true await wbstream.write(message)
finally: expect AsyncStreamWriteEOFError:
await wstream.closeWait() await wbstream.write(message)
await transp.closeWait() check wbstream.atEof() == true
await wbstream.closeWait()
check wbstream.atEof() == true
finally:
await wstream.closeWait()
await transp.closeWait()
except CatchableError as exc:
raiseAssert exc.msg
let flags = {ServerFlags.ReuseAddr, ServerFlags.TcpNoDelay} let flags = {ServerFlags.ReuseAddr, ServerFlags.TcpNoDelay}
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
@ -580,15 +613,18 @@ suite "ChunkedStream test suite":
] ]
proc checkVector(inputstr: string): Future[string] {.async.} = proc checkVector(inputstr: string): Future[string] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
var data = inputstr var wstream = newAsyncStreamWriter(transp)
await wstream.write(data) var data = inputstr
await wstream.finish() await wstream.write(data)
await wstream.closeWait() await wstream.finish()
await transp.closeWait() await wstream.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
serveClient, {ReuseAddr}) serveClient, {ReuseAddr})
@ -630,15 +666,18 @@ suite "ChunkedStream test suite":
] ]
proc checkVector(inputstr: string): Future[bool] {.async.} = proc checkVector(inputstr: string): Future[bool] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
var data = inputstr var wstream = newAsyncStreamWriter(transp)
await wstream.write(data) var data = inputstr
await wstream.finish() await wstream.write(data)
await wstream.closeWait() await wstream.finish()
await transp.closeWait() await wstream.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var res = false var res = false
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
@ -713,14 +752,17 @@ suite "ChunkedStream test suite":
test "ChunkedStream too big chunk header test": test "ChunkedStream too big chunk header test":
proc checkTooBigChunkHeader(inputstr: seq[byte]): Future[bool] {.async.} = proc checkTooBigChunkHeader(inputstr: seq[byte]): Future[bool] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
await wstream.write(inputstr) var wstream = newAsyncStreamWriter(transp)
await wstream.finish() await wstream.write(inputstr)
await wstream.closeWait() await wstream.finish()
await transp.closeWait() await wstream.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
serveClient, {ReuseAddr}) serveClient, {ReuseAddr})
@ -751,23 +793,26 @@ suite "ChunkedStream test suite":
proc checkVector(inputstr: seq[byte], proc checkVector(inputstr: seq[byte],
chunkSize: int): Future[seq[byte]] {.async.} = chunkSize: int): Future[seq[byte]] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
var wstream2 = newChunkedStreamWriter(wstream) var wstream = newAsyncStreamWriter(transp)
var data = inputstr var wstream2 = newChunkedStreamWriter(wstream)
var offset = 0 var data = inputstr
while true: var offset = 0
if len(data) == offset: while true:
break if len(data) == offset:
let toWrite = min(chunkSize, len(data) - offset) break
await wstream2.write(addr data[offset], toWrite) let toWrite = min(chunkSize, len(data) - offset)
offset = offset + toWrite await wstream2.write(addr data[offset], toWrite)
await wstream2.finish() offset = offset + toWrite
await wstream2.closeWait() await wstream2.finish()
await wstream.closeWait() await wstream2.closeWait()
await transp.closeWait() await wstream.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
serveClient, {ReuseAddr}) serveClient, {ReuseAddr})
@ -796,23 +841,26 @@ suite "ChunkedStream test suite":
writeChunkSize: int, writeChunkSize: int,
readChunkSize: int): Future[seq[byte]] {.async.} = readChunkSize: int): Future[seq[byte]] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
var wstream2 = newChunkedStreamWriter(wstream) var wstream = newAsyncStreamWriter(transp)
var data = inputstr var wstream2 = newChunkedStreamWriter(wstream)
var offset = 0 var data = inputstr
while true: var offset = 0
if len(data) == offset: while true:
break if len(data) == offset:
let toWrite = min(writeChunkSize, len(data) - offset) break
await wstream2.write(addr data[offset], toWrite) let toWrite = min(writeChunkSize, len(data) - offset)
offset = offset + toWrite await wstream2.write(addr data[offset], toWrite)
await wstream2.finish() offset = offset + toWrite
await wstream2.closeWait() await wstream2.finish()
await wstream.closeWait() await wstream2.closeWait()
await transp.closeWait() await wstream.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
serveClient, {ReuseAddr}) serveClient, {ReuseAddr})
@ -849,30 +897,33 @@ suite "TLSStream test suite":
const HttpHeadersMark = @[byte(0x0D), byte(0x0A), byte(0x0D), byte(0x0A)] const HttpHeadersMark = @[byte(0x0D), byte(0x0A), byte(0x0D), byte(0x0A)]
test "Simple HTTPS connection": test "Simple HTTPS connection":
proc headerClient(address: TransportAddress, proc headerClient(address: TransportAddress,
name: string): Future[bool] {.async.} = name: string): Future[bool] {.async: (raises: []).} =
var mark = "HTTP/1.1 " try:
var buffer = newSeq[byte](8192) var mark = "HTTP/1.1 "
var transp = await connect(address) var buffer = newSeq[byte](8192)
var reader = newAsyncStreamReader(transp) var transp = await connect(address)
var writer = newAsyncStreamWriter(transp) var reader = newAsyncStreamReader(transp)
var tlsstream = newTLSClientAsyncStream(reader, writer, name) var writer = newAsyncStreamWriter(transp)
await tlsstream.writer.write("GET / HTTP/1.1\r\nHost: " & name & var tlsstream = newTLSClientAsyncStream(reader, writer, name)
"\r\nConnection: close\r\n\r\n") await tlsstream.writer.write("GET / HTTP/1.1\r\nHost: " & name &
var readFut = tlsstream.reader.readUntil(addr buffer[0], len(buffer), "\r\nConnection: close\r\n\r\n")
HttpHeadersMark) var readFut = tlsstream.reader.readUntil(addr buffer[0], len(buffer),
let res = await withTimeout(readFut, 5.seconds) HttpHeadersMark)
if res: let res = await withTimeout(readFut, 5.seconds)
var length = readFut.read() if res:
buffer.setLen(length) var length = readFut.read()
if len(buffer) > len(mark): buffer.setLen(length)
if equalMem(addr buffer[0], addr mark[0], len(mark)): if len(buffer) > len(mark):
result = true if equalMem(addr buffer[0], addr mark[0], len(mark)):
result = true
await tlsstream.reader.closeWait() await tlsstream.reader.closeWait()
await tlsstream.writer.closeWait() await tlsstream.writer.closeWait()
await reader.closeWait() await reader.closeWait()
await writer.closeWait() await writer.closeWait()
await transp.closeWait() await transp.closeWait()
except CatchableError as exc:
raiseAssert exc.msg
let res = waitFor(headerClient(resolveTAddress("www.google.com:443")[0], let res = waitFor(headerClient(resolveTAddress("www.google.com:443")[0],
"www.google.com")) "www.google.com"))
@ -884,20 +935,23 @@ suite "TLSStream test suite":
let testMessage = "TEST MESSAGE" let testMessage = "TEST MESSAGE"
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var reader = newAsyncStreamReader(transp) try:
var writer = newAsyncStreamWriter(transp) var reader = newAsyncStreamReader(transp)
var sstream = newTLSServerAsyncStream(reader, writer, key, cert) var writer = newAsyncStreamWriter(transp)
await handshake(sstream) var sstream = newTLSServerAsyncStream(reader, writer, key, cert)
await sstream.writer.write(testMessage & "\r\n") await handshake(sstream)
await sstream.writer.finish() await sstream.writer.write(testMessage & "\r\n")
await sstream.writer.closeWait() await sstream.writer.finish()
await sstream.reader.closeWait() await sstream.writer.closeWait()
await reader.closeWait() await sstream.reader.closeWait()
await writer.closeWait() await reader.closeWait()
await transp.closeWait() await writer.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
key = TLSPrivateKey.init(pemkey) key = TLSPrivateKey.init(pemkey)
cert = TLSCertificate.init(pemcert) cert = TLSCertificate.init(pemcert)
@ -931,20 +985,23 @@ suite "TLSStream test suite":
let trustAnchors = TrustAnchorStore.new(SelfSignedTrustAnchors) let trustAnchors = TrustAnchorStore.new(SelfSignedTrustAnchors)
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var reader = newAsyncStreamReader(transp) try:
var writer = newAsyncStreamWriter(transp) var reader = newAsyncStreamReader(transp)
var sstream = newTLSServerAsyncStream(reader, writer, key, cert) var writer = newAsyncStreamWriter(transp)
await handshake(sstream) var sstream = newTLSServerAsyncStream(reader, writer, key, cert)
await sstream.writer.write(testMessage & "\r\n") await handshake(sstream)
await sstream.writer.finish() await sstream.writer.write(testMessage & "\r\n")
await sstream.writer.closeWait() await sstream.writer.finish()
await sstream.reader.closeWait() await sstream.writer.closeWait()
await reader.closeWait() await sstream.reader.closeWait()
await writer.closeWait() await reader.closeWait()
await transp.closeWait() await writer.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
serveClient, {ReuseAddr}) serveClient, {ReuseAddr})
@ -988,46 +1045,49 @@ suite "BoundedStream test suite":
var clientRes = false var clientRes = false
proc processClient(server: StreamServer, proc processClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
case btest var wstream = newAsyncStreamWriter(transp)
of BoundaryRead: case btest
await wstream.write(message) of BoundaryRead:
await wstream.write(boundary) await wstream.write(message)
await wstream.finish() await wstream.write(boundary)
await wstream.closeWait() await wstream.finish()
clientRes = true await wstream.closeWait()
of BoundaryDouble: clientRes = true
await wstream.write(message) of BoundaryDouble:
await wstream.write(boundary) await wstream.write(message)
await wstream.write(message) await wstream.write(boundary)
await wstream.finish() await wstream.write(message)
await wstream.closeWait() await wstream.finish()
clientRes = true await wstream.closeWait()
of BoundarySize: clientRes = true
var ncmessage = message of BoundarySize:
ncmessage.setLen(len(message) - 2) var ncmessage = message
await wstream.write(ncmessage) ncmessage.setLen(len(message) - 2)
await wstream.write(@[0x2D'u8, 0x2D'u8]) await wstream.write(ncmessage)
await wstream.finish() await wstream.write(@[0x2D'u8, 0x2D'u8])
await wstream.closeWait() await wstream.finish()
clientRes = true await wstream.closeWait()
of BoundaryIncomplete: clientRes = true
var ncmessage = message of BoundaryIncomplete:
ncmessage.setLen(len(message) - 2) var ncmessage = message
await wstream.write(ncmessage) ncmessage.setLen(len(message) - 2)
await wstream.finish() await wstream.write(ncmessage)
await wstream.closeWait() await wstream.finish()
clientRes = true await wstream.closeWait()
of BoundaryEmpty: clientRes = true
await wstream.write(boundary) of BoundaryEmpty:
await wstream.finish() await wstream.write(boundary)
await wstream.closeWait() await wstream.finish()
clientRes = true await wstream.closeWait()
clientRes = true
await transp.closeWait() await transp.closeWait()
server.stop() server.stop()
server.close() server.close()
except CatchableError as exc:
raiseAssert exc.msg
var res = false var res = false
let flags = {ServerFlags.ReuseAddr, ServerFlags.TcpNoDelay} let flags = {ServerFlags.ReuseAddr, ServerFlags.TcpNoDelay}
@ -1090,60 +1150,63 @@ suite "BoundedStream test suite":
message.add(messagePart) message.add(messagePart)
proc processClient(server: StreamServer, proc processClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
var wbstream = newBoundedStreamWriter(wstream, uint64(size), var wstream = newAsyncStreamWriter(transp)
comparison = cmp) var wbstream = newBoundedStreamWriter(wstream, uint64(size),
case stest comparison = cmp)
of SizeReadWrite: case stest
for i in 0 ..< 10: of SizeReadWrite:
await wbstream.write(messagePart) for i in 0 ..< 10:
await wbstream.finish() await wbstream.write(messagePart)
await wbstream.closeWait() await wbstream.finish()
clientRes = true await wbstream.closeWait()
of SizeOverflow:
for i in 0 ..< 10:
await wbstream.write(messagePart)
try:
await wbstream.write(messagePart)
except BoundedStreamOverflowError:
clientRes = true clientRes = true
await wbstream.closeWait() of SizeOverflow:
of SizeIncomplete: for i in 0 ..< 10:
for i in 0 ..< 9: await wbstream.write(messagePart)
await wbstream.write(messagePart)
case cmp
of BoundCmp.Equal:
try: try:
await wbstream.finish() await wbstream.write(messagePart)
except BoundedStreamIncompleteError: except BoundedStreamOverflowError:
clientRes = true clientRes = true
of BoundCmp.LessOrEqual: await wbstream.closeWait()
try: of SizeIncomplete:
await wbstream.finish() for i in 0 ..< 9:
clientRes = true await wbstream.write(messagePart)
except BoundedStreamIncompleteError: case cmp
discard of BoundCmp.Equal:
await wbstream.closeWait() try:
of SizeEmpty: await wbstream.finish()
case cmp except BoundedStreamIncompleteError:
of BoundCmp.Equal: clientRes = true
try: of BoundCmp.LessOrEqual:
await wbstream.finish() try:
except BoundedStreamIncompleteError: await wbstream.finish()
clientRes = true clientRes = true
of BoundCmp.LessOrEqual: except BoundedStreamIncompleteError:
try: discard
await wbstream.finish() await wbstream.closeWait()
clientRes = true of SizeEmpty:
except BoundedStreamIncompleteError: case cmp
discard of BoundCmp.Equal:
await wbstream.closeWait() try:
await wbstream.finish()
except BoundedStreamIncompleteError:
clientRes = true
of BoundCmp.LessOrEqual:
try:
await wbstream.finish()
clientRes = true
except BoundedStreamIncompleteError:
discard
await wbstream.closeWait()
await wstream.closeWait() await wstream.closeWait()
await transp.closeWait() await transp.closeWait()
server.stop() server.stop()
server.close() server.close()
except CatchableError as exc:
raiseAssert exc.msg
let flags = {ServerFlags.ReuseAddr, ServerFlags.TcpNoDelay} let flags = {ServerFlags.ReuseAddr, ServerFlags.TcpNoDelay}
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
@ -1243,23 +1306,26 @@ suite "BoundedStream test suite":
writeChunkSize: int, writeChunkSize: int,
readChunkSize: int): Future[seq[byte]] {.async.} = readChunkSize: int): Future[seq[byte]] {.async.} =
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
var wstream2 = newBoundedStreamWriter(wstream, uint64(len(inputstr))) var wstream = newAsyncStreamWriter(transp)
var data = inputstr var wstream2 = newBoundedStreamWriter(wstream, uint64(len(inputstr)))
var offset = 0 var data = inputstr
while true: var offset = 0
if len(data) == offset: while true:
break if len(data) == offset:
let toWrite = min(writeChunkSize, len(data) - offset) break
await wstream2.write(addr data[offset], toWrite) let toWrite = min(writeChunkSize, len(data) - offset)
offset = offset + toWrite await wstream2.write(addr data[offset], toWrite)
await wstream2.finish() offset = offset + toWrite
await wstream2.closeWait() await wstream2.finish()
await wstream.closeWait() await wstream2.closeWait()
await transp.closeWait() await wstream.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
serveClient, {ReuseAddr}) serveClient, {ReuseAddr})
@ -1293,17 +1359,20 @@ suite "BoundedStream test suite":
proc checkEmptyStreams(): Future[bool] {.async.} = proc checkEmptyStreams(): Future[bool] {.async.} =
var writer1Res = false var writer1Res = false
proc serveClient(server: StreamServer, proc serveClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var wstream = newAsyncStreamWriter(transp) try:
var wstream2 = newBoundedStreamWriter(wstream, 0'u64) var wstream = newAsyncStreamWriter(transp)
await wstream2.finish() var wstream2 = newBoundedStreamWriter(wstream, 0'u64)
let res = wstream2.atEof() await wstream2.finish()
await wstream2.closeWait() let res = wstream2.atEof()
await wstream.closeWait() await wstream2.closeWait()
await transp.closeWait() await wstream.closeWait()
server.stop() await transp.closeWait()
server.close() server.stop()
writer1Res = res server.close()
writer1Res = res
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(initTAddress("127.0.0.1:0"), var server = createStreamServer(initTAddress("127.0.0.1:0"),
serveClient, {ReuseAddr}) serveClient, {ReuseAddr})

View File

@ -21,16 +21,19 @@ suite "Asynchronous issues test suite":
test: string test: string
proc udp4DataAvailable(transp: DatagramTransport, proc udp4DataAvailable(transp: DatagramTransport,
remote: TransportAddress) {.async, gcsafe.} = remote: TransportAddress) {.async: (raises: []).} =
var udata = getUserData[CustomData](transp) try:
var expect = TEST_MSG var udata = getUserData[CustomData](transp)
var data: seq[byte] var expect = TEST_MSG
var datalen: int var data: seq[byte]
transp.peekMessage(data, datalen) var datalen: int
if udata.test == "CHECK" and datalen == MSG_LEN and transp.peekMessage(data, datalen)
equalMem(addr data[0], addr expect[0], datalen): if udata.test == "CHECK" and datalen == MSG_LEN and
udata.test = "OK" equalMem(addr data[0], addr expect[0], datalen):
transp.close() udata.test = "OK"
transp.close()
except CatchableError as exc:
raiseAssert exc.msg
proc issue6(): Future[bool] {.async.} = proc issue6(): Future[bool] {.async.} =
var myself = initTAddress("127.0.0.1:" & $HELLO_PORT) var myself = initTAddress("127.0.0.1:" & $HELLO_PORT)

View File

@ -30,286 +30,319 @@ suite "Datagram Transport test suite":
" clients x " & $MessagesCount & " messages)" " clients x " & $MessagesCount & " messages)"
proc client1(transp: DatagramTransport, proc client1(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
var pbytes = transp.getMessage() try:
var nbytes = len(pbytes) var pbytes = transp.getMessage()
if nbytes > 0: var nbytes = len(pbytes)
var data = newString(nbytes + 1) if nbytes > 0:
copyMem(addr data[0], addr pbytes[0], nbytes) var data = newString(nbytes + 1)
data.setLen(nbytes) copyMem(addr data[0], addr pbytes[0], nbytes)
if data.startsWith("REQUEST"): data.setLen(nbytes)
var numstr = data[7..^1] if data.startsWith("REQUEST"):
var num = parseInt(numstr) var numstr = data[7..^1]
var ans = "ANSWER" & $num var num = parseInt(numstr)
await transp.sendTo(raddr, addr ans[0], len(ans)) var ans = "ANSWER" & $num
await transp.sendTo(raddr, addr ans[0], len(ans))
else:
var err = "ERROR"
await transp.sendTo(raddr, addr err[0], len(err))
else: else:
var err = "ERROR" var counterPtr = cast[ptr int](transp.udata)
await transp.sendTo(raddr, addr err[0], len(err)) counterPtr[] = -1
else: transp.close()
var counterPtr = cast[ptr int](transp.udata) except CatchableError as exc:
counterPtr[] = -1 raiseAssert exc.msg
transp.close()
proc client2(transp: DatagramTransport, proc client2(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
var pbytes = transp.getMessage() try:
var nbytes = len(pbytes) var pbytes = transp.getMessage()
if nbytes > 0: var nbytes = len(pbytes)
var data = newString(nbytes + 1) if nbytes > 0:
copyMem(addr data[0], addr pbytes[0], nbytes) var data = newString(nbytes + 1)
data.setLen(nbytes) copyMem(addr data[0], addr pbytes[0], nbytes)
if data.startsWith("ANSWER"): data.setLen(nbytes)
var counterPtr = cast[ptr int](transp.udata) if data.startsWith("ANSWER"):
counterPtr[] = counterPtr[] + 1 var counterPtr = cast[ptr int](transp.udata)
if counterPtr[] == TestsCount: counterPtr[] = counterPtr[] + 1
transp.close() if counterPtr[] == TestsCount:
transp.close()
else:
var ta = initTAddress("127.0.0.1:33336")
var req = "REQUEST" & $counterPtr[]
await transp.sendTo(ta, addr req[0], len(req))
else: else:
var ta = initTAddress("127.0.0.1:33336") var counterPtr = cast[ptr int](transp.udata)
var req = "REQUEST" & $counterPtr[] counterPtr[] = -1
await transp.sendTo(ta, addr req[0], len(req)) transp.close()
else: else:
## Read operation failed with error
var counterPtr = cast[ptr int](transp.udata) var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1 counterPtr[] = -1
transp.close() transp.close()
else: except CatchableError as exc:
## Read operation failed with error raiseAssert exc.msg
var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1
transp.close()
proc client3(transp: DatagramTransport, proc client3(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
var pbytes = transp.getMessage() try:
var nbytes = len(pbytes) var pbytes = transp.getMessage()
if nbytes > 0: var nbytes = len(pbytes)
var data = newString(nbytes + 1) if nbytes > 0:
copyMem(addr data[0], addr pbytes[0], nbytes) var data = newString(nbytes + 1)
data.setLen(nbytes) copyMem(addr data[0], addr pbytes[0], nbytes)
if data.startsWith("ANSWER"): data.setLen(nbytes)
var counterPtr = cast[ptr int](transp.udata) if data.startsWith("ANSWER"):
counterPtr[] = counterPtr[] + 1 var counterPtr = cast[ptr int](transp.udata)
if counterPtr[] == TestsCount: counterPtr[] = counterPtr[] + 1
transp.close() if counterPtr[] == TestsCount:
transp.close()
else:
var req = "REQUEST" & $counterPtr[]
await transp.send(addr req[0], len(req))
else: else:
var req = "REQUEST" & $counterPtr[] var counterPtr = cast[ptr int](transp.udata)
await transp.send(addr req[0], len(req)) counterPtr[] = -1
transp.close()
else: else:
## Read operation failed with error
var counterPtr = cast[ptr int](transp.udata) var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1 counterPtr[] = -1
transp.close() transp.close()
else: except CatchableError as exc:
## Read operation failed with error raiseAssert exc.msg
var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1
transp.close()
proc client4(transp: DatagramTransport, proc client4(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
var pbytes = transp.getMessage() try:
var nbytes = len(pbytes) var pbytes = transp.getMessage()
if nbytes > 0: var nbytes = len(pbytes)
var data = newString(nbytes + 1) if nbytes > 0:
copyMem(addr data[0], addr pbytes[0], nbytes) var data = newString(nbytes + 1)
data.setLen(nbytes) copyMem(addr data[0], addr pbytes[0], nbytes)
if data.startsWith("ANSWER"): data.setLen(nbytes)
var counterPtr = cast[ptr int](transp.udata) if data.startsWith("ANSWER"):
counterPtr[] = counterPtr[] + 1 var counterPtr = cast[ptr int](transp.udata)
if counterPtr[] == MessagesCount: counterPtr[] = counterPtr[] + 1
transp.close() if counterPtr[] == MessagesCount:
transp.close()
else:
var req = "REQUEST" & $counterPtr[]
await transp.send(addr req[0], len(req))
else: else:
var req = "REQUEST" & $counterPtr[] var counterPtr = cast[ptr int](transp.udata)
await transp.send(addr req[0], len(req)) counterPtr[] = -1
transp.close()
else: else:
## Read operation failed with error
var counterPtr = cast[ptr int](transp.udata) var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1 counterPtr[] = -1
transp.close() transp.close()
else: except CatchableError as exc:
## Read operation failed with error raiseAssert exc.msg
var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1
transp.close()
proc client5(transp: DatagramTransport, proc client5(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
var pbytes = transp.getMessage() try:
var nbytes = len(pbytes) var pbytes = transp.getMessage()
if nbytes > 0: var nbytes = len(pbytes)
var data = newString(nbytes + 1) if nbytes > 0:
copyMem(addr data[0], addr pbytes[0], nbytes) var data = newString(nbytes + 1)
data.setLen(nbytes) copyMem(addr data[0], addr pbytes[0], nbytes)
if data.startsWith("ANSWER"): data.setLen(nbytes)
var counterPtr = cast[ptr int](transp.udata) if data.startsWith("ANSWER"):
counterPtr[] = counterPtr[] + 1 var counterPtr = cast[ptr int](transp.udata)
if counterPtr[] == MessagesCount: counterPtr[] = counterPtr[] + 1
transp.close() if counterPtr[] == MessagesCount:
transp.close()
else:
var req = "REQUEST" & $counterPtr[]
await transp.sendTo(raddr, addr req[0], len(req))
else: else:
var req = "REQUEST" & $counterPtr[] var counterPtr = cast[ptr int](transp.udata)
await transp.sendTo(raddr, addr req[0], len(req)) counterPtr[] = -1
transp.close()
else: else:
## Read operation failed with error
var counterPtr = cast[ptr int](transp.udata) var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1 counterPtr[] = -1
transp.close() transp.close()
else: except CatchableError as exc:
## Read operation failed with error raiseAssert exc.msg
var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1
transp.close()
proc client6(transp: DatagramTransport, proc client6(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
var pbytes = transp.getMessage() try:
var nbytes = len(pbytes) var pbytes = transp.getMessage()
if nbytes > 0: var nbytes = len(pbytes)
var data = newString(nbytes + 1) if nbytes > 0:
copyMem(addr data[0], addr pbytes[0], nbytes) var data = newString(nbytes + 1)
data.setLen(nbytes) copyMem(addr data[0], addr pbytes[0], nbytes)
if data.startsWith("REQUEST"): data.setLen(nbytes)
var numstr = data[7..^1] if data.startsWith("REQUEST"):
var num = parseInt(numstr) var numstr = data[7..^1]
var ans = "ANSWER" & $num var num = parseInt(numstr)
await transp.sendTo(raddr, ans) var ans = "ANSWER" & $num
await transp.sendTo(raddr, ans)
else:
var err = "ERROR"
await transp.sendTo(raddr, err)
else: else:
var err = "ERROR" ## Read operation failed with error
await transp.sendTo(raddr, err) var counterPtr = cast[ptr int](transp.udata)
else: counterPtr[] = -1
## Read operation failed with error transp.close()
var counterPtr = cast[ptr int](transp.udata) except CatchableError as exc:
counterPtr[] = -1 raiseAssert exc.msg
transp.close()
proc client7(transp: DatagramTransport, proc client7(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
var pbytes = transp.getMessage() try:
var nbytes = len(pbytes) var pbytes = transp.getMessage()
if nbytes > 0: var nbytes = len(pbytes)
var data = newString(nbytes + 1) if nbytes > 0:
copyMem(addr data[0], addr pbytes[0], nbytes) var data = newString(nbytes + 1)
data.setLen(nbytes) copyMem(addr data[0], addr pbytes[0], nbytes)
if data.startsWith("ANSWER"): data.setLen(nbytes)
var counterPtr = cast[ptr int](transp.udata) if data.startsWith("ANSWER"):
counterPtr[] = counterPtr[] + 1 var counterPtr = cast[ptr int](transp.udata)
if counterPtr[] == TestsCount: counterPtr[] = counterPtr[] + 1
transp.close() if counterPtr[] == TestsCount:
transp.close()
else:
var req = "REQUEST" & $counterPtr[]
await transp.sendTo(raddr, req)
else: else:
var req = "REQUEST" & $counterPtr[] var counterPtr = cast[ptr int](transp.udata)
await transp.sendTo(raddr, req) counterPtr[] = -1
transp.close()
else: else:
## Read operation failed with error
var counterPtr = cast[ptr int](transp.udata) var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1 counterPtr[] = -1
transp.close() transp.close()
else: except CatchableError as exc:
## Read operation failed with error raiseAssert exc.msg
var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1
transp.close()
proc client8(transp: DatagramTransport, proc client8(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
var pbytes = transp.getMessage() try:
var nbytes = len(pbytes) var pbytes = transp.getMessage()
if nbytes > 0: var nbytes = len(pbytes)
var data = newString(nbytes + 1) if nbytes > 0:
copyMem(addr data[0], addr pbytes[0], nbytes) var data = newString(nbytes + 1)
data.setLen(nbytes) copyMem(addr data[0], addr pbytes[0], nbytes)
if data.startsWith("ANSWER"): data.setLen(nbytes)
var counterPtr = cast[ptr int](transp.udata) if data.startsWith("ANSWER"):
counterPtr[] = counterPtr[] + 1 var counterPtr = cast[ptr int](transp.udata)
if counterPtr[] == TestsCount: counterPtr[] = counterPtr[] + 1
transp.close() if counterPtr[] == TestsCount:
transp.close()
else:
var req = "REQUEST" & $counterPtr[]
await transp.send(req)
else: else:
var req = "REQUEST" & $counterPtr[] var counterPtr = cast[ptr int](transp.udata)
await transp.send(req) counterPtr[] = -1
transp.close()
else: else:
## Read operation failed with error
var counterPtr = cast[ptr int](transp.udata) var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1 counterPtr[] = -1
transp.close() transp.close()
else: except CatchableError as exc:
## Read operation failed with error raiseAssert exc.msg
var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1
transp.close()
proc client9(transp: DatagramTransport, proc client9(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
var pbytes = transp.getMessage() try:
var nbytes = len(pbytes) var pbytes = transp.getMessage()
if nbytes > 0: var nbytes = len(pbytes)
var data = newString(nbytes + 1) if nbytes > 0:
copyMem(addr data[0], addr pbytes[0], nbytes) var data = newString(nbytes + 1)
data.setLen(nbytes) copyMem(addr data[0], addr pbytes[0], nbytes)
if data.startsWith("REQUEST"): data.setLen(nbytes)
var numstr = data[7..^1] if data.startsWith("REQUEST"):
var num = parseInt(numstr) var numstr = data[7..^1]
var ans = "ANSWER" & $num var num = parseInt(numstr)
var ansseq = newSeq[byte](len(ans)) var ans = "ANSWER" & $num
copyMem(addr ansseq[0], addr ans[0], len(ans)) var ansseq = newSeq[byte](len(ans))
await transp.sendTo(raddr, ansseq) copyMem(addr ansseq[0], addr ans[0], len(ans))
await transp.sendTo(raddr, ansseq)
else:
var err = "ERROR"
var errseq = newSeq[byte](len(err))
copyMem(addr errseq[0], addr err[0], len(err))
await transp.sendTo(raddr, errseq)
else: else:
var err = "ERROR" ## Read operation failed with error
var errseq = newSeq[byte](len(err)) var counterPtr = cast[ptr int](transp.udata)
copyMem(addr errseq[0], addr err[0], len(err)) counterPtr[] = -1
await transp.sendTo(raddr, errseq) transp.close()
else: except CatchableError as exc:
## Read operation failed with error raiseAssert exc.msg
var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1
transp.close()
proc client10(transp: DatagramTransport, proc client10(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
var pbytes = transp.getMessage() try:
var nbytes = len(pbytes) var pbytes = transp.getMessage()
if nbytes > 0: var nbytes = len(pbytes)
var data = newString(nbytes + 1) if nbytes > 0:
copyMem(addr data[0], addr pbytes[0], nbytes) var data = newString(nbytes + 1)
data.setLen(nbytes) copyMem(addr data[0], addr pbytes[0], nbytes)
if data.startsWith("ANSWER"): data.setLen(nbytes)
var counterPtr = cast[ptr int](transp.udata) if data.startsWith("ANSWER"):
counterPtr[] = counterPtr[] + 1 var counterPtr = cast[ptr int](transp.udata)
if counterPtr[] == TestsCount: counterPtr[] = counterPtr[] + 1
transp.close() if counterPtr[] == TestsCount:
transp.close()
else:
var req = "REQUEST" & $counterPtr[]
var reqseq = newSeq[byte](len(req))
copyMem(addr reqseq[0], addr req[0], len(req))
await transp.sendTo(raddr, reqseq)
else: else:
var req = "REQUEST" & $counterPtr[] var counterPtr = cast[ptr int](transp.udata)
var reqseq = newSeq[byte](len(req)) counterPtr[] = -1
copyMem(addr reqseq[0], addr req[0], len(req)) transp.close()
await transp.sendTo(raddr, reqseq)
else: else:
## Read operation failed with error
var counterPtr = cast[ptr int](transp.udata) var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1 counterPtr[] = -1
transp.close() transp.close()
else: except CatchableError as exc:
## Read operation failed with error raiseAssert exc.msg
var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1
transp.close()
proc client11(transp: DatagramTransport, proc client11(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
var pbytes = transp.getMessage() try:
var nbytes = len(pbytes) var pbytes = transp.getMessage()
if nbytes > 0: var nbytes = len(pbytes)
var data = newString(nbytes + 1) if nbytes > 0:
copyMem(addr data[0], addr pbytes[0], nbytes) var data = newString(nbytes + 1)
data.setLen(nbytes) copyMem(addr data[0], addr pbytes[0], nbytes)
if data.startsWith("ANSWER"): data.setLen(nbytes)
var counterPtr = cast[ptr int](transp.udata) if data.startsWith("ANSWER"):
counterPtr[] = counterPtr[] + 1 var counterPtr = cast[ptr int](transp.udata)
if counterPtr[] == TestsCount: counterPtr[] = counterPtr[] + 1
transp.close() if counterPtr[] == TestsCount:
transp.close()
else:
var req = "REQUEST" & $counterPtr[]
var reqseq = newSeq[byte](len(req))
copyMem(addr reqseq[0], addr req[0], len(req))
await transp.send(reqseq)
else: else:
var req = "REQUEST" & $counterPtr[] var counterPtr = cast[ptr int](transp.udata)
var reqseq = newSeq[byte](len(req)) counterPtr[] = -1
copyMem(addr reqseq[0], addr req[0], len(req)) transp.close()
await transp.send(reqseq)
else: else:
## Read operation failed with error
var counterPtr = cast[ptr int](transp.udata) var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1 counterPtr[] = -1
transp.close() transp.close()
else: except CatchableError as exc:
## Read operation failed with error raiseAssert exc.msg
var counterPtr = cast[ptr int](transp.udata)
counterPtr[] = -1
transp.close()
proc testPointerSendTo(): Future[int] {.async.} = proc testPointerSendTo(): Future[int] {.async.} =
## sendTo(pointer) test ## sendTo(pointer) test
@ -439,7 +472,7 @@ suite "Datagram Transport test suite":
var ta = initTAddress("127.0.0.1:0") var ta = initTAddress("127.0.0.1:0")
var counter = 0 var counter = 0
proc clientMark(transp: DatagramTransport, proc clientMark(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
counter = 1 counter = 1
transp.close() transp.close()
var dgram1 = newDatagramTransport(client1, local = ta) var dgram1 = newDatagramTransport(client1, local = ta)
@ -457,7 +490,7 @@ suite "Datagram Transport test suite":
proc testTransportClose(): Future[bool] {.async.} = proc testTransportClose(): Future[bool] {.async.} =
var ta = initTAddress("127.0.0.1:45000") var ta = initTAddress("127.0.0.1:45000")
proc clientMark(transp: DatagramTransport, proc clientMark(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
discard discard
var dgram = newDatagramTransport(clientMark, local = ta) var dgram = newDatagramTransport(clientMark, local = ta)
dgram.close() dgram.close()
@ -473,12 +506,15 @@ suite "Datagram Transport test suite":
var bta = initTAddress("255.255.255.255:45010") var bta = initTAddress("255.255.255.255:45010")
var res = 0 var res = 0
proc clientMark(transp: DatagramTransport, proc clientMark(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
var bmsg = transp.getMessage() try:
var smsg = string.fromBytes(bmsg) var bmsg = transp.getMessage()
if smsg == expectMessage: var smsg = string.fromBytes(bmsg)
inc(res) if smsg == expectMessage:
transp.close() inc(res)
transp.close()
except CatchableError as exc:
raiseAssert exc.msg
var dgram1 = newDatagramTransport(clientMark, local = ta1, var dgram1 = newDatagramTransport(clientMark, local = ta1,
flags = {Broadcast}, ttl = 2) flags = {Broadcast}, ttl = 2)
await dgram1.sendTo(bta, expectMessage) await dgram1.sendTo(bta, expectMessage)
@ -493,15 +529,19 @@ suite "Datagram Transport test suite":
var event = newAsyncEvent() var event = newAsyncEvent()
proc clientMark1(transp: DatagramTransport, proc clientMark1(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
var bmsg = transp.getMessage() try:
var smsg = string.fromBytes(bmsg) var bmsg = transp.getMessage()
if smsg == expectStr: var smsg = string.fromBytes(bmsg)
inc(res) if smsg == expectStr:
event.fire() inc(res)
event.fire()
except CatchableError as exc:
raiseAssert exc.msg
proc clientMark2(transp: DatagramTransport, proc clientMark2(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
discard discard
var dgram1 = newDatagramTransport(clientMark1, local = ta) var dgram1 = newDatagramTransport(clientMark1, local = ta)
@ -544,15 +584,18 @@ suite "Datagram Transport test suite":
res = 0 res = 0
proc process1(transp: DatagramTransport, proc process1(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
var bmsg = transp.getMessage() try:
var smsg = string.fromBytes(bmsg) var bmsg = transp.getMessage()
if smsg == expectStr: var smsg = string.fromBytes(bmsg)
inc(res) if smsg == expectStr:
event.fire() inc(res)
event.fire()
except CatchableError as exc:
raiseAssert exc.msg
proc process2(transp: DatagramTransport, proc process2(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async.} = raddr: TransportAddress): Future[void] {.async: (raises: []).} =
discard discard
let let

View File

@ -459,20 +459,31 @@ suite "Exceptions tracking":
check waitFor(test1()) == 12 check waitFor(test1()) == 12
proc test2: Future[int] {.async: (raw: true, raises: [IOError, OSError]).} = proc test2: Future[int] {.async: (raw: true, raises: [IOError, OSError]).} =
checkNotCompiles:
result.fail(newException(ValueError, "fail"))
result = newFuture[int]() result = newFuture[int]()
result.fail(newException(IOError, "fail")) result.fail(newException(IOError, "fail"))
result.fail(newException(OSError, "fail"))
checkNotCompiles:
result.fail(newException(ValueError, "fail"))
proc test3: Future[void] {.async: (raw: true, raises: []).} = proc test3: Future[void] {.async: (raw: true, raises: []).} =
result = newFuture[void]()
checkNotCompiles: checkNotCompiles:
result.fail(newException(ValueError, "fail")) result.fail(newException(ValueError, "fail"))
result.complete()
# Inheritance # Inheritance
proc test4: Future[void] {.async: (raw: true, raises: [CatchableError]).} = proc test4: Future[void] {.async: (raw: true, raises: [CatchableError]).} =
result = newFuture[void]()
result.fail(newException(IOError, "fail")) result.fail(newException(IOError, "fail"))
check:
waitFor(test1()) == 12
expect(IOError):
discard waitFor(test2())
waitFor(test3())
expect(IOError):
waitFor(test4())
test "or errors": test "or errors":
proc testit {.async: (raises: [ValueError]).} = proc testit {.async: (raises: [ValueError]).} =
raise (ref ValueError)() raise (ref ValueError)()

View File

@ -27,29 +27,36 @@ suite "Server's test suite":
checkLeaks() checkLeaks()
proc serveStreamClient(server: StreamServer, proc serveStreamClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
discard discard
proc serveCustomStreamClient(server: StreamServer, proc serveCustomStreamClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var cserver = cast[CustomServer](server) try:
var ctransp = cast[CustomTransport](transp) var cserver = cast[CustomServer](server)
cserver.test1 = "CONNECTION" var ctransp = cast[CustomTransport](transp)
cserver.test2 = ctransp.test cserver.test1 = "CONNECTION"
cserver.test3 = await transp.readLine() cserver.test2 = ctransp.test
var answer = "ANSWER\r\n" cserver.test3 = await transp.readLine()
discard await transp.write(answer) var answer = "ANSWER\r\n"
transp.close() discard await transp.write(answer)
await transp.join() transp.close()
await transp.join()
except CatchableError as exc:
raiseAssert exc.msg
proc serveUdataStreamClient(server: StreamServer, proc serveUdataStreamClient(server: StreamServer,
transp: StreamTransport) {.async.} = transp: StreamTransport) {.async: (raises: []).} =
var udata = getUserData[CustomData](server) try:
var line = await transp.readLine() var udata = getUserData[CustomData](server)
var msg = line & udata.test & "\r\n" var line = await transp.readLine()
discard await transp.write(msg) var msg = line & udata.test & "\r\n"
transp.close() discard await transp.write(msg)
await transp.join() transp.close()
await transp.join()
except CatchableError as exc:
raiseAssert exc.msg
proc customServerTransport(server: StreamServer, proc customServerTransport(server: StreamServer,
fd: AsyncFD): StreamTransport = fd: AsyncFD): StreamTransport =

View File

@ -55,124 +55,148 @@ suite "Stream Transport test suite":
for i in 0 ..< len(result): for i in 0 ..< len(result):
result[i] = byte(message[i mod len(message)]) result[i] = byte(message[i mod len(message)])
proc serveClient1(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient1(server: StreamServer, transp: StreamTransport) {.
while not transp.atEof(): async: (raises: []).} =
var data = await transp.readLine() try:
if len(data) == 0: while not transp.atEof():
doAssert(transp.atEof()) var data = await transp.readLine()
break if len(data) == 0:
doAssert(data.startsWith("REQUEST")) doAssert(transp.atEof())
var numstr = data[7..^1] break
var num = parseInt(numstr) doAssert(data.startsWith("REQUEST"))
var ans = "ANSWER" & $num & "\r\n" var numstr = data[7..^1]
var res = await transp.write(cast[pointer](addr ans[0]), len(ans)) var num = parseInt(numstr)
doAssert(res == len(ans)) var ans = "ANSWER" & $num & "\r\n"
transp.close() var res = await transp.write(cast[pointer](addr ans[0]), len(ans))
await transp.join() doAssert(res == len(ans))
transp.close()
await transp.join()
except CatchableError as exc:
raiseAssert exc.msg
proc serveClient2(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient2(server: StreamServer, transp: StreamTransport) {.
var buffer: array[20, char] async: (raises: []).} =
var check = "REQUEST" try:
while not transp.atEof(): var buffer: array[20, char]
zeroMem(addr buffer[0], MessageSize) var check = "REQUEST"
try: while not transp.atEof():
await transp.readExactly(addr buffer[0], MessageSize) zeroMem(addr buffer[0], MessageSize)
except TransportIncompleteError: try:
break await transp.readExactly(addr buffer[0], MessageSize)
doAssert(equalMem(addr buffer[0], addr check[0], len(check))) except TransportIncompleteError:
var numstr = "" break
var i = 7 doAssert(equalMem(addr buffer[0], addr check[0], len(check)))
while i < MessageSize and (buffer[i] in {'0'..'9'}): var numstr = ""
numstr.add(buffer[i]) var i = 7
inc(i) while i < MessageSize and (buffer[i] in {'0'..'9'}):
var num = parseInt(numstr) numstr.add(buffer[i])
var ans = "ANSWER" & $num inc(i)
zeroMem(addr buffer[0], MessageSize) var num = parseInt(numstr)
copyMem(addr buffer[0], addr ans[0], len(ans)) var ans = "ANSWER" & $num
var res = await transp.write(cast[pointer](addr buffer[0]), MessageSize) zeroMem(addr buffer[0], MessageSize)
doAssert(res == MessageSize) copyMem(addr buffer[0], addr ans[0], len(ans))
transp.close() var res = await transp.write(cast[pointer](addr buffer[0]), MessageSize)
await transp.join() doAssert(res == MessageSize)
transp.close()
await transp.join()
except CatchableError as exc:
raiseAssert exc.msg
proc serveClient3(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient3(server: StreamServer, transp: StreamTransport) {.
var buffer: array[20, char] async: (raises: []).} =
var check = "REQUEST" try:
var suffixStr = "SUFFIX" var buffer: array[20, char]
var suffix = newSeq[byte](6) var check = "REQUEST"
copyMem(addr suffix[0], addr suffixStr[0], len(suffixStr)) var suffixStr = "SUFFIX"
var counter = MessagesCount var suffix = newSeq[byte](6)
while counter > 0: copyMem(addr suffix[0], addr suffixStr[0], len(suffixStr))
zeroMem(addr buffer[0], MessageSize) var counter = MessagesCount
var res = await transp.readUntil(addr buffer[0], MessageSize, suffix) while counter > 0:
doAssert(equalMem(addr buffer[0], addr check[0], len(check))) zeroMem(addr buffer[0], MessageSize)
var numstr = "" var res = await transp.readUntil(addr buffer[0], MessageSize, suffix)
var i = 7 doAssert(equalMem(addr buffer[0], addr check[0], len(check)))
while i < MessageSize and (buffer[i] in {'0'..'9'}): var numstr = ""
numstr.add(buffer[i]) var i = 7
inc(i) while i < MessageSize and (buffer[i] in {'0'..'9'}):
var num = parseInt(numstr) numstr.add(buffer[i])
doAssert(len(numstr) < 8) inc(i)
var ans = "ANSWER" & $num & "SUFFIX" var num = parseInt(numstr)
zeroMem(addr buffer[0], MessageSize) doAssert(len(numstr) < 8)
copyMem(addr buffer[0], addr ans[0], len(ans)) var ans = "ANSWER" & $num & "SUFFIX"
res = await transp.write(cast[pointer](addr buffer[0]), len(ans)) zeroMem(addr buffer[0], MessageSize)
doAssert(res == len(ans)) copyMem(addr buffer[0], addr ans[0], len(ans))
dec(counter) res = await transp.write(cast[pointer](addr buffer[0]), len(ans))
transp.close() doAssert(res == len(ans))
await transp.join() dec(counter)
transp.close()
await transp.join()
except CatchableError as exc:
raiseAssert exc.msg
proc serveClient4(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient4(server: StreamServer, transp: StreamTransport) {.
var pathname = await transp.readLine() async: (raises: []).} =
var size = await transp.readLine() try:
var sizeNum = parseInt(size) var pathname = await transp.readLine()
doAssert(sizeNum >= 0) var size = await transp.readLine()
var rbuffer = newSeq[byte](sizeNum) var sizeNum = parseInt(size)
await transp.readExactly(addr rbuffer[0], sizeNum) doAssert(sizeNum >= 0)
var lbuffer = readFile(pathname) var rbuffer = newSeq[byte](sizeNum)
doAssert(len(lbuffer) == sizeNum) await transp.readExactly(addr rbuffer[0], sizeNum)
doAssert(equalMem(addr rbuffer[0], addr lbuffer[0], sizeNum)) var lbuffer = readFile(pathname)
var answer = "OK\r\n" doAssert(len(lbuffer) == sizeNum)
var res = await transp.write(cast[pointer](addr answer[0]), len(answer)) doAssert(equalMem(addr rbuffer[0], addr lbuffer[0], sizeNum))
doAssert(res == len(answer)) var answer = "OK\r\n"
transp.close() var res = await transp.write(cast[pointer](addr answer[0]), len(answer))
await transp.join() doAssert(res == len(answer))
transp.close()
await transp.join()
except CatchableError as exc:
raiseAssert exc.msg
proc serveClient7(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient7(server: StreamServer, transp: StreamTransport) {.
var answer = "DONE\r\n" async: (raises: []).} =
var expect = "" try:
var line = await transp.readLine() var answer = "DONE\r\n"
doAssert(len(line) == BigMessageCount * len(BigMessagePattern)) var expect = ""
for i in 0..<BigMessageCount: var line = await transp.readLine()
expect.add(BigMessagePattern) doAssert(len(line) == BigMessageCount * len(BigMessagePattern))
doAssert(line == expect) for i in 0..<BigMessageCount:
var res = await transp.write(answer) expect.add(BigMessagePattern)
doAssert(res == len(answer)) doAssert(line == expect)
transp.close() var res = await transp.write(answer)
await transp.join() doAssert(res == len(answer))
server.stop() transp.close()
server.close() await transp.join()
server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
proc serveClient8(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient8(server: StreamServer, transp: StreamTransport) {.
var answer = "DONE\r\n" async: (raises: []).} =
var strpattern = BigMessagePattern try:
var pattern = newSeq[byte](len(BigMessagePattern)) var answer = "DONE\r\n"
var expect = newSeq[byte]() var strpattern = BigMessagePattern
var data = newSeq[byte]((BigMessageCount + 1) * len(BigMessagePattern)) var pattern = newSeq[byte](len(BigMessagePattern))
var sep = @[0x0D'u8, 0x0A'u8] var expect = newSeq[byte]()
copyMem(addr pattern[0], addr strpattern[0], len(BigMessagePattern)) var data = newSeq[byte]((BigMessageCount + 1) * len(BigMessagePattern))
var count = await transp.readUntil(addr data[0], len(data), sep = sep) var sep = @[0x0D'u8, 0x0A'u8]
doAssert(count == BigMessageCount * len(BigMessagePattern) + 2) copyMem(addr pattern[0], addr strpattern[0], len(BigMessagePattern))
for i in 0..<BigMessageCount: var count = await transp.readUntil(addr data[0], len(data), sep = sep)
expect.add(pattern) doAssert(count == BigMessageCount * len(BigMessagePattern) + 2)
expect.add(sep) for i in 0..<BigMessageCount:
data.setLen(count) expect.add(pattern)
doAssert(expect == data) expect.add(sep)
var res = await transp.write(answer) data.setLen(count)
doAssert(res == len(answer)) doAssert(expect == data)
transp.close() var res = await transp.write(answer)
await transp.join() doAssert(res == len(answer))
server.stop() transp.close()
server.close() await transp.join()
server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
proc swarmWorker1(address: TransportAddress): Future[int] {.async.} = proc swarmWorker1(address: TransportAddress): Future[int] {.async.} =
var transp = await connect(address) var transp = await connect(address)
@ -399,18 +423,22 @@ suite "Stream Transport test suite":
var res = workers[i].read() var res = workers[i].read()
result += res result += res
proc serveClient(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient(server: StreamServer, transp: StreamTransport) {.
var data = await transp.read() async: (raises: []).} =
doAssert(len(data) == len(ConstantMessage) * MessagesCount) try:
transp.close() var data = await transp.read()
var expect = "" doAssert(len(data) == len(ConstantMessage) * MessagesCount)
for i in 0..<MessagesCount: transp.close()
expect.add(ConstantMessage) var expect = ""
doAssert(equalMem(addr expect[0], addr data[0], len(data))) for i in 0..<MessagesCount:
dec(counter) expect.add(ConstantMessage)
if counter == 0: doAssert(equalMem(addr expect[0], addr data[0], len(data)))
server.stop() dec(counter)
server.close() if counter == 0:
server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(address, serveClient, {ReuseAddr}) var server = createStreamServer(address, serveClient, {ReuseAddr})
server.start() server.start()
@ -420,18 +448,22 @@ suite "Stream Transport test suite":
proc testWCR(address: TransportAddress): Future[int] {.async.} = proc testWCR(address: TransportAddress): Future[int] {.async.} =
var counter = ClientsCount var counter = ClientsCount
proc serveClient(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient(server: StreamServer, transp: StreamTransport) {.
var expect = ConstantMessage async: (raises: []).} =
var skip = await transp.consume(len(ConstantMessage) * (MessagesCount - 1)) try:
doAssert(skip == len(ConstantMessage) * (MessagesCount - 1)) var expect = ConstantMessage
var data = await transp.read() var skip = await transp.consume(len(ConstantMessage) * (MessagesCount - 1))
doAssert(len(data) == len(ConstantMessage)) doAssert(skip == len(ConstantMessage) * (MessagesCount - 1))
transp.close() var data = await transp.read()
doAssert(equalMem(addr data[0], addr expect[0], len(expect))) doAssert(len(data) == len(ConstantMessage))
dec(counter) transp.close()
if counter == 0: doAssert(equalMem(addr data[0], addr expect[0], len(expect)))
server.stop() dec(counter)
server.close() if counter == 0:
server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
proc swarmWorker(address: TransportAddress): Future[int] {.async.} = proc swarmWorker(address: TransportAddress): Future[int] {.async.} =
var transp = await connect(address) var transp = await connect(address)
@ -534,11 +566,15 @@ suite "Stream Transport test suite":
# server.close() # server.close()
# await server.join() # await server.join()
proc serveClient11(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient11(server: StreamServer, transp: StreamTransport) {.
var res = await transp.write(BigMessagePattern) async: (raises: []).} =
doAssert(res == len(BigMessagePattern)) try:
transp.close() var res = await transp.write(BigMessagePattern)
await transp.join() doAssert(res == len(BigMessagePattern))
transp.close()
await transp.join()
except CatchableError as exc:
raiseAssert exc.msg
proc swarmWorker11(address: TransportAddress): Future[int] {.async.} = proc swarmWorker11(address: TransportAddress): Future[int] {.async.} =
var buffer: array[len(BigMessagePattern) + 1, byte] var buffer: array[len(BigMessagePattern) + 1, byte]
@ -558,11 +594,15 @@ suite "Stream Transport test suite":
server.close() server.close()
await server.join() await server.join()
proc serveClient12(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient12(server: StreamServer, transp: StreamTransport) {.
var res = await transp.write(BigMessagePattern) async: (raises: []).} =
doAssert(res == len(BigMessagePattern)) try:
transp.close() var res = await transp.write(BigMessagePattern)
await transp.join() doAssert(res == len(BigMessagePattern))
transp.close()
await transp.join()
except CatchableError as exc:
raiseAssert exc.msg
proc swarmWorker12(address: TransportAddress): Future[int] {.async.} = proc swarmWorker12(address: TransportAddress): Future[int] {.async.} =
var buffer: array[len(BigMessagePattern), byte] var buffer: array[len(BigMessagePattern), byte]
@ -584,9 +624,13 @@ suite "Stream Transport test suite":
server.close() server.close()
await server.join() await server.join()
proc serveClient13(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient13(server: StreamServer, transp: StreamTransport) {.
transp.close() async: (raises: []).} =
await transp.join() try:
transp.close()
await transp.join()
except CatchableError as exc:
raiseAssert exc.msg
proc swarmWorker13(address: TransportAddress): Future[int] {.async.} = proc swarmWorker13(address: TransportAddress): Future[int] {.async.} =
var transp = await connect(address) var transp = await connect(address)
@ -645,11 +689,15 @@ suite "Stream Transport test suite":
else: else:
return (e.code == oserrno.ECONNREFUSED) or (e.code == oserrno.ENOENT) return (e.code == oserrno.ECONNREFUSED) or (e.code == oserrno.ENOENT)
proc serveClient16(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient16(server: StreamServer, transp: StreamTransport) {.
var res = await transp.write(BigMessagePattern) async: (raises: []).} =
doAssert(res == len(BigMessagePattern)) try:
transp.close() var res = await transp.write(BigMessagePattern)
await transp.join() doAssert(res == len(BigMessagePattern))
transp.close()
await transp.join()
except CatchableError as exc:
raiseAssert exc.msg
proc swarmWorker16(address: TransportAddress): Future[int] {.async.} = proc swarmWorker16(address: TransportAddress): Future[int] {.async.} =
var buffer = newString(5) var buffer = newString(5)
@ -680,7 +728,8 @@ suite "Stream Transport test suite":
await server.join() await server.join()
proc testCloseTransport(address: TransportAddress): Future[int] {.async.} = proc testCloseTransport(address: TransportAddress): Future[int] {.async.} =
proc client(server: StreamServer, transp: StreamTransport) {.async.} = proc client(server: StreamServer, transp: StreamTransport) {.
async: (raises: []).} =
discard discard
var server = createStreamServer(address, client, {ReuseAddr}) var server = createStreamServer(address, client, {ReuseAddr})
server.start() server.start()
@ -694,9 +743,12 @@ suite "Stream Transport test suite":
proc testWriteConnReset(address: TransportAddress): Future[int] {.async.} = proc testWriteConnReset(address: TransportAddress): Future[int] {.async.} =
var syncFut = newFuture[void]() var syncFut = newFuture[void]()
proc client(server: StreamServer, transp: StreamTransport) {.async.} = proc client(server: StreamServer, transp: StreamTransport) {.async: (raises: []).} =
await transp.closeWait() try:
syncFut.complete() await transp.closeWait()
syncFut.complete()
except CatchableError as exc:
raiseAssert exc.msg
var n = 10 var n = 10
var server = createStreamServer(address, client, {ReuseAddr}) var server = createStreamServer(address, client, {ReuseAddr})
server.start() server.start()
@ -721,12 +773,16 @@ suite "Stream Transport test suite":
var serverRemote, serverLocal: TransportAddress var serverRemote, serverLocal: TransportAddress
var connRemote, connLocal: TransportAddress var connRemote, connLocal: TransportAddress
proc serveClient(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient(server: StreamServer, transp: StreamTransport) {.
serverRemote = transp.remoteAddress() async: (raises: []).} =
serverLocal = transp.localAddress() try:
await transp.closeWait() serverRemote = transp.remoteAddress()
server.stop() serverLocal = transp.localAddress()
server.close() await transp.closeWait()
server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var ta = initTAddress("0.0.0.0:0") var ta = initTAddress("0.0.0.0:0")
var server = createStreamServer(ta, serveClient, {ReuseAddr}) var server = createStreamServer(ta, serveClient, {ReuseAddr})
@ -748,13 +804,17 @@ suite "Stream Transport test suite":
var bigMessageSize = 10 * 1024 * 1024 - 1 var bigMessageSize = 10 * 1024 * 1024 - 1
var finishMessage = "DONE" var finishMessage = "DONE"
var cdata = newSeqOfCap[byte](bigMessageSize) var cdata = newSeqOfCap[byte](bigMessageSize)
proc serveClient(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient(server: StreamServer, transp: StreamTransport) {.
cdata = await transp.read(bigMessageSize) async: (raises: []).} =
var size = await transp.write(finishMessage) try:
doAssert(size == len(finishMessage)) cdata = await transp.read(bigMessageSize)
await transp.closeWait() var size = await transp.write(finishMessage)
server.stop() doAssert(size == len(finishMessage))
server.close() await transp.closeWait()
server.stop()
server.close()
except CatchableError as exc:
raiseAssert exc.msg
var flag = false var flag = false
var server = createStreamServer(address, serveClient, {ReuseAddr}) var server = createStreamServer(address, serveClient, {ReuseAddr})
@ -787,10 +847,15 @@ suite "Stream Transport test suite":
result = flag result = flag
proc testReadLine(address: TransportAddress): Future[bool] {.async.} = proc testReadLine(address: TransportAddress): Future[bool] {.async.} =
proc serveClient(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient(server: StreamServer, transp: StreamTransport) {.
discard await transp.write("DATA\r\r\r\r\r\n") async: (raises: []).} =
transp.close() try:
await transp.join() discard await transp.write("DATA\r\r\r\r\r\n")
transp.close()
await transp.join()
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(address, serveClient, {ReuseAddr}) var server = createStreamServer(address, serveClient, {ReuseAddr})
server.start() server.start()
@ -895,52 +960,56 @@ suite "Stream Transport test suite":
var state = 0 var state = 0
var c1, c2, c3, c4, c5, c6, c7: bool var c1, c2, c3, c4, c5, c6, c7: bool
proc serveClient(server: StreamServer, transp: StreamTransport) {.async.} = proc serveClient(server: StreamServer, transp: StreamTransport) {.
if state == 0: async: (raises: []).} =
# EOF from the beginning. try:
state = 1 if state == 0:
await transp.closeWait() # EOF from the beginning.
elif state == 1: state = 1
# Message has only zero-size header. await transp.closeWait()
var message = createLVMessage(0'u32) elif state == 1:
discard await transp.write(message) # Message has only zero-size header.
state = 2 var message = createLVMessage(0'u32)
await transp.closeWait() discard await transp.write(message)
elif state == 2: state = 2
# Message has header, but do not have any data at all. await transp.closeWait()
var message = createLVMessage(4'u32) elif state == 2:
message.setLen(4) # Message has header, but do not have any data at all.
discard await transp.write(message) var message = createLVMessage(4'u32)
state = 3 message.setLen(4)
await transp.closeWait() discard await transp.write(message)
elif state == 3: state = 3
# Message do not have enough data for specified size in header. await transp.closeWait()
var message = createLVMessage(1024'u32) elif state == 3:
message.setLen(1024) # Message do not have enough data for specified size in header.
discard await transp.write(message) var message = createLVMessage(1024'u32)
state = 4 message.setLen(1024)
await transp.closeWait() discard await transp.write(message)
elif state == 4: state = 4
# Good encoded message with oversize. await transp.closeWait()
var message = createLVMessage(1024'u32) elif state == 4:
discard await transp.write(message) # Good encoded message with oversize.
state = 5 var message = createLVMessage(1024'u32)
await transp.closeWait() discard await transp.write(message)
elif state == 5: state = 5
# Good encoded message. await transp.closeWait()
var message = createLVMessage(1024'u32) elif state == 5:
discard await transp.write(message) # Good encoded message.
state = 6 var message = createLVMessage(1024'u32)
await transp.closeWait() discard await transp.write(message)
elif state == 6: state = 6
# Good encoded message with additional data. await transp.closeWait()
var message = createLVMessage(1024'u32) elif state == 6:
discard await transp.write(message) # Good encoded message with additional data.
discard await transp.write("DONE") var message = createLVMessage(1024'u32)
state = 7 discard await transp.write(message)
await transp.closeWait() discard await transp.write("DONE")
else: state = 7
doAssert(false) await transp.closeWait()
else:
doAssert(false)
except CatchableError as exc:
raiseAssert exc.msg
var server = createStreamServer(address, serveClient, {ReuseAddr}) var server = createStreamServer(address, serveClient, {ReuseAddr})
server.start() server.start()
@ -1260,8 +1329,11 @@ suite "Stream Transport test suite":
proc testConnectBindLocalAddress() {.async.} = proc testConnectBindLocalAddress() {.async.} =
proc client(server: StreamServer, transp: StreamTransport) {.async.} = proc client(server: StreamServer, transp: StreamTransport) {.async: (raises: []).} =
await transp.closeWait() try:
await transp.closeWait()
except CatchableError as exc:
raiseAssert exc.msg
let server1 = createStreamServer(initTAddress("127.0.0.1:0"), client) let server1 = createStreamServer(initTAddress("127.0.0.1:0"), client)
let server2 = createStreamServer(initTAddress("127.0.0.1:0"), client) let server2 = createStreamServer(initTAddress("127.0.0.1:0"), client)
@ -1302,8 +1374,11 @@ suite "Stream Transport test suite":
await server3.closeWait() await server3.closeWait()
proc testConnectCancelLeaksTest() {.async.} = proc testConnectCancelLeaksTest() {.async.} =
proc client(server: StreamServer, transp: StreamTransport) {.async.} = proc client(server: StreamServer, transp: StreamTransport) {.async: (raises: []).} =
await transp.closeWait() try:
await transp.closeWait()
except CatchableError as exc:
raiseAssert exc.msg
let let
server = createStreamServer(initTAddress("127.0.0.1:0"), client) server = createStreamServer(initTAddress("127.0.0.1:0"), client)