posix: fast path for write (#244)

When `write` is called on a `StreamTransport`, the current sequence of
operations is:

* copy data to queue
* register for "write" event notification
* return unfinished future to `write` caller
* wait for "write" notification (in `poll`)
* perform one `send`
* wait for notification again if there's more data to write
* complete the future

In this PR, we introduce a fast path for writing:

* If the queue is empty, try to send as much data as possible
  * If all data is sent, return completed future without `poll` round
  * If there's more data to send than can be sent in one go, add the
rest to queue
* If the queue is not empty, enqueue as above
* When notified that write is possible, keep writing until OS buffer is
full before waiting for event again

The fast path provides significant performance benefits when there are
many small writes, such as when sending gossip to many peers, by
avoiding the poll loop and data copy on each send.

Also fixes an issue where the socket would not be removed from the
writer set if there were pending writes on close.
This commit is contained in:
Jacek Sieka 2021-12-08 11:35:27 +01:00 committed by GitHub
parent 7da1f5d4d2
commit c25fa1f6cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 246 additions and 243 deletions

View File

@ -1,5 +1,5 @@
packageName = "chronos" packageName = "chronos"
version = "3.0.9" version = "3.0.10"
author = "Status Research & Development GmbH" author = "Status Research & Development GmbH"
description = "Chronos" description = "Chronos"
license = "Apache License 2.0 or MIT" license = "Apache License 2.0 or MIT"

View File

@ -29,7 +29,8 @@ when defined(nimdoc):
## ##
## ``count`` is the number of bytes to copy between the file descriptors. ## ``count`` is the number of bytes to copy between the file descriptors.
## On exit ``count`` will hold number of bytes actually transferred between ## On exit ``count`` will hold number of bytes actually transferred between
## file descriptors. ## file descriptors. May be >0 even in the case of error return, if some
## bytes were sent before the error occurred.
## ##
## If the transfer was successful, the number of bytes written to ``outfd`` ## If the transfer was successful, the number of bytes written to ``outfd``
## is stored in ``count``, and ``0`` returned. Note that a successful call ## is stored in ``count``, and ``0`` returned. Note that a successful call
@ -45,10 +46,13 @@ when defined(linux) or defined(android):
proc sendfile*(outfd, infd: int, offset: int, count: var int): int = proc sendfile*(outfd, infd: int, offset: int, count: var int): int =
var o = offset var o = offset
result = osSendFile(cint(outfd), cint(infd), addr o, count) let res = osSendFile(cint(outfd), cint(infd), addr o, count)
if result >= 0: if res >= 0:
count = result count = res
result = 0 0
else:
count = 0
-1
elif defined(freebsd) or defined(openbsd) or defined(netbsd) or elif defined(freebsd) or defined(openbsd) or defined(netbsd) or
defined(dragonflybsd): defined(dragonflybsd):
@ -69,18 +73,17 @@ elif defined(freebsd) or defined(openbsd) or defined(netbsd) or
proc sendfile*(outfd, infd: int, offset: int, count: var int): int = proc sendfile*(outfd, infd: int, offset: int, count: var int): int =
var o = 0'u var o = 0'u
result = osSendFile(cint(infd), cint(outfd), uint(offset), uint(count), nil, let res = osSendFile(cint(infd), cint(outfd), uint(offset), uint(count), nil,
addr o, 0) addr o, 0)
if result >= 0: if res >= 0:
count = int(o) count = int(o)
result = 0 0
else: else:
let err = osLastError() let err = osLastError()
if int(err) == EAGAIN: count =
count = int(o) if int(err) == EAGAIN: int(o)
result = 0 else: 0
else: -1
result = -1
elif defined(macosx): elif defined(macosx):
import posix, os import posix, os
@ -100,14 +103,13 @@ elif defined(macosx):
proc sendfile*(outfd, infd: int, offset: int, count: var int): int = proc sendfile*(outfd, infd: int, offset: int, count: var int): int =
var o = count var o = count
result = osSendFile(cint(infd), cint(outfd), offset, addr o, nil, 0) let res = osSendFile(cint(infd), cint(outfd), offset, addr o, nil, 0)
if result >= 0: if res >= 0:
count = int(o) count = int(o)
result = 0 0
else: else:
let err = osLastError() let err = osLastError()
if int(err) == EAGAIN: count =
count = int(o) if int(err) == EAGAIN: int(o)
result = 0 else: 0
else: -1
result = -1

View File

@ -191,11 +191,11 @@ template shiftBuffer(t, c: untyped) =
else: else:
(t).offset = 0 (t).offset = 0
template shiftVectorBuffer(v, o: untyped) = template shiftVectorBuffer(v: var StreamVector, o: untyped) =
(v).buf = cast[pointer](cast[uint]((v).buf) + uint(o)) (v).buf = cast[pointer](cast[uint]((v).buf) + uint(o))
(v).buflen -= int(o) (v).buflen -= int(o)
template shiftVectorFile(v, o: untyped) = template shiftVectorFile(v: var StreamVector, o: untyped) =
(v).buf = cast[pointer](cast[uint]((v).buf) - cast[uint](o)) (v).buf = cast[pointer](cast[uint]((v).buf) - cast[uint](o))
(v).offset += cast[uint]((o)) (v).offset += cast[uint]((o))
@ -1239,8 +1239,17 @@ else:
result = (err == OSErrorCode(ECONNRESET)) or result = (err == OSErrorCode(ECONNRESET)) or
(err == OSErrorCode(EPIPE)) (err == OSErrorCode(EPIPE))
proc removeWriter(transp: StreamTransport) =
try:
transp.fd.removeWriter()
# For debugging, record that we're no longer getting write notifications
transp.state.incl WritePaused
except IOSelectorsException as exc:
raiseAsDefect exc, "removeWriter"
except ValueError as exc:
raiseAsDefect exc, "removeWriter"
proc writeStreamLoop(udata: pointer) = proc writeStreamLoop(udata: pointer) =
# TODO fix Defect raises - they "shouldn't" happen
var cdata = cast[ptr CompletionData](udata) var cdata = cast[ptr CompletionData](udata)
var transp = cast[StreamTransport](cdata.udata) var transp = cast[StreamTransport](cdata.udata)
let fd = SocketHandle(cdata.fd) let fd = SocketHandle(cdata.fd)
@ -1251,171 +1260,91 @@ else:
return return
if WriteClosed in transp.state: if WriteClosed in transp.state:
transp.state.incl({WritePaused}) if transp.queue.len > 0:
let error = getTransportUseClosedError() transp.removeWriter()
failPendingWriteQueue(transp.queue, error)
else:
if len(transp.queue) > 0:
var vector = transp.queue.popFirst()
while true:
if transp.kind == TransportKind.Socket:
if vector.kind == VectorKind.DataBuffer:
let res = posix.send(fd, vector.buf, vector.buflen, MSG_NOSIGNAL)
if res >= 0:
if vector.buflen - res == 0:
if not(vector.writer.finished()):
vector.writer.complete(vector.size)
else:
vector.shiftVectorBuffer(res)
transp.queue.addFirst(vector)
else:
let err = osLastError()
if int(err) == EINTR:
continue
else:
try:
transp.fd.removeWriter()
except IOSelectorsException as exc:
raiseAsDefect exc, "removeWriter"
except ValueError as exc:
raiseAsDefect exc, "removeWriter"
if isConnResetError(err): let error = getTransportUseClosedError()
# Soft error happens which indicates that remote peer got failPendingWriteQueue(transp.queue, error)
# disconnected, complete all pending writes in queue with 0. return
transp.state.incl({WriteEof, WritePaused})
if not(vector.writer.finished()):
vector.writer.complete(0)
completePendingWriteQueue(transp.queue, 0)
else:
transp.state.incl({WriteError, WritePaused})
let error = getTransportOsError(err)
if not(vector.writer.finished()):
vector.writer.fail(error)
failPendingWriteQueue(transp.queue, error)
else:
var nbytes = cast[int](vector.buf)
let res = sendfile(int(fd), cast[int](vector.buflen),
int(vector.offset),
nbytes)
if res >= 0:
if cast[int](vector.buf) - nbytes == 0:
vector.size += nbytes
if not(vector.writer.finished()):
vector.writer.complete(vector.size)
else:
vector.size += nbytes
vector.shiftVectorFile(nbytes)
transp.queue.addFirst(vector)
else:
let err = osLastError()
if int(err) == EINTR:
continue
else:
try:
transp.fd.removeWriter()
except IOSelectorsException as exc:
raiseAsDefect exc, "removeWriter"
except ValueError as exc:
raiseAsDefect exc, "removeWriter"
if isConnResetError(err): # We exit this loop in two ways:
# Soft error happens which indicates that remote peer got # * The queue is empty: we call removeWriter to disable further callbacks
# disconnected, complete all pending writes in queue with 0. # * EWOULDBLOCK is returned and we need to wait for a new notification
transp.state.incl({WriteEof, WritePaused})
if not(vector.writer.finished()):
vector.writer.complete(0)
completePendingWriteQueue(transp.queue, 0)
else:
transp.state.incl({WriteError, WritePaused})
let error = getTransportOsError(err)
if not(vector.writer.finished()):
vector.writer.fail(error)
failPendingWriteQueue(transp.queue, error)
break
elif transp.kind == TransportKind.Pipe: while len(transp.queue) > 0:
if vector.kind == VectorKind.DataBuffer: template handleError() =
let res = posix.write(cint(fd), vector.buf, vector.buflen) let err = osLastError()
if res >= 0:
if vector.buflen - res == 0:
if not(vector.writer.finished()):
vector.writer.complete(vector.size)
else:
vector.shiftVectorBuffer(res)
transp.queue.addFirst(vector)
else:
let err = osLastError()
if int(err) == EINTR:
continue
else:
try:
transp.fd.removeWriter()
except IOSelectorsException as exc:
raiseAsDefect exc, "removeWriter"
except ValueError as exc:
raiseAsDefect exc, "removeWriter"
if isConnResetError(err): if cint(err) == EINTR:
# Soft error happens which indicates that remote peer got # Signal happened while writing - try again with all data
# disconnected, complete all pending writes in queue with 0. transp.queue.addFirst(vector)
transp.state.incl({WriteEof, WritePaused}) continue
if not(vector.writer.finished()):
vector.writer.complete(0) if cint(err) in [EWOULDBLOCK, EAGAIN]:
completePendingWriteQueue(transp.queue, 0) # Socket buffer is full - wait until next write notification - in
else: # particular, ensure removeWriter is not called
transp.state.incl({WriteError, WritePaused}) transp.queue.addFirst(vector)
let error = getTransportOsError(err) return
if not(vector.writer.finished()):
vector.writer.fail(error) # The errors below will clear the write queue, meaning we'll exit the
failPendingWriteQueue(transp.queue, error) # loop
else: if isConnResetError(err):
var nbytes = cast[int](vector.buf) # Soft error happens which indicates that remote peer got
let res = sendfile(int(fd), cast[int](vector.buflen), # disconnected, complete all pending writes in queue with 0.
int(vector.offset), transp.state.incl({WriteEof})
nbytes) if not(vector.writer.finished()):
if res >= 0: vector.writer.complete(0)
if cast[int](vector.buf) - nbytes == 0: completePendingWriteQueue(transp.queue, 0)
vector.size += nbytes else:
if not(vector.writer.finished()): transp.state.incl({WriteError})
vector.writer.complete(vector.size) let error = getTransportOsError(err)
else: if not(vector.writer.finished()):
vector.size += nbytes vector.writer.fail(error)
vector.shiftVectorFile(nbytes) failPendingWriteQueue(transp.queue, error)
transp.queue.addFirst(vector)
else: var vector = transp.queue.popFirst()
let err = osLastError() case vector.kind
if int(err) == EINTR: of VectorKind.DataBuffer:
continue let res =
else: case transp.kind
try: of TransportKind.Socket:
transp.fd.removeWriter() posix.send(fd, vector.buf, vector.buflen, MSG_NOSIGNAL)
except IOSelectorsException as exc: of TransportKind.Pipe:
raiseAsDefect exc, "removeWriter" posix.write(cint(fd), vector.buf, vector.buflen)
except ValueError as exc: else: raiseAssert "Unsupported transport kind: " & $transp.kind
raiseAsDefect exc, "removeWriter"
if isConnResetError(err): if res >= 0:
# Soft error happens which indicates that remote peer got if vector.buflen == res:
# disconnected, complete all pending writes in queue with 0. if not(vector.writer.finished()):
transp.state.incl({WriteEof, WritePaused}) vector.writer.complete(vector.size)
if not(vector.writer.finished()): else:
vector.writer.complete(0) vector.shiftVectorBuffer(res)
completePendingWriteQueue(transp.queue, 0) transp.queue.addFirst(vector) # Try again with rest of data
else: else:
transp.state.incl({WriteError, WritePaused}) handleError()
let error = getTransportOsError(err)
if not(vector.writer.finished()): of VectorKind.DataFile:
vector.writer.fail(error) var nbytes = cast[int](vector.buf)
failPendingWriteQueue(transp.queue, error) let res = sendfile(int(fd), cast[int](vector.buflen),
break int(vector.offset), nbytes)
else:
transp.state.incl(WritePaused) # In case of some errors on some systems, some bytes may have been
try: # written (see sendfile.nim)
transp.fd.removeWriter() vector.size += nbytes
except IOSelectorsException as exc:
raiseAsDefect exc, "removeWriter" if res >= 0:
except ValueError as exc: if cast[int](vector.buf) == nbytes:
raiseAsDefect exc, "removeWriter" if not(vector.writer.finished()):
vector.writer.complete(vector.size)
else:
vector.shiftVectorFile(nbytes)
transp.queue.addFirst(vector)
else:
vector.shiftVectorFile(nbytes)
handleError()
# Nothing left in the queue - no need for further write notifications
transp.removeWriter()
proc readStreamLoop(udata: pointer) = proc readStreamLoop(udata: pointer) =
# TODO fix Defect raises - they "shouldn't" happen # TODO fix Defect raises - they "shouldn't" happen
@ -1700,11 +1629,17 @@ else:
raiseAsDefect exc, "addReader" raiseAsDefect exc, "addReader"
proc resumeWrite(transp: StreamTransport) {.inline.} = proc resumeWrite(transp: StreamTransport) {.inline.} =
if WritePaused in transp.state: if transp.queue.len() == 1:
transp.state.excl(WritePaused) # writeStreamLoop keeps writing until queue is empty - we should not call
# TODO reset flag on exception?? # resumeWrite under any other condition than when the items are
# added to a queue - if the flag is not set here, it means that the socket
# was not removed from write notifications at the right time, and this
# would mean an imbalance in registration and deregistration
doAssert WritePaused in transp.state
try: try:
addWriter(transp.fd, writeStreamLoop, cast[pointer](transp)) addWriter(transp.fd, writeStreamLoop, cast[pointer](transp))
transp.state.excl WritePaused
except IOSelectorsException as exc: except IOSelectorsException as exc:
raiseAsDefect exc, "addWriter" raiseAsDefect exc, "addWriter"
except ValueError as exc: except ValueError as exc:
@ -2101,6 +2036,40 @@ proc getUserData*[T](server: StreamServer): T {.inline.} =
## Obtain user data stored in ``server`` object. ## Obtain user data stored in ``server`` object.
result = cast[T](server.udata) result = cast[T](server.udata)
template fastWrite(fd: auto, pbytes: var ptr byte, rbytes: var int, nbytes: int) =
# On windows, the write could be initiated here if there is no other write
# ongoing, but the queue is still needed due to the mechanics of iocp
when not defined(windows):
if transp.queue.len == 0:
while rbytes > 0:
let res = posix.send(SocketHandle(fd), pbytes, rbytes, MSG_NOSIGNAL)
if res > 0:
pbytes = cast[ptr byte](cast[uint](pbytes) + cast[uint](res))
rbytes -= res
if rbytes == 0:
retFuture.complete(nbytes)
return retFuture
# Not all bytes written - keep going
else:
let err = osLastError()
if cint(err) in [EAGAIN, EWOULDBLOCK]:
break # No bytes written, add to queue
if cint(err) == EINTR:
continue
if isConnResetError(err):
transp.state.incl({WriteEof})
retFuture.complete(0)
return retFuture
else:
transp.state.incl({WriteError})
let error = getTransportOsError(err)
retFuture.fail(error)
return retFuture
proc write*(transp: StreamTransport, pbytes: pointer, proc write*(transp: StreamTransport, pbytes: pointer,
nbytes: int): Future[int] = nbytes: int): Future[int] =
## Write data from buffer ``pbytes`` with size ``nbytes`` using transport ## Write data from buffer ``pbytes`` with size ``nbytes`` using transport
@ -2108,8 +2077,15 @@ proc write*(transp: StreamTransport, pbytes: pointer,
var retFuture = newFuture[int]("stream.transport.write(pointer)") var retFuture = newFuture[int]("stream.transport.write(pointer)")
transp.checkClosed(retFuture) transp.checkClosed(retFuture)
transp.checkWriteEof(retFuture) transp.checkWriteEof(retFuture)
var
pbytes = cast[ptr byte](pbytes)
rbytes = nbytes # Remaining bytes
fastWrite(transp.fd, pbytes, rbytes, nbytes)
var vector = StreamVector(kind: DataBuffer, writer: retFuture, var vector = StreamVector(kind: DataBuffer, writer: retFuture,
buf: pbytes, buflen: nbytes, size: nbytes) buf: pbytes, buflen: rbytes, size: nbytes)
transp.queue.addLast(vector) transp.queue.addLast(vector)
transp.resumeWrite() transp.resumeWrite()
return retFuture return retFuture
@ -2119,15 +2095,28 @@ proc write*(transp: StreamTransport, msg: string, msglen = -1): Future[int] =
var retFuture = newFutureStr[int]("stream.transport.write(string)") var retFuture = newFutureStr[int]("stream.transport.write(string)")
transp.checkClosed(retFuture) transp.checkClosed(retFuture)
transp.checkWriteEof(retFuture) transp.checkWriteEof(retFuture)
if not(isLiteral(msg)):
let
nbytes = if msglen <= 0: len(msg) else: msglen
var
pbytes = cast[ptr byte](unsafeAddr msg[0])
rbytes = nbytes
fastWrite(transp.fd, pbytes, rbytes, nbytes)
let
written = nbytes - rbytes # In case fastWrite wrote some
pbytes = if not(isLiteral(msg)):
shallowCopy(retFuture.gcholder, msg) shallowCopy(retFuture.gcholder, msg)
cast[ptr byte](addr retFuture.gcholder[written])
else: else:
retFuture.gcholder = msg retFuture.gcholder = msg[written..<nbytes]
let length = if msglen <= 0: len(msg) else: msglen cast[ptr byte](addr retFuture.gcholder[0])
var vector = StreamVector(kind: DataBuffer,
writer: cast[Future[int]](retFuture), var vector = StreamVector(kind: DataBuffer, writer: retFuture,
buf: addr retFuture.gcholder[0], buflen: length, buf: pbytes, buflen: rbytes, size: nbytes)
size: length)
transp.queue.addLast(vector) transp.queue.addLast(vector)
transp.resumeWrite() transp.resumeWrite()
return retFuture return retFuture
@ -2137,15 +2126,28 @@ proc write*[T](transp: StreamTransport, msg: seq[T], msglen = -1): Future[int] =
var retFuture = newFutureSeq[int, T]("stream.transport.write(seq)") var retFuture = newFutureSeq[int, T]("stream.transport.write(seq)")
transp.checkClosed(retFuture) transp.checkClosed(retFuture)
transp.checkWriteEof(retFuture) transp.checkWriteEof(retFuture)
if not(isLiteral(msg)):
let
nbytes = if msglen <= 0: (len(msg) * sizeof(T)) else: (msglen * sizeof(T))
var
pbytes = cast[ptr byte](unsafeAddr msg[0])
rbytes = nbytes
fastWrite(transp.fd, pbytes, rbytes, nbytes)
let
written = nbytes - rbytes # In case fastWrite wrote some
pbytes = if not(isLiteral(msg)):
shallowCopy(retFuture.gcholder, msg) shallowCopy(retFuture.gcholder, msg)
cast[ptr byte](addr retFuture.gcholder[written])
else: else:
retFuture.gcholder = msg retFuture.gcholder = msg[written..<nbytes]
let length = if msglen <= 0: (len(msg) * sizeof(T)) else: (msglen * sizeof(T)) cast[ptr byte](addr retFuture.gcholder[0])
var vector = StreamVector(kind: DataBuffer,
writer: cast[Future[int]](retFuture), var vector = StreamVector(kind: DataBuffer, writer: retFuture,
buf: addr retFuture.gcholder[0], buf: pbytes, buflen: rbytes, size: nbytes)
buflen: length, size: length)
transp.queue.addLast(vector) transp.queue.addLast(vector)
transp.resumeWrite() transp.resumeWrite()
return retFuture return retFuture

View File

@ -28,27 +28,6 @@ suite "Stream Transport test suite":
FilesCount = 10 FilesCount = 10
TestsCount = 100 TestsCount = 100
m1 = "readLine() multiple clients with messages (" & $ClientsCount &
" clients x " & $MessagesCount & " messages)"
m2 = "readExactly() multiple clients with messages (" & $ClientsCount &
" clients x " & $MessagesCount & " messages)"
m3 = "readUntil() multiple clients with messages (" & $ClientsCount &
" clients x " & $MessagesCount & " messages)"
m4 = "writeFile() multiple clients (" & $FilesCount & " files)"
m5 = "write(string)/read(int) multiple clients (" & $ClientsCount &
" clients x " & $MessagesCount & " messages)"
m6 = "write(seq[byte])/consume(int)/read(int) multiple clients (" &
$ClientsCount & " clients x " & $MessagesCount & " messages)"
m7 = "readLine() buffer overflow test"
m8 = "readUntil() buffer overflow test"
m11 = "readExactly() unexpected disconnect test"
m12 = "readUntil() unexpected disconnect test"
m13 = "readLine() unexpected disconnect empty string test"
m14 = "Closing socket while operation pending test (issue #8)"
m15 = "Connection refused test"
m16 = "readOnce() read until atEof() test"
m17 = "0.0.0.0/::0 (INADDR_ANY) test"
when defined(windows): when defined(windows):
let addresses = [ let addresses = [
initTAddress("127.0.0.1:33335"), initTAddress("127.0.0.1:33335"),
@ -1159,15 +1138,30 @@ suite "Stream Transport test suite":
proc acceptTask(server: StreamServer) {.async.} = proc acceptTask(server: StreamServer) {.async.} =
let transp = await server.accept() let transp = await server.accept()
var futs = newSeq[Future[int]](TestsCount) var futs = newSeq[Future[int]]()
var msg = createBigMessage(1024) var msg = createBigMessage(1024)
for i in 0 ..< len(futs): var tries = 0
futs[i] = transp.write(msg)
while futs.len() < TestsCount:
let fut = transp.write(msg)
# `write` has a fast path that puts the data in the OS socket buffer -
# we'll keep writing until we get EAGAIN from the OS so that we have
# data in the in-chronos queue to fail on close
if not fut.completed():
futs.add(fut)
else:
tries += 1
if tries > 65*1024:
# We've queued 64mb on the socket and it still allows writing,
# something is wrong - we'll break here which will cause the test
# to fail
break
await transp.closeWait() await transp.closeWait()
await sleepAsync(100.milliseconds) await sleepAsync(100.milliseconds)
for i in 0 ..< len(futs): for i in 0 ..< len(futs):
# writes may complete via fast write
if futs[i].failed() and (futs[i].error of TransportUseClosedError): if futs[i].failed() and (futs[i].error of TransportUseClosedError):
inc(res) inc(res)
@ -1244,29 +1238,34 @@ suite "Stream Transport test suite":
for i in 0..<len(addresses): for i in 0..<len(addresses):
test prefixes[i] & "close(transport) test": test prefixes[i] & "close(transport) test":
check waitFor(testCloseTransport(addresses[i])) == 1 check waitFor(testCloseTransport(addresses[i])) == 1
test prefixes[i] & m8: test prefixes[i] & "readUntil() buffer overflow test":
check waitFor(test8(addresses[i])) == 1 check waitFor(test8(addresses[i])) == 1
test prefixes[i] & m7: test prefixes[i] & "readLine() buffer overflow test":
check waitFor(test7(addresses[i])) == 1 check waitFor(test7(addresses[i])) == 1
test prefixes[i] & m11: test prefixes[i] & "readExactly() unexpected disconnect test":
check waitFor(test11(addresses[i])) == 1 check waitFor(test11(addresses[i])) == 1
test prefixes[i] & m12: test prefixes[i] & "readUntil() unexpected disconnect test":
check waitFor(test12(addresses[i])) == 1 check waitFor(test12(addresses[i])) == 1
test prefixes[i] & m13: test prefixes[i] & "readLine() unexpected disconnect empty string test":
check waitFor(test13(addresses[i])) == 1 check waitFor(test13(addresses[i])) == 1
test prefixes[i] & m14: test prefixes[i] & "Closing socket while operation pending test (issue #8)":
check waitFor(test14(addresses[i])) == 1 check waitFor(test14(addresses[i])) == 1
test prefixes[i] & m1: test prefixes[i] & "readLine() multiple clients with messages (" &
$ClientsCount & " clients x " & $MessagesCount & " messages)":
check waitFor(test1(addresses[i])) == ClientsCount * MessagesCount check waitFor(test1(addresses[i])) == ClientsCount * MessagesCount
test prefixes[i] & m2: test prefixes[i] & "readExactly() multiple clients with messages (" &
$ClientsCount & " clients x " & $MessagesCount & " messages)":
check waitFor(test2(addresses[i])) == ClientsCount * MessagesCount check waitFor(test2(addresses[i])) == ClientsCount * MessagesCount
test prefixes[i] & m3: test prefixes[i] & "readUntil() multiple clients with messages (" &
$ClientsCount & " clients x " & $MessagesCount & " messages)":
check waitFor(test3(addresses[i])) == ClientsCount * MessagesCount check waitFor(test3(addresses[i])) == ClientsCount * MessagesCount
test prefixes[i] & m5: test prefixes[i] & "write(string)/read(int) multiple clients (" &
$ClientsCount & " clients x " & $MessagesCount & " messages)":
check waitFor(testWR(addresses[i])) == ClientsCount * MessagesCount check waitFor(testWR(addresses[i])) == ClientsCount * MessagesCount
test prefixes[i] & m6: test prefixes[i] & "write(seq[byte])/consume(int)/read(int) multiple clients (" &
$ClientsCount & " clients x " & $MessagesCount & " messages)":
check waitFor(testWCR(addresses[i])) == ClientsCount * MessagesCount check waitFor(testWCR(addresses[i])) == ClientsCount * MessagesCount
test prefixes[i] & m4: test prefixes[i] & "writeFile() multiple clients (" & $FilesCount & " files)":
when defined(windows): when defined(windows):
if addresses[i].family == AddressFamily.IPv4: if addresses[i].family == AddressFamily.IPv4:
check waitFor(testSendFile(addresses[i])) == FilesCount check waitFor(testSendFile(addresses[i])) == FilesCount
@ -1274,21 +1273,21 @@ suite "Stream Transport test suite":
skip() skip()
else: else:
check waitFor(testSendFile(addresses[i])) == FilesCount check waitFor(testSendFile(addresses[i])) == FilesCount
test prefixes[i] & m15: test prefixes[i] & "Connection refused test":
var address: TransportAddress var address: TransportAddress
if addresses[i].family == AddressFamily.Unix: if addresses[i].family == AddressFamily.Unix:
address = initTAddress("/tmp/notexistingtestpipe") address = initTAddress("/tmp/notexistingtestpipe")
else: else:
address = initTAddress("127.0.0.1:43335") address = initTAddress("127.0.0.1:43335")
check waitFor(testConnectionRefused(address)) == true check waitFor(testConnectionRefused(address)) == true
test prefixes[i] & m16: test prefixes[i] & "readOnce() read until atEof() test":
check waitFor(test16(addresses[i])) == 1 check waitFor(test16(addresses[i])) == 1
test prefixes[i] & "Connection reset test on send() only": test prefixes[i] & "Connection reset test on send() only":
when defined(macosx): when defined(macosx):
skip() skip()
else: else:
check waitFor(testWriteConnReset(addresses[i])) == 1 check waitFor(testWriteConnReset(addresses[i])) == 1
test prefixes[i] & m17: test prefixes[i] & "0.0.0.0/::0 (INADDR_ANY) test":
if addresses[i].family == AddressFamily.IPv4: if addresses[i].family == AddressFamily.IPv4:
check waitFor(testAnyAddress()) == true check waitFor(testAnyAddress()) == true
else: else: