mirror of
https://github.com/logos-storage/nim-chronos.git
synced 2026-01-07 16:03:09 +00:00
Fix transport.write() unable to send data through OS pipes. (#256)
* Fix transport.write() unable to send data through pipe. Add test for pipes. * Fix flaky test. * Add workaround for Nim's issue #19425.
This commit is contained in:
parent
17fed89c99
commit
519ca463df
@ -10,6 +10,7 @@
|
|||||||
{.push raises: [Defect].}
|
{.push raises: [Defect].}
|
||||||
|
|
||||||
import std/[net, nativesockets]
|
import std/[net, nativesockets]
|
||||||
|
import stew/base10
|
||||||
import ./asyncloop
|
import ./asyncloop
|
||||||
|
|
||||||
when defined(windows) or defined(nimdoc):
|
when defined(windows) or defined(nimdoc):
|
||||||
@ -24,7 +25,7 @@ when defined(windows) or defined(nimdoc):
|
|||||||
DEFAULT_PIPE_SIZE = 65536'i32
|
DEFAULT_PIPE_SIZE = 65536'i32
|
||||||
ERROR_PIPE_CONNECTED = 535
|
ERROR_PIPE_CONNECTED = 535
|
||||||
ERROR_PIPE_BUSY = 231
|
ERROR_PIPE_BUSY = 231
|
||||||
pipeHeaderName = r"\\.\pipe\chronos\"
|
pipeHeaderName = r"\\.\pipe\LOCAL\chronos\"
|
||||||
|
|
||||||
proc connectNamedPipe(hNamedPipe: Handle, lpOverlapped: pointer): WINBOOL
|
proc connectNamedPipe(hNamedPipe: Handle, lpOverlapped: pointer): WINBOOL
|
||||||
{.importc: "ConnectNamedPipe", stdcall, dynlib: "kernel32".}
|
{.importc: "ConnectNamedPipe", stdcall, dynlib: "kernel32".}
|
||||||
@ -152,18 +153,19 @@ proc createAsyncPipe*(): tuple[read: AsyncFD, write: AsyncFD] =
|
|||||||
## on error.
|
## on error.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
var pipeIn, pipeOut: Handle
|
var pipeIn, pipeOut: Handle
|
||||||
var pipeName: WideCString
|
var pipeName: string
|
||||||
var uniq = 0'u64
|
var uniq = 0'u64
|
||||||
var sa = SECURITY_ATTRIBUTES(nLength: sizeof(SECURITY_ATTRIBUTES).cint,
|
var sa = SECURITY_ATTRIBUTES(nLength: sizeof(SECURITY_ATTRIBUTES).cint,
|
||||||
lpSecurityDescriptor: nil, bInheritHandle: 0)
|
lpSecurityDescriptor: nil, bInheritHandle: 0)
|
||||||
while true:
|
while true:
|
||||||
QueryPerformanceCounter(uniq)
|
QueryPerformanceCounter(uniq)
|
||||||
pipeName = newWideCString(pipeHeaderName & $uniq)
|
pipeName = pipeHeaderName & Base10.toString(uniq)
|
||||||
|
|
||||||
var openMode = FILE_FLAG_FIRST_PIPE_INSTANCE or FILE_FLAG_OVERLAPPED or
|
var openMode = FILE_FLAG_FIRST_PIPE_INSTANCE or FILE_FLAG_OVERLAPPED or
|
||||||
PIPE_ACCESS_INBOUND
|
PIPE_ACCESS_INBOUND
|
||||||
var pipeMode = PIPE_TYPE_BYTE or PIPE_READMODE_BYTE or PIPE_WAIT
|
var pipeMode = PIPE_TYPE_BYTE or PIPE_READMODE_BYTE or PIPE_WAIT
|
||||||
pipeIn = createNamedPipe(pipeName, openMode, pipeMode, 1'i32,
|
pipeIn = createNamedPipe(newWideCString(pipeName), openMode, pipeMode,
|
||||||
DEFAULT_PIPE_SIZE, DEFAULT_PIPE_SIZE,
|
1'i32, DEFAULT_PIPE_SIZE, DEFAULT_PIPE_SIZE,
|
||||||
0'i32, addr sa)
|
0'i32, addr sa)
|
||||||
if pipeIn == INVALID_HANDLE_VALUE:
|
if pipeIn == INVALID_HANDLE_VALUE:
|
||||||
let err = osLastError()
|
let err = osLastError()
|
||||||
@ -176,8 +178,8 @@ proc createAsyncPipe*(): tuple[read: AsyncFD, write: AsyncFD] =
|
|||||||
break
|
break
|
||||||
|
|
||||||
var openMode = (GENERIC_WRITE or FILE_WRITE_DATA or SYNCHRONIZE)
|
var openMode = (GENERIC_WRITE or FILE_WRITE_DATA or SYNCHRONIZE)
|
||||||
pipeOut = createFileW(pipeName, openMode, 0, addr(sa), OPEN_EXISTING,
|
pipeOut = createFileW(newWideCString(pipeName), openMode, 0, addr(sa),
|
||||||
FILE_FLAG_OVERLAPPED, 0)
|
OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0)
|
||||||
if pipeOut == INVALID_HANDLE_VALUE:
|
if pipeOut == INVALID_HANDLE_VALUE:
|
||||||
discard closeHandle(pipeIn)
|
discard closeHandle(pipeIn)
|
||||||
return (read: asyncInvalidPipe, write: asyncInvalidPipe)
|
return (read: asyncInvalidPipe, write: asyncInvalidPipe)
|
||||||
|
|||||||
@ -2048,14 +2048,23 @@ 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) =
|
template fastWrite(transp: auto, pbytes: var ptr byte, rbytes: var int,
|
||||||
|
nbytes: int) =
|
||||||
# On windows, the write could be initiated here if there is no other write
|
# 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
|
# ongoing, but the queue is still needed due to the mechanics of iocp
|
||||||
|
|
||||||
when not defined(windows) and not defined(nimdoc):
|
when not defined(windows) and not defined(nimdoc):
|
||||||
if transp.queue.len == 0:
|
if transp.queue.len == 0:
|
||||||
while rbytes > 0:
|
while rbytes > 0:
|
||||||
let res = posix.send(SocketHandle(fd), pbytes, rbytes, MSG_NOSIGNAL)
|
let res =
|
||||||
|
case transp.kind
|
||||||
|
of TransportKind.Socket:
|
||||||
|
posix.send(SocketHandle(transp.fd), pbytes, rbytes,
|
||||||
|
MSG_NOSIGNAL)
|
||||||
|
of TransportKind.Pipe:
|
||||||
|
posix.write(cint(transp.fd), pbytes, rbytes)
|
||||||
|
else:
|
||||||
|
raiseAssert "Unsupported transport kind: " & $transp.kind
|
||||||
if res > 0:
|
if res > 0:
|
||||||
pbytes = cast[ptr byte](cast[uint](pbytes) + cast[uint](res))
|
pbytes = cast[ptr byte](cast[uint](pbytes) + cast[uint](res))
|
||||||
rbytes -= res
|
rbytes -= res
|
||||||
@ -2094,7 +2103,7 @@ proc write*(transp: StreamTransport, pbytes: pointer,
|
|||||||
pbytes = cast[ptr byte](pbytes)
|
pbytes = cast[ptr byte](pbytes)
|
||||||
rbytes = nbytes # Remaining bytes
|
rbytes = nbytes # Remaining bytes
|
||||||
|
|
||||||
fastWrite(transp.fd, pbytes, rbytes, nbytes)
|
fastWrite(transp, pbytes, rbytes, nbytes)
|
||||||
|
|
||||||
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)
|
||||||
@ -2115,7 +2124,7 @@ proc write*(transp: StreamTransport, msg: string, msglen = -1): Future[int] =
|
|||||||
pbytes = cast[ptr byte](unsafeAddr msg[0])
|
pbytes = cast[ptr byte](unsafeAddr msg[0])
|
||||||
rbytes = nbytes
|
rbytes = nbytes
|
||||||
|
|
||||||
fastWrite(transp.fd, pbytes, rbytes, nbytes)
|
fastWrite(transp, pbytes, rbytes, nbytes)
|
||||||
|
|
||||||
let
|
let
|
||||||
written = nbytes - rbytes # In case fastWrite wrote some
|
written = nbytes - rbytes # In case fastWrite wrote some
|
||||||
@ -2146,7 +2155,7 @@ proc write*[T](transp: StreamTransport, msg: seq[T], msglen = -1): Future[int] =
|
|||||||
pbytes = cast[ptr byte](unsafeAddr msg[0])
|
pbytes = cast[ptr byte](unsafeAddr msg[0])
|
||||||
rbytes = nbytes
|
rbytes = nbytes
|
||||||
|
|
||||||
fastWrite(transp.fd, pbytes, rbytes, nbytes)
|
fastWrite(transp, pbytes, rbytes, nbytes)
|
||||||
|
|
||||||
let
|
let
|
||||||
written = nbytes - rbytes # In case fastWrite wrote some
|
written = nbytes - rbytes # In case fastWrite wrote some
|
||||||
|
|||||||
@ -943,7 +943,7 @@ suite "BoundedStream test suite":
|
|||||||
let flags = {ServerFlags.ReuseAddr, ServerFlags.TcpNoDelay}
|
let flags = {ServerFlags.ReuseAddr, ServerFlags.TcpNoDelay}
|
||||||
var server = createStreamServer(address, processClient, flags = flags)
|
var server = createStreamServer(address, processClient, flags = flags)
|
||||||
server.start()
|
server.start()
|
||||||
var conn = await connect(address)
|
var conn = await connect(server.localAddress())
|
||||||
var rstream = newAsyncStreamReader(conn)
|
var rstream = newAsyncStreamReader(conn)
|
||||||
case btest
|
case btest
|
||||||
of BoundaryRead:
|
of BoundaryRead:
|
||||||
@ -1057,7 +1057,7 @@ suite "BoundedStream test suite":
|
|||||||
let flags = {ServerFlags.ReuseAddr, ServerFlags.TcpNoDelay}
|
let flags = {ServerFlags.ReuseAddr, ServerFlags.TcpNoDelay}
|
||||||
var server = createStreamServer(address, processClient, flags = flags)
|
var server = createStreamServer(address, processClient, flags = flags)
|
||||||
server.start()
|
server.start()
|
||||||
var conn = await connect(address)
|
var conn = await connect(server.localAddress())
|
||||||
var rstream = newAsyncStreamReader(conn)
|
var rstream = newAsyncStreamReader(conn)
|
||||||
var rbstream = newBoundedStreamReader(rstream, uint64(size),
|
var rbstream = newBoundedStreamReader(rstream, uint64(size),
|
||||||
comparison = cmp)
|
comparison = cmp)
|
||||||
@ -1108,7 +1108,7 @@ suite "BoundedStream test suite":
|
|||||||
await server.join()
|
await server.join()
|
||||||
return (res and clientRes)
|
return (res and clientRes)
|
||||||
|
|
||||||
let address = initTAddress("127.0.0.1:48030")
|
let address = initTAddress("127.0.0.1:0")
|
||||||
let suffix =
|
let suffix =
|
||||||
case itemComp
|
case itemComp
|
||||||
of BoundCmp.Equal:
|
of BoundCmp.Equal:
|
||||||
|
|||||||
@ -1233,6 +1233,33 @@ suite "Stream Transport test suite":
|
|||||||
except AsyncTimeoutError:
|
except AsyncTimeoutError:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
proc testPipe(): Future[bool] {.async.} =
|
||||||
|
let (rfd, wfd) = createAsyncPipe()
|
||||||
|
|
||||||
|
let
|
||||||
|
message = createBigMessage(16384 * 1024)
|
||||||
|
rtransp = fromPipe(rfd)
|
||||||
|
wtransp = fromPipe(wfd)
|
||||||
|
var
|
||||||
|
buffer = newSeq[byte](16384 * 1024)
|
||||||
|
|
||||||
|
proc writer(transp: StreamTransport): Future[int] {.async.} =
|
||||||
|
let res =
|
||||||
|
try:
|
||||||
|
await transp.write(message)
|
||||||
|
except CatchableError:
|
||||||
|
-1
|
||||||
|
return res
|
||||||
|
|
||||||
|
var fut = wtransp.writer()
|
||||||
|
try:
|
||||||
|
await rtransp.readExactly(addr buffer[0], 16384 * 1024)
|
||||||
|
except CatchableError:
|
||||||
|
discard
|
||||||
|
|
||||||
|
await allFutures(rtransp.closeWait(), wtransp.closeWait())
|
||||||
|
return buffer == message
|
||||||
|
|
||||||
markFD = getCurrentFD()
|
markFD = getCurrentFD()
|
||||||
|
|
||||||
for i in 0..<len(addresses):
|
for i in 0..<len(addresses):
|
||||||
@ -1318,6 +1345,8 @@ suite "Stream Transport test suite":
|
|||||||
check waitFor(testWriteOnClose(addresses[i])) == true
|
check waitFor(testWriteOnClose(addresses[i])) == true
|
||||||
test prefixes[i] & "read() notification on close() test":
|
test prefixes[i] & "read() notification on close() test":
|
||||||
check waitFor(testReadOnClose(addresses[i])) == true
|
check waitFor(testReadOnClose(addresses[i])) == true
|
||||||
|
test "[PIPE] readExactly()/write() test":
|
||||||
|
check waitFor(testPipe()) == true
|
||||||
test "Servers leak test":
|
test "Servers leak test":
|
||||||
check getTracker("stream.server").isLeaked() == false
|
check getTracker("stream.server").isLeaked() == false
|
||||||
test "Transports leak test":
|
test "Transports leak test":
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user