2018-05-16 08:22:34 +00:00
|
|
|
#
|
2019-02-06 14:49:11 +00:00
|
|
|
# Chronos Stream Transport
|
|
|
|
# (c) Copyright 2018-Present
|
2018-05-16 08:22:34 +00:00
|
|
|
# Status Research & Development GmbH
|
|
|
|
#
|
|
|
|
# Licensed under either of
|
|
|
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
|
|
|
# MIT license (LICENSE-MIT)
|
2018-06-05 22:48:03 +00:00
|
|
|
import net, nativesockets, os, deques
|
2019-10-29 21:19:41 +00:00
|
|
|
import ../asyncloop, ../handles
|
2018-05-16 08:22:34 +00:00
|
|
|
import common
|
|
|
|
|
2018-10-25 10:19:19 +00:00
|
|
|
{.deadCodeElim: on.}
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
when defined(windows):
|
|
|
|
import winlean
|
|
|
|
else:
|
|
|
|
import posix
|
|
|
|
|
|
|
|
type
|
2018-05-21 21:52:57 +00:00
|
|
|
VectorKind = enum
|
2018-05-22 21:03:13 +00:00
|
|
|
DataBuffer, # Simple buffer pointer/length
|
|
|
|
DataFile # File handle for sendfile/TransmitFile
|
2018-05-21 21:52:57 +00:00
|
|
|
|
|
|
|
StreamVector = object
|
2018-05-22 21:03:13 +00:00
|
|
|
kind: VectorKind # Writer vector source kind
|
|
|
|
buf: pointer # Writer buffer pointer
|
|
|
|
buflen: int # Writer buffer size
|
|
|
|
offset: uint # Writer vector offset
|
2018-10-25 19:59:40 +00:00
|
|
|
size: int # Original size
|
2018-06-05 20:21:07 +00:00
|
|
|
writer: Future[int] # Writer vector completion Future
|
2018-05-21 21:52:57 +00:00
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
TransportKind* {.pure.} = enum
|
2018-05-22 21:03:13 +00:00
|
|
|
Socket, # Socket transport
|
|
|
|
Pipe, # Pipe transport
|
|
|
|
File # File transport
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-10-25 10:19:19 +00:00
|
|
|
TransportFlags* = enum
|
|
|
|
None,
|
|
|
|
# Default value
|
|
|
|
WinServerPipe,
|
|
|
|
# This is internal flag which used to differentiate between server pipe
|
|
|
|
# handle and client pipe handle.
|
|
|
|
WinNoPipeFlash
|
|
|
|
# By default `AddressFamily.Unix` transports in Windows are using
|
|
|
|
# `FlushFileBuffers()` when transport closing.
|
|
|
|
# This flag disables usage of `FlushFileBuffers()` on `AddressFamily.Unix`
|
|
|
|
# transport shutdown. If both server and client are running in the same
|
|
|
|
# thread, because of `FlushFileBuffers()` will ensure that all bytes
|
|
|
|
# or messages written to the pipe are read by the client, it is possible to
|
|
|
|
# get stuck on transport `close()`.
|
|
|
|
# Please use this flag only if you are making both client and server in
|
|
|
|
# the same thread.
|
|
|
|
|
2019-04-04 09:34:23 +00:00
|
|
|
StreamTransportTracker* = ref object of TrackerBase
|
|
|
|
opened*: int64
|
|
|
|
closed*: int64
|
|
|
|
|
|
|
|
StreamServerTracker* = ref object of TrackerBase
|
|
|
|
opened*: int64
|
|
|
|
closed*: int64
|
|
|
|
|
2020-03-05 09:59:10 +00:00
|
|
|
ReadMessagePredicate* = proc (data: openarray[byte]): tuple[consumed: int,
|
|
|
|
done: bool] {.
|
|
|
|
gcsafe, raises: [].}
|
|
|
|
|
2019-04-04 09:34:23 +00:00
|
|
|
const
|
|
|
|
StreamTransportTrackerName = "stream.transport"
|
|
|
|
StreamServerTrackerName = "stream.server"
|
|
|
|
|
2018-06-10 23:08:17 +00:00
|
|
|
when defined(windows):
|
|
|
|
const SO_UPDATE_CONNECT_CONTEXT = 0x7010
|
|
|
|
|
|
|
|
type
|
|
|
|
StreamTransport* = ref object of RootRef
|
|
|
|
fd*: AsyncFD # File descriptor
|
|
|
|
state: set[TransportState] # Current Transport state
|
|
|
|
reader: Future[void] # Current reader Future
|
|
|
|
buffer: seq[byte] # Reading buffer
|
|
|
|
offset: int # Reading buffer offset
|
|
|
|
error: ref Exception # Current error
|
|
|
|
queue: Deque[StreamVector] # Writer queue
|
|
|
|
future: Future[void] # Stream life future
|
|
|
|
# Windows specific part
|
|
|
|
rwsabuf: TWSABuf # Reader WSABUF
|
|
|
|
wwsabuf: TWSABuf # Writer WSABUF
|
|
|
|
rovl: CustomOverlapped # Reader OVERLAPPED structure
|
|
|
|
wovl: CustomOverlapped # Writer OVERLAPPED structure
|
|
|
|
roffset: int # Pending reading offset
|
2018-10-25 10:19:19 +00:00
|
|
|
flags: set[TransportFlags] # Internal flags
|
2018-06-10 23:08:17 +00:00
|
|
|
case kind*: TransportKind
|
|
|
|
of TransportKind.Socket:
|
|
|
|
domain: Domain # Socket transport domain (IPv4/IPv6)
|
|
|
|
local: TransportAddress # Local address
|
|
|
|
remote: TransportAddress # Remote address
|
|
|
|
of TransportKind.Pipe:
|
|
|
|
todo1: int
|
|
|
|
of TransportKind.File:
|
|
|
|
todo2: int
|
|
|
|
else:
|
|
|
|
type
|
|
|
|
StreamTransport* = ref object of RootRef
|
|
|
|
fd*: AsyncFD # File descriptor
|
|
|
|
state: set[TransportState] # Current Transport state
|
|
|
|
reader: Future[void] # Current reader Future
|
|
|
|
buffer: seq[byte] # Reading buffer
|
|
|
|
offset: int # Reading buffer offset
|
|
|
|
error: ref Exception # Current error
|
|
|
|
queue: Deque[StreamVector] # Writer queue
|
|
|
|
future: Future[void] # Stream life future
|
|
|
|
case kind*: TransportKind
|
|
|
|
of TransportKind.Socket:
|
|
|
|
domain: Domain # Socket transport domain (IPv4/IPv6)
|
|
|
|
local: TransportAddress # Local address
|
|
|
|
remote: TransportAddress # Remote address
|
|
|
|
of TransportKind.Pipe:
|
|
|
|
todo1: int
|
|
|
|
of TransportKind.File:
|
|
|
|
todo2: int
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
type
|
2018-05-22 22:29:07 +00:00
|
|
|
StreamCallback* = proc(server: StreamServer,
|
2018-06-06 21:29:37 +00:00
|
|
|
client: StreamTransport): Future[void] {.gcsafe.}
|
2018-05-28 23:35:15 +00:00
|
|
|
## New remote client connection callback
|
|
|
|
## ``server`` - StreamServer object.
|
|
|
|
## ``client`` - accepted client transport.
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-06-10 23:08:17 +00:00
|
|
|
TransportInitCallback* = proc(server: StreamServer,
|
|
|
|
fd: AsyncFD): StreamTransport {.gcsafe.}
|
2018-10-25 10:19:19 +00:00
|
|
|
## Custom transport initialization procedure, which can allocate inherited
|
2018-06-10 23:08:17 +00:00
|
|
|
## StreamTransport object.
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
StreamServer* = ref object of SocketServer
|
2018-06-10 23:08:17 +00:00
|
|
|
## StreamServer object
|
|
|
|
function*: StreamCallback # callback which will be called after new
|
|
|
|
# client accepted
|
|
|
|
init*: TransportInitCallback # callback which will be called before
|
|
|
|
# transport for new client
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
proc remoteAddress*(transp: StreamTransport): TransportAddress =
|
|
|
|
## Returns ``transp`` remote socket address.
|
|
|
|
if transp.kind != TransportKind.Socket:
|
|
|
|
raise newException(TransportError, "Socket required!")
|
2018-10-25 10:19:19 +00:00
|
|
|
if transp.remote.family == AddressFamily.None:
|
2018-05-16 08:22:34 +00:00
|
|
|
var saddr: Sockaddr_storage
|
|
|
|
var slen = SockLen(sizeof(saddr))
|
|
|
|
if getpeername(SocketHandle(transp.fd), cast[ptr SockAddr](addr saddr),
|
|
|
|
addr slen) != 0:
|
2018-06-15 00:28:02 +00:00
|
|
|
raiseTransportOsError(osLastError())
|
2018-10-25 10:19:19 +00:00
|
|
|
fromSAddr(addr saddr, slen, transp.remote)
|
2018-05-16 08:22:34 +00:00
|
|
|
result = transp.remote
|
|
|
|
|
|
|
|
proc localAddress*(transp: StreamTransport): TransportAddress =
|
|
|
|
## Returns ``transp`` local socket address.
|
|
|
|
if transp.kind != TransportKind.Socket:
|
|
|
|
raise newException(TransportError, "Socket required!")
|
2018-10-25 10:19:19 +00:00
|
|
|
if transp.local.family == AddressFamily.None:
|
2018-05-16 08:22:34 +00:00
|
|
|
var saddr: Sockaddr_storage
|
|
|
|
var slen = SockLen(sizeof(saddr))
|
|
|
|
if getsockname(SocketHandle(transp.fd), cast[ptr SockAddr](addr saddr),
|
|
|
|
addr slen) != 0:
|
2018-06-15 00:28:02 +00:00
|
|
|
raiseTransportOsError(osLastError())
|
2018-10-25 10:19:19 +00:00
|
|
|
fromSAddr(addr saddr, slen, transp.local)
|
2018-05-16 08:22:34 +00:00
|
|
|
result = transp.local
|
|
|
|
|
2019-10-09 12:12:19 +00:00
|
|
|
proc localAddress*(server: StreamServer): TransportAddress =
|
|
|
|
## Returns ``server`` bound local socket address.
|
|
|
|
result = server.local
|
|
|
|
|
2020-03-05 09:59:10 +00:00
|
|
|
template completeReader(stream: StreamTransport) =
|
|
|
|
if not(isNil(transp.reader)) and not(transp.reader.finished()):
|
|
|
|
transp.reader.complete()
|
|
|
|
transp.reader = nil
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
template setReadError(t, e: untyped) =
|
|
|
|
(t).state.incl(ReadError)
|
2018-10-03 00:44:39 +00:00
|
|
|
(t).error = getTransportOsError(e)
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
template checkPending(t: untyped) =
|
|
|
|
if not isNil((t).reader):
|
|
|
|
raise newException(TransportError, "Read operation already pending!")
|
|
|
|
|
|
|
|
template shiftBuffer(t, c: untyped) =
|
|
|
|
if (t).offset > c:
|
2020-03-05 09:59:10 +00:00
|
|
|
if c > 0:
|
|
|
|
moveMem(addr((t).buffer[0]), addr((t).buffer[(c)]), (t).offset - (c))
|
|
|
|
(t).offset = (t).offset - (c)
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
|
|
|
(t).offset = 0
|
|
|
|
|
2018-05-21 21:52:57 +00:00
|
|
|
template shiftVectorBuffer(v, o: untyped) =
|
|
|
|
(v).buf = cast[pointer](cast[uint]((v).buf) + uint(o))
|
|
|
|
(v).buflen -= int(o)
|
|
|
|
|
|
|
|
template shiftVectorFile(v, o: untyped) =
|
|
|
|
(v).buf = cast[pointer](cast[uint]((v).buf) - cast[uint](o))
|
|
|
|
(v).offset += cast[uint]((o))
|
|
|
|
|
2019-04-04 09:34:23 +00:00
|
|
|
proc setupStreamTransportTracker(): StreamTransportTracker {.gcsafe.}
|
|
|
|
proc setupStreamServerTracker(): StreamServerTracker {.gcsafe.}
|
|
|
|
|
|
|
|
proc getStreamTransportTracker(): StreamTransportTracker {.inline.} =
|
|
|
|
result = cast[StreamTransportTracker](getTracker(StreamTransportTrackerName))
|
|
|
|
if isNil(result):
|
|
|
|
result = setupStreamTransportTracker()
|
|
|
|
|
|
|
|
proc getStreamServerTracker(): StreamServerTracker {.inline.} =
|
|
|
|
result = cast[StreamServerTracker](getTracker(StreamServerTrackerName))
|
|
|
|
if isNil(result):
|
|
|
|
result = setupStreamServerTracker()
|
|
|
|
|
|
|
|
proc dumpTransportTracking(): string {.gcsafe.} =
|
|
|
|
var tracker = getStreamTransportTracker()
|
|
|
|
result = "Opened transports: " & $tracker.opened & "\n" &
|
|
|
|
"Closed transports: " & $tracker.closed
|
|
|
|
|
|
|
|
proc dumpServerTracking(): string {.gcsafe.} =
|
|
|
|
var tracker = getStreamServerTracker()
|
|
|
|
result = "Opened servers: " & $tracker.opened & "\n" &
|
|
|
|
"Closed servers: " & $tracker.closed
|
|
|
|
|
|
|
|
proc leakTransport(): bool {.gcsafe.} =
|
|
|
|
var tracker = getStreamTransportTracker()
|
|
|
|
result = tracker.opened != tracker.closed
|
|
|
|
|
|
|
|
proc leakServer(): bool {.gcsafe.} =
|
|
|
|
var tracker = getStreamServerTracker()
|
|
|
|
result = tracker.opened != tracker.closed
|
|
|
|
|
|
|
|
proc trackStream(t: StreamTransport) {.inline.} =
|
|
|
|
var tracker = getStreamTransportTracker()
|
|
|
|
inc(tracker.opened)
|
|
|
|
|
|
|
|
proc untrackStream(t: StreamTransport) {.inline.} =
|
|
|
|
var tracker = getStreamTransportTracker()
|
|
|
|
inc(tracker.closed)
|
|
|
|
|
|
|
|
proc trackServer(s: StreamServer) {.inline.} =
|
|
|
|
var tracker = getStreamServerTracker()
|
|
|
|
inc(tracker.opened)
|
|
|
|
|
|
|
|
proc untrackServer(s: StreamServer) {.inline.} =
|
|
|
|
var tracker = getStreamServerTracker()
|
|
|
|
inc(tracker.closed)
|
|
|
|
|
|
|
|
proc setupStreamTransportTracker(): StreamTransportTracker {.gcsafe.} =
|
|
|
|
result = new StreamTransportTracker
|
|
|
|
result.opened = 0
|
|
|
|
result.closed = 0
|
|
|
|
result.dump = dumpTransportTracking
|
|
|
|
result.isLeaked = leakTransport
|
|
|
|
addTracker(StreamTransportTrackerName, result)
|
|
|
|
|
|
|
|
proc setupStreamServerTracker(): StreamServerTracker {.gcsafe.} =
|
|
|
|
result = new StreamServerTracker
|
|
|
|
result.opened = 0
|
|
|
|
result.closed = 0
|
|
|
|
result.dump = dumpServerTracking
|
|
|
|
result.isLeaked = leakServer
|
|
|
|
addTracker(StreamServerTrackerName, result)
|
|
|
|
|
2019-05-28 06:29:00 +00:00
|
|
|
proc completePendingWriteQueue(queue: var Deque[StreamVector],
|
|
|
|
v: int) {.inline.} =
|
|
|
|
while len(queue) > 0:
|
|
|
|
var vector = queue.popFirst()
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(v)
|
2019-05-28 06:29:00 +00:00
|
|
|
|
2019-08-28 04:57:06 +00:00
|
|
|
proc failPendingWriteQueue(queue: var Deque[StreamVector],
|
|
|
|
error: ref Exception) {.inline.} =
|
|
|
|
while len(queue) > 0:
|
|
|
|
var vector = queue.popFirst()
|
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.fail(error)
|
|
|
|
|
2020-06-24 08:21:52 +00:00
|
|
|
proc clean(server: StreamServer) {.inline.} =
|
|
|
|
if not(server.loopFuture.finished()):
|
|
|
|
untrackServer(server)
|
|
|
|
server.loopFuture.complete()
|
|
|
|
if not isNil(server.udata) and GCUserData in server.flags:
|
|
|
|
GC_unref(cast[ref int](server.udata))
|
|
|
|
GC_unref(server)
|
|
|
|
|
|
|
|
proc clean(transp: StreamTransport) {.inline.} =
|
|
|
|
if not(transp.future.finished()):
|
|
|
|
untrackStream(transp)
|
|
|
|
transp.future.complete()
|
|
|
|
GC_unref(transp)
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
when defined(windows):
|
|
|
|
|
|
|
|
template zeroOvelappedOffset(t: untyped) =
|
|
|
|
(t).offset = 0
|
|
|
|
(t).offsetHigh = 0
|
|
|
|
|
|
|
|
template setOverlappedOffset(t, o: untyped) =
|
|
|
|
(t).offset = cast[int32](cast[uint64](o) and 0xFFFFFFFF'u64)
|
|
|
|
(t).offsetHigh = cast[int32](cast[uint64](o) shr 32)
|
|
|
|
|
2018-05-21 21:52:57 +00:00
|
|
|
template getFileSize(v: untyped): uint =
|
|
|
|
cast[uint]((v).buf)
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-05-21 21:52:57 +00:00
|
|
|
template getFileHandle(v: untyped): Handle =
|
|
|
|
cast[Handle]((v).buflen)
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-05-21 21:52:57 +00:00
|
|
|
template setReaderWSABuffer(t: untyped) =
|
|
|
|
(t).rwsabuf.buf = cast[cstring](
|
2018-05-16 08:22:34 +00:00
|
|
|
cast[uint](addr t.buffer[0]) + uint((t).roffset))
|
2018-05-21 21:52:57 +00:00
|
|
|
(t).rwsabuf.len = int32(len((t).buffer) - (t).roffset)
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-05-21 21:52:57 +00:00
|
|
|
template setWriterWSABuffer(t, v: untyped) =
|
|
|
|
(t).wwsabuf.buf = cast[cstring](v.buf)
|
|
|
|
(t).wwsabuf.len = cast[int32](v.buflen)
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2019-05-28 06:29:00 +00:00
|
|
|
proc isConnResetError(err: OSErrorCode): bool {.inline.} =
|
|
|
|
result = (err == OSErrorCode(WSAECONNRESET)) or
|
|
|
|
(err == OSErrorCode(WSAECONNABORTED)) or
|
|
|
|
(err == OSErrorCode(ERROR_PIPE_NOT_CONNECTED))
|
|
|
|
|
2018-05-22 16:27:20 +00:00
|
|
|
proc writeStreamLoop(udata: pointer) {.gcsafe, nimcall.} =
|
2018-05-16 08:22:34 +00:00
|
|
|
var bytesCount: int32
|
2018-05-21 21:52:57 +00:00
|
|
|
var ovl = cast[PtrCustomOverlapped](udata)
|
2018-06-10 23:08:17 +00:00
|
|
|
var transp = cast[StreamTransport](ovl.data.udata)
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
while len(transp.queue) > 0:
|
|
|
|
if WritePending in transp.state:
|
|
|
|
## Continuation
|
|
|
|
transp.state.excl(WritePending)
|
|
|
|
let err = transp.wovl.data.errCode
|
|
|
|
if err == OSErrorCode(-1):
|
|
|
|
bytesCount = transp.wovl.data.bytesCount
|
|
|
|
var vector = transp.queue.popFirst()
|
|
|
|
if bytesCount == 0:
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(0)
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
|
|
|
if transp.kind == TransportKind.Socket:
|
|
|
|
if vector.kind == VectorKind.DataBuffer:
|
2018-05-21 21:52:57 +00:00
|
|
|
if bytesCount < transp.wwsabuf.len:
|
|
|
|
vector.shiftVectorBuffer(bytesCount)
|
2018-05-16 08:22:34 +00:00
|
|
|
transp.queue.addFirst(vector)
|
|
|
|
else:
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(transp.wwsabuf.len)
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
|
|
|
if uint(bytesCount) < getFileSize(vector):
|
2018-05-21 21:52:57 +00:00
|
|
|
vector.shiftVectorFile(bytesCount)
|
2018-05-16 08:22:34 +00:00
|
|
|
transp.queue.addFirst(vector)
|
|
|
|
else:
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(int(getFileSize(vector)))
|
2018-10-25 10:19:19 +00:00
|
|
|
elif transp.kind == TransportKind.Pipe:
|
|
|
|
if vector.kind == VectorKind.DataBuffer:
|
|
|
|
if bytesCount < transp.wwsabuf.len:
|
|
|
|
vector.shiftVectorBuffer(bytesCount)
|
|
|
|
transp.queue.addFirst(vector)
|
|
|
|
else:
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(transp.wwsabuf.len)
|
2018-05-28 23:35:15 +00:00
|
|
|
elif int(err) == ERROR_OPERATION_ABORTED:
|
|
|
|
# CancelIO() interrupt
|
2019-08-28 04:57:06 +00:00
|
|
|
transp.state.incl({WritePaused, WriteEof})
|
|
|
|
let vector = transp.queue.popFirst()
|
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(0)
|
|
|
|
completePendingWriteQueue(transp.queue, 0)
|
2018-06-05 20:21:07 +00:00
|
|
|
break
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
2019-08-28 04:57:06 +00:00
|
|
|
let vector = transp.queue.popFirst()
|
2019-05-28 06:29:00 +00:00
|
|
|
if isConnResetError(err):
|
|
|
|
# Soft error happens which indicates that remote peer got
|
|
|
|
# disconnected, complete all pending writes in queue with 0.
|
2019-08-28 04:57:06 +00:00
|
|
|
transp.state.incl({WritePaused, WriteEof})
|
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(0)
|
2019-05-28 06:29:00 +00:00
|
|
|
completePendingWriteQueue(transp.queue, 0)
|
|
|
|
break
|
|
|
|
else:
|
2019-08-28 04:57:06 +00:00
|
|
|
transp.state.incl({WritePaused, WriteError})
|
|
|
|
let error = getTransportOsError(err)
|
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.fail(error)
|
|
|
|
failPendingWriteQueue(transp.queue, error)
|
|
|
|
break
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
|
|
|
## Initiation
|
|
|
|
transp.state.incl(WritePending)
|
|
|
|
if transp.kind == TransportKind.Socket:
|
|
|
|
let sock = SocketHandle(transp.wovl.data.fd)
|
2018-05-16 15:28:23 +00:00
|
|
|
var vector = transp.queue.popFirst()
|
|
|
|
if vector.kind == VectorKind.DataBuffer:
|
2018-05-16 08:22:34 +00:00
|
|
|
transp.wovl.zeroOvelappedOffset()
|
2018-05-21 21:52:57 +00:00
|
|
|
transp.setWriterWSABuffer(vector)
|
|
|
|
let ret = WSASend(sock, addr transp.wwsabuf, 1,
|
2018-05-16 08:22:34 +00:00
|
|
|
addr bytesCount, DWORD(0),
|
|
|
|
cast[POVERLAPPED](addr transp.wovl), nil)
|
|
|
|
if ret != 0:
|
|
|
|
let err = osLastError()
|
2018-05-16 15:28:23 +00:00
|
|
|
if int(err) == ERROR_OPERATION_ABORTED:
|
2018-05-28 23:35:15 +00:00
|
|
|
# CancelIO() interrupt
|
2018-05-21 21:52:57 +00:00
|
|
|
transp.state.excl(WritePending)
|
2019-08-28 04:57:06 +00:00
|
|
|
transp.state.incl({WritePaused, WriteEof})
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(0)
|
2019-08-28 04:57:06 +00:00
|
|
|
completePendingWriteQueue(transp.queue, 0)
|
|
|
|
break
|
2018-05-16 15:28:23 +00:00
|
|
|
elif int(err) == ERROR_IO_PENDING:
|
|
|
|
transp.queue.addFirst(vector)
|
|
|
|
else:
|
2018-05-16 08:22:34 +00:00
|
|
|
transp.state.excl(WritePending)
|
2019-05-28 06:29:00 +00:00
|
|
|
if isConnResetError(err):
|
|
|
|
# Soft error happens which indicates that remote peer got
|
|
|
|
# disconnected, complete all pending writes in queue with 0.
|
|
|
|
transp.state.incl({WritePaused, WriteEof})
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(0)
|
2019-05-28 06:29:00 +00:00
|
|
|
completePendingWriteQueue(transp.queue, 0)
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
transp.state.incl({WritePaused, WriteError})
|
2019-08-28 04:57:06 +00:00
|
|
|
let error = getTransportOsError(err)
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
2019-08-28 04:57:06 +00:00
|
|
|
vector.writer.fail(error)
|
|
|
|
failPendingWriteQueue(transp.queue, error)
|
|
|
|
break
|
2018-05-16 15:28:23 +00:00
|
|
|
else:
|
|
|
|
transp.queue.addFirst(vector)
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
|
|
|
let loop = getGlobalDispatcher()
|
|
|
|
var size: int32
|
|
|
|
var flags: int32
|
|
|
|
|
|
|
|
if getFileSize(vector) > 2_147_483_646'u:
|
|
|
|
size = 2_147_483_646
|
|
|
|
else:
|
|
|
|
size = int32(getFileSize(vector))
|
|
|
|
|
|
|
|
transp.wovl.setOverlappedOffset(vector.offset)
|
|
|
|
var ret = loop.transmitFile(sock, getFileHandle(vector), size, 0,
|
|
|
|
cast[POVERLAPPED](addr transp.wovl),
|
|
|
|
nil, flags)
|
|
|
|
if ret == 0:
|
|
|
|
let err = osLastError()
|
2018-05-16 15:28:23 +00:00
|
|
|
if int(err) == ERROR_OPERATION_ABORTED:
|
2018-05-28 23:35:15 +00:00
|
|
|
# CancelIO() interrupt
|
2018-05-21 21:52:57 +00:00
|
|
|
transp.state.excl(WritePending)
|
2019-08-28 04:57:06 +00:00
|
|
|
transp.state.incl({WritePaused, WriteEof})
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(0)
|
2019-08-28 04:57:06 +00:00
|
|
|
completePendingWriteQueue(transp.queue, 0)
|
|
|
|
break
|
2018-05-16 15:28:23 +00:00
|
|
|
elif int(err) == ERROR_IO_PENDING:
|
|
|
|
transp.queue.addFirst(vector)
|
|
|
|
else:
|
2018-05-16 08:22:34 +00:00
|
|
|
transp.state.excl(WritePending)
|
2019-05-28 06:29:00 +00:00
|
|
|
if isConnResetError(err):
|
|
|
|
# Soft error happens which indicates that remote peer got
|
|
|
|
# disconnected, complete all pending writes in queue with 0.
|
|
|
|
transp.state.incl({WritePaused, WriteEof})
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(0)
|
2019-05-28 06:29:00 +00:00
|
|
|
completePendingWriteQueue(transp.queue, 0)
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
transp.state.incl({WritePaused, WriteError})
|
2019-08-28 04:57:06 +00:00
|
|
|
let error = getTransportOsError(err)
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
2019-08-28 04:57:06 +00:00
|
|
|
vector.writer.fail(error)
|
|
|
|
failPendingWriteQueue(transp.queue, error)
|
|
|
|
break
|
2018-05-16 15:28:23 +00:00
|
|
|
else:
|
|
|
|
transp.queue.addFirst(vector)
|
2018-10-25 10:19:19 +00:00
|
|
|
elif transp.kind == TransportKind.Pipe:
|
|
|
|
let pipe = Handle(transp.wovl.data.fd)
|
|
|
|
var vector = transp.queue.popFirst()
|
|
|
|
if vector.kind == VectorKind.DataBuffer:
|
|
|
|
transp.wovl.zeroOvelappedOffset()
|
|
|
|
transp.setWriterWSABuffer(vector)
|
|
|
|
let ret = writeFile(pipe, cast[pointer](transp.wwsabuf.buf),
|
|
|
|
DWORD(transp.wwsabuf.len), addr bytesCount,
|
|
|
|
cast[POVERLAPPED](addr transp.wovl))
|
|
|
|
if ret == 0:
|
|
|
|
let err = osLastError()
|
2019-08-28 04:57:06 +00:00
|
|
|
if int(err) in {ERROR_OPERATION_ABORTED, ERROR_NO_DATA}:
|
2018-10-25 10:19:19 +00:00
|
|
|
# CancelIO() interrupt
|
|
|
|
transp.state.excl(WritePending)
|
2019-08-28 04:57:06 +00:00
|
|
|
transp.state.incl({WritePaused, WriteEof})
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(0)
|
2019-08-28 04:57:06 +00:00
|
|
|
completePendingWriteQueue(transp.queue, 0)
|
|
|
|
break
|
2018-10-25 10:19:19 +00:00
|
|
|
elif int(err) == ERROR_IO_PENDING:
|
|
|
|
transp.queue.addFirst(vector)
|
|
|
|
else:
|
|
|
|
transp.state.excl(WritePending)
|
2019-05-28 06:29:00 +00:00
|
|
|
if isConnResetError(err):
|
|
|
|
# Soft error happens which indicates that remote peer got
|
|
|
|
# disconnected, complete all pending writes in queue with 0.
|
|
|
|
transp.state.incl({WritePaused, WriteEof})
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(0)
|
2019-05-28 06:29:00 +00:00
|
|
|
completePendingWriteQueue(transp.queue, 0)
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
transp.state.incl({WritePaused, WriteError})
|
2019-08-28 04:57:06 +00:00
|
|
|
let error = getTransportOsError(err)
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
2019-08-28 04:57:06 +00:00
|
|
|
vector.writer.fail(error)
|
|
|
|
failPendingWriteQueue(transp.queue, error)
|
|
|
|
break
|
2018-10-25 10:19:19 +00:00
|
|
|
else:
|
|
|
|
transp.queue.addFirst(vector)
|
2018-05-16 08:22:34 +00:00
|
|
|
break
|
|
|
|
|
|
|
|
if len(transp.queue) == 0:
|
|
|
|
transp.state.incl(WritePaused)
|
|
|
|
|
2018-05-22 16:27:20 +00:00
|
|
|
proc readStreamLoop(udata: pointer) {.gcsafe, nimcall.} =
|
2018-05-21 21:52:57 +00:00
|
|
|
var ovl = cast[PtrCustomOverlapped](udata)
|
2018-06-10 23:08:17 +00:00
|
|
|
var transp = cast[StreamTransport](ovl.data.udata)
|
2018-05-16 08:22:34 +00:00
|
|
|
while true:
|
|
|
|
if ReadPending in transp.state:
|
|
|
|
## Continuation
|
2018-05-21 21:52:57 +00:00
|
|
|
transp.state.excl(ReadPending)
|
2018-05-16 08:22:34 +00:00
|
|
|
let err = transp.rovl.data.errCode
|
|
|
|
if err == OSErrorCode(-1):
|
|
|
|
let bytesCount = transp.rovl.data.bytesCount
|
|
|
|
if bytesCount == 0:
|
2018-06-14 06:49:59 +00:00
|
|
|
transp.state.incl({ReadEof, ReadPaused})
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
|
|
|
if transp.offset != transp.roffset:
|
|
|
|
moveMem(addr transp.buffer[transp.offset],
|
|
|
|
addr transp.buffer[transp.roffset],
|
|
|
|
bytesCount)
|
|
|
|
transp.offset += bytesCount
|
|
|
|
transp.roffset = transp.offset
|
|
|
|
if transp.offset == len(transp.buffer):
|
|
|
|
transp.state.incl(ReadPaused)
|
2019-03-30 22:31:10 +00:00
|
|
|
elif int(err) in {ERROR_OPERATION_ABORTED, ERROR_CONNECTION_ABORTED,
|
|
|
|
ERROR_BROKEN_PIPE, ERROR_NETNAME_DELETED}:
|
|
|
|
# CancelIO() interrupt or closeSocket() call.
|
2018-07-03 05:35:45 +00:00
|
|
|
transp.state.incl(ReadPaused)
|
2018-10-25 10:19:19 +00:00
|
|
|
elif transp.kind == TransportKind.Socket and
|
|
|
|
(int(err) in {ERROR_NETNAME_DELETED, WSAECONNABORTED}):
|
|
|
|
transp.state.incl({ReadEof, ReadPaused})
|
|
|
|
elif transp.kind == TransportKind.Pipe and
|
2019-03-30 22:31:10 +00:00
|
|
|
(int(err) in {ERROR_PIPE_NOT_CONNECTED}):
|
2018-07-03 05:35:45 +00:00
|
|
|
transp.state.incl({ReadEof, ReadPaused})
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
|
|
|
transp.setReadError(err)
|
2019-04-04 09:34:23 +00:00
|
|
|
|
2020-03-05 09:59:10 +00:00
|
|
|
transp.completeReader()
|
2019-04-04 09:34:23 +00:00
|
|
|
|
|
|
|
if ReadClosed in transp.state:
|
|
|
|
# Stop tracking transport
|
2020-06-24 08:21:52 +00:00
|
|
|
transp.clean()
|
2019-04-04 09:34:23 +00:00
|
|
|
|
2018-05-21 21:52:57 +00:00
|
|
|
if ReadPaused in transp.state:
|
|
|
|
# Transport buffer is full, so we will not continue on reading.
|
|
|
|
break
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
|
|
|
## Initiation
|
2018-05-21 21:52:57 +00:00
|
|
|
if transp.state * {ReadEof, ReadClosed, ReadError} == {}:
|
2018-05-16 08:22:34 +00:00
|
|
|
var flags = DWORD(0)
|
|
|
|
var bytesCount: int32 = 0
|
|
|
|
transp.state.excl(ReadPaused)
|
|
|
|
transp.state.incl(ReadPending)
|
|
|
|
if transp.kind == TransportKind.Socket:
|
|
|
|
let sock = SocketHandle(transp.rovl.data.fd)
|
2018-05-21 21:52:57 +00:00
|
|
|
transp.roffset = transp.offset
|
|
|
|
transp.setReaderWSABuffer()
|
|
|
|
let ret = WSARecv(sock, addr transp.rwsabuf, 1,
|
2018-05-16 08:22:34 +00:00
|
|
|
addr bytesCount, addr flags,
|
|
|
|
cast[POVERLAPPED](addr transp.rovl), nil)
|
|
|
|
if ret != 0:
|
|
|
|
let err = osLastError()
|
2018-10-25 10:19:19 +00:00
|
|
|
if int32(err) == ERROR_OPERATION_ABORTED:
|
2018-05-28 23:35:15 +00:00
|
|
|
# CancelIO() interrupt
|
2018-05-21 21:52:57 +00:00
|
|
|
transp.state.excl(ReadPending)
|
2018-05-16 08:22:34 +00:00
|
|
|
transp.state.incl(ReadPaused)
|
2018-07-24 15:43:30 +00:00
|
|
|
elif int32(err) in {WSAECONNRESET, WSAENETRESET, WSAECONNABORTED}:
|
|
|
|
transp.state.excl(ReadPending)
|
|
|
|
transp.state.incl({ReadEof, ReadPaused})
|
2020-03-05 09:59:10 +00:00
|
|
|
transp.completeReader()
|
2018-05-16 08:22:34 +00:00
|
|
|
elif int32(err) != ERROR_IO_PENDING:
|
2018-06-04 09:57:17 +00:00
|
|
|
transp.state.excl(ReadPending)
|
|
|
|
transp.state.incl(ReadPaused)
|
2018-05-16 08:22:34 +00:00
|
|
|
transp.setReadError(err)
|
2020-03-05 09:59:10 +00:00
|
|
|
transp.completeReader()
|
2018-10-25 10:19:19 +00:00
|
|
|
elif transp.kind == TransportKind.Pipe:
|
|
|
|
let pipe = Handle(transp.rovl.data.fd)
|
|
|
|
transp.roffset = transp.offset
|
|
|
|
transp.setReaderWSABuffer()
|
|
|
|
let ret = readFile(pipe, cast[pointer](transp.rwsabuf.buf),
|
|
|
|
DWORD(transp.rwsabuf.len), addr bytesCount,
|
|
|
|
cast[POVERLAPPED](addr transp.rovl))
|
|
|
|
if ret == 0:
|
|
|
|
let err = osLastError()
|
|
|
|
if int32(err) == ERROR_OPERATION_ABORTED:
|
|
|
|
# CancelIO() interrupt
|
|
|
|
transp.state.excl(ReadPending)
|
|
|
|
transp.state.incl(ReadPaused)
|
|
|
|
elif int32(err) in {ERROR_BROKEN_PIPE, ERROR_PIPE_NOT_CONNECTED}:
|
|
|
|
transp.state.excl(ReadPending)
|
|
|
|
transp.state.incl({ReadEof, ReadPaused})
|
2020-03-05 09:59:10 +00:00
|
|
|
transp.completeReader()
|
2018-10-25 10:19:19 +00:00
|
|
|
elif int32(err) != ERROR_IO_PENDING:
|
|
|
|
transp.state.excl(ReadPending)
|
|
|
|
transp.state.incl(ReadPaused)
|
|
|
|
transp.setReadError(err)
|
2020-03-05 09:59:10 +00:00
|
|
|
transp.completeReader()
|
2018-08-24 12:20:08 +00:00
|
|
|
else:
|
|
|
|
transp.state.incl(ReadPaused)
|
2020-03-05 09:59:10 +00:00
|
|
|
transp.completeReader()
|
2019-03-30 22:31:10 +00:00
|
|
|
# Transport close happens in callback, and we not started new
|
|
|
|
# WSARecvFrom session.
|
|
|
|
if ReadClosed in transp.state:
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(transp.future.finished()):
|
2019-03-30 22:31:10 +00:00
|
|
|
transp.future.complete()
|
2018-05-16 08:22:34 +00:00
|
|
|
## Finish Loop
|
|
|
|
break
|
|
|
|
|
2018-06-10 23:08:17 +00:00
|
|
|
proc newStreamSocketTransport(sock: AsyncFD, bufsize: int,
|
|
|
|
child: StreamTransport): StreamTransport =
|
|
|
|
var transp: StreamTransport
|
|
|
|
if not isNil(child):
|
|
|
|
transp = child
|
|
|
|
else:
|
|
|
|
transp = StreamTransport(kind: TransportKind.Socket)
|
2018-05-21 21:52:57 +00:00
|
|
|
transp.fd = sock
|
|
|
|
transp.rovl.data = CompletionData(fd: sock, cb: readStreamLoop,
|
|
|
|
udata: cast[pointer](transp))
|
|
|
|
transp.wovl.data = CompletionData(fd: sock, cb: writeStreamLoop,
|
|
|
|
udata: cast[pointer](transp))
|
|
|
|
transp.buffer = newSeq[byte](bufsize)
|
|
|
|
transp.state = {ReadPaused, WritePaused}
|
|
|
|
transp.queue = initDeque[StreamVector]()
|
|
|
|
transp.future = newFuture[void]("stream.socket.transport")
|
|
|
|
GC_ref(transp)
|
2018-06-10 23:08:17 +00:00
|
|
|
result = transp
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-10-25 10:19:19 +00:00
|
|
|
proc newStreamPipeTransport(fd: AsyncFD, bufsize: int,
|
|
|
|
child: StreamTransport,
|
|
|
|
flags: set[TransportFlags] = {}): StreamTransport =
|
|
|
|
var transp: StreamTransport
|
|
|
|
if not isNil(child):
|
|
|
|
transp = child
|
|
|
|
else:
|
|
|
|
transp = StreamTransport(kind: TransportKind.Pipe)
|
|
|
|
transp.fd = fd
|
|
|
|
transp.rovl.data = CompletionData(fd: fd, cb: readStreamLoop,
|
|
|
|
udata: cast[pointer](transp))
|
|
|
|
transp.wovl.data = CompletionData(fd: fd, cb: writeStreamLoop,
|
|
|
|
udata: cast[pointer](transp))
|
|
|
|
transp.buffer = newSeq[byte](bufsize)
|
|
|
|
transp.flags = flags
|
|
|
|
transp.state = {ReadPaused, WritePaused}
|
|
|
|
transp.queue = initDeque[StreamVector]()
|
|
|
|
transp.future = newFuture[void]("stream.pipe.transport")
|
|
|
|
GC_ref(transp)
|
|
|
|
result = transp
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
proc bindToDomain(handle: AsyncFD, domain: Domain): bool =
|
|
|
|
result = true
|
|
|
|
if domain == Domain.AF_INET6:
|
|
|
|
var saddr: Sockaddr_in6
|
2018-09-26 18:05:53 +00:00
|
|
|
saddr.sin6_family = type(saddr.sin6_family)(toInt(domain))
|
2018-05-16 08:22:34 +00:00
|
|
|
if bindAddr(SocketHandle(handle), cast[ptr SockAddr](addr(saddr)),
|
|
|
|
sizeof(saddr).SockLen) != 0'i32:
|
|
|
|
result = false
|
2018-10-25 10:19:19 +00:00
|
|
|
elif domain == Domain.AF_INET:
|
2018-05-16 08:22:34 +00:00
|
|
|
var saddr: Sockaddr_in
|
2018-09-26 18:05:53 +00:00
|
|
|
saddr.sin_family = type(saddr.sin_family)(toInt(domain))
|
2018-05-16 08:22:34 +00:00
|
|
|
if bindAddr(SocketHandle(handle), cast[ptr SockAddr](addr(saddr)),
|
|
|
|
sizeof(saddr).SockLen) != 0'i32:
|
|
|
|
result = false
|
|
|
|
|
|
|
|
proc connect*(address: TransportAddress,
|
2018-06-10 23:08:17 +00:00
|
|
|
bufferSize = DefaultStreamBufferSize,
|
2018-10-25 10:19:19 +00:00
|
|
|
child: StreamTransport = nil,
|
|
|
|
flags: set[TransportFlags] = {}): Future[StreamTransport] =
|
2018-05-27 05:49:47 +00:00
|
|
|
## Open new connection to remote peer with address ``address`` and create
|
|
|
|
## new transport object ``StreamTransport`` for established connection.
|
2018-05-28 23:35:15 +00:00
|
|
|
## ``bufferSize`` is size of internal buffer for transport.
|
2018-05-16 08:22:34 +00:00
|
|
|
let loop = getGlobalDispatcher()
|
|
|
|
|
|
|
|
var retFuture = newFuture[StreamTransport]("stream.transport.connect")
|
2018-10-25 10:19:19 +00:00
|
|
|
if address.family in {AddressFamily.IPv4, AddressFamily.IPv6}:
|
|
|
|
## Socket handling part
|
|
|
|
var
|
|
|
|
saddr: Sockaddr_storage
|
|
|
|
slen: SockLen
|
|
|
|
sock: AsyncFD
|
|
|
|
povl: RefCustomOverlapped
|
|
|
|
proto: Protocol
|
2020-02-25 21:50:39 +00:00
|
|
|
|
|
|
|
var raddress = windowsAnyAddressFix(address)
|
2018-10-25 10:19:19 +00:00
|
|
|
|
2019-10-09 12:12:19 +00:00
|
|
|
toSAddr(raddress, saddr, slen)
|
2018-10-25 10:19:19 +00:00
|
|
|
proto = Protocol.IPPROTO_TCP
|
2019-10-09 12:12:19 +00:00
|
|
|
sock = createAsyncSocket(raddress.getDomain(), SockType.SOCK_STREAM,
|
|
|
|
proto)
|
2018-10-25 10:19:19 +00:00
|
|
|
if sock == asyncInvalidSocket:
|
2019-06-20 20:30:41 +00:00
|
|
|
retFuture.fail(getTransportOsError(osLastError()))
|
|
|
|
return retFuture
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2019-10-09 12:12:19 +00:00
|
|
|
if not bindToDomain(sock, raddress.getDomain()):
|
2018-10-25 10:19:19 +00:00
|
|
|
let err = wsaGetLastError()
|
|
|
|
sock.closeSocket()
|
|
|
|
retFuture.fail(getTransportOsError(err))
|
|
|
|
return retFuture
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2019-06-20 20:30:41 +00:00
|
|
|
proc socketContinuation(udata: pointer) {.gcsafe.} =
|
2018-10-25 10:19:19 +00:00
|
|
|
var ovl = cast[RefCustomOverlapped](udata)
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(retFuture.finished()):
|
2018-10-25 10:19:19 +00:00
|
|
|
if ovl.data.errCode == OSErrorCode(-1):
|
|
|
|
if setsockopt(SocketHandle(sock), cint(SOL_SOCKET),
|
|
|
|
cint(SO_UPDATE_CONNECT_CONTEXT), nil,
|
|
|
|
SockLen(0)) != 0'i32:
|
|
|
|
let err = wsaGetLastError()
|
|
|
|
sock.closeSocket()
|
|
|
|
retFuture.fail(getTransportOsError(err))
|
|
|
|
else:
|
2019-04-04 09:34:23 +00:00
|
|
|
let transp = newStreamSocketTransport(povl.data.fd, bufferSize,
|
|
|
|
child)
|
|
|
|
# Start tracking transport
|
|
|
|
trackStream(transp)
|
|
|
|
retFuture.complete(transp)
|
2018-10-25 10:19:19 +00:00
|
|
|
else:
|
|
|
|
sock.closeSocket()
|
|
|
|
retFuture.fail(getTransportOsError(ovl.data.errCode))
|
|
|
|
GC_unref(ovl)
|
|
|
|
|
2019-06-20 20:30:41 +00:00
|
|
|
proc cancel(udata: pointer) {.gcsafe.} =
|
2020-06-05 16:11:51 +00:00
|
|
|
sock.closeSocket()
|
2019-06-20 20:30:41 +00:00
|
|
|
|
2018-10-25 10:19:19 +00:00
|
|
|
povl = RefCustomOverlapped()
|
|
|
|
GC_ref(povl)
|
|
|
|
povl.data = CompletionData(fd: sock, cb: socketContinuation)
|
2019-07-15 09:59:42 +00:00
|
|
|
var res = loop.connectEx(SocketHandle(sock),
|
|
|
|
cast[ptr SockAddr](addr saddr),
|
|
|
|
DWORD(slen), nil, 0, nil,
|
|
|
|
cast[POVERLAPPED](povl))
|
|
|
|
# We will not process immediate completion, to avoid undefined behavior.
|
|
|
|
if not res:
|
|
|
|
let err = osLastError()
|
|
|
|
if int32(err) != ERROR_IO_PENDING:
|
|
|
|
GC_unref(povl)
|
|
|
|
sock.closeSocket()
|
|
|
|
retFuture.fail(getTransportOsError(err))
|
|
|
|
|
|
|
|
retFuture.cancelCallback = cancel
|
2018-10-25 10:19:19 +00:00
|
|
|
|
|
|
|
elif address.family == AddressFamily.Unix:
|
|
|
|
## Unix domain socket emulation with Windows Named Pipes.
|
2019-06-20 20:30:41 +00:00
|
|
|
var pipeHandle = INVALID_HANDLE_VALUE
|
2018-10-25 10:19:19 +00:00
|
|
|
proc pipeContinuation(udata: pointer) {.gcsafe.} =
|
2019-06-20 20:30:41 +00:00
|
|
|
# Continue only if `retFuture` is not cancelled.
|
|
|
|
if not(retFuture.finished()):
|
|
|
|
var pipeSuffix = $cast[cstring](unsafeAddr address.address_un[0])
|
|
|
|
var pipeName = newWideCString(r"\\.\pipe\" & pipeSuffix[1 .. ^1])
|
|
|
|
pipeHandle = createFileW(pipeName, GENERIC_READ or GENERIC_WRITE,
|
|
|
|
FILE_SHARE_READ or FILE_SHARE_WRITE,
|
|
|
|
nil, OPEN_EXISTING,
|
|
|
|
FILE_FLAG_OVERLAPPED, Handle(0))
|
|
|
|
if pipeHandle == INVALID_HANDLE_VALUE:
|
|
|
|
let err = osLastError()
|
|
|
|
if int32(err) == ERROR_PIPE_BUSY:
|
2020-01-27 18:28:44 +00:00
|
|
|
discard setTimer(Moment.fromNow(50.milliseconds),
|
|
|
|
pipeContinuation, nil)
|
2019-06-20 20:30:41 +00:00
|
|
|
else:
|
|
|
|
retFuture.fail(getTransportOsError(err))
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
2019-06-20 20:30:41 +00:00
|
|
|
register(AsyncFD(pipeHandle))
|
|
|
|
let transp = newStreamPipeTransport(AsyncFD(pipeHandle),
|
|
|
|
bufferSize, child)
|
|
|
|
# Start tracking transport
|
|
|
|
trackStream(transp)
|
|
|
|
retFuture.complete(transp)
|
2018-10-25 10:19:19 +00:00
|
|
|
pipeContinuation(nil)
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
return retFuture
|
|
|
|
|
2020-06-24 08:21:52 +00:00
|
|
|
proc createAcceptPipe(server: StreamServer) =
|
|
|
|
let pipeSuffix = $cast[cstring](addr server.local.address_un)
|
|
|
|
let pipeName = newWideCString(r"\\.\pipe\" & pipeSuffix[1 .. ^1])
|
|
|
|
var openMode = PIPE_ACCESS_DUPLEX or FILE_FLAG_OVERLAPPED
|
|
|
|
if FirstPipe notin server.flags:
|
|
|
|
openMode = openMode or FILE_FLAG_FIRST_PIPE_INSTANCE
|
|
|
|
server.flags.incl(FirstPipe)
|
|
|
|
let pipeMode = int32(PIPE_TYPE_BYTE or PIPE_READMODE_BYTE or PIPE_WAIT)
|
|
|
|
let pipeHandle = createNamedPipe(pipeName, openMode, pipeMode,
|
|
|
|
PIPE_UNLIMITED_INSTANCES,
|
|
|
|
DWORD(server.bufferSize),
|
|
|
|
DWORD(server.bufferSize),
|
|
|
|
DWORD(0), nil)
|
|
|
|
if pipeHandle != INVALID_HANDLE_VALUE:
|
|
|
|
server.sock = AsyncFD(pipeHandle)
|
|
|
|
register(server.sock)
|
|
|
|
else:
|
|
|
|
server.sock = asyncInvalidPipe
|
|
|
|
server.errorCode = osLastError()
|
|
|
|
|
2018-10-25 10:19:19 +00:00
|
|
|
proc acceptPipeLoop(udata: pointer) {.gcsafe, nimcall.} =
|
|
|
|
var ovl = cast[PtrCustomOverlapped](udata)
|
|
|
|
var server = cast[StreamServer](ovl.data.udata)
|
|
|
|
|
|
|
|
while true:
|
|
|
|
if server.apending:
|
|
|
|
## Continuation
|
|
|
|
server.apending = false
|
2020-07-15 08:09:34 +00:00
|
|
|
if server.status notin {ServerStatus.Stopped, ServerStatus.Closed}:
|
|
|
|
if ovl.data.errCode == OSErrorCode(-1):
|
|
|
|
var ntransp: StreamTransport
|
|
|
|
var flags = {WinServerPipe}
|
|
|
|
if NoPipeFlash in server.flags:
|
|
|
|
flags.incl(WinNoPipeFlash)
|
|
|
|
if not isNil(server.init):
|
|
|
|
var transp = server.init(server, server.sock)
|
|
|
|
ntransp = newStreamPipeTransport(server.sock, server.bufferSize,
|
|
|
|
transp, flags)
|
|
|
|
else:
|
|
|
|
ntransp = newStreamPipeTransport(server.sock, server.bufferSize,
|
|
|
|
nil, flags)
|
|
|
|
# Start tracking transport
|
|
|
|
trackStream(ntransp)
|
|
|
|
asyncCheck server.function(server, ntransp)
|
|
|
|
elif int32(ovl.data.errCode) == ERROR_OPERATION_ABORTED:
|
|
|
|
# CancelIO() interrupt or close call.
|
|
|
|
if server.status in {ServerStatus.Closed, ServerStatus.Stopped}:
|
|
|
|
server.clean()
|
|
|
|
break
|
2019-03-30 22:31:10 +00:00
|
|
|
else:
|
2020-07-15 08:09:34 +00:00
|
|
|
# We should not raise defects in this loop.
|
|
|
|
discard disconnectNamedPipe(Handle(server.sock))
|
|
|
|
discard closeHandle(HANDLE(server.sock))
|
|
|
|
raiseTransportOsError(osLastError())
|
|
|
|
else:
|
|
|
|
# Server close happens in callback, and we are not started new
|
|
|
|
# connectNamedPipe session.
|
|
|
|
if not(server.loopFuture.finished()):
|
2020-06-24 08:21:52 +00:00
|
|
|
server.clean()
|
2018-10-25 10:19:19 +00:00
|
|
|
break
|
|
|
|
else:
|
|
|
|
## Initiation
|
2019-03-30 22:31:10 +00:00
|
|
|
if server.status notin {ServerStatus.Stopped, ServerStatus.Closed}:
|
|
|
|
server.apending = true
|
|
|
|
var pipeSuffix = $cast[cstring](addr server.local.address_un)
|
|
|
|
var pipeName = newWideCString(r"\\.\pipe\" & pipeSuffix[1 .. ^1])
|
|
|
|
var openMode = PIPE_ACCESS_DUPLEX or FILE_FLAG_OVERLAPPED
|
|
|
|
if FirstPipe notin server.flags:
|
|
|
|
openMode = openMode or FILE_FLAG_FIRST_PIPE_INSTANCE
|
|
|
|
server.flags.incl(FirstPipe)
|
|
|
|
let pipeMode = int32(PIPE_TYPE_BYTE or PIPE_READMODE_BYTE or PIPE_WAIT)
|
|
|
|
let pipeHandle = createNamedPipe(pipeName, openMode, pipeMode,
|
|
|
|
PIPE_UNLIMITED_INSTANCES,
|
|
|
|
DWORD(server.bufferSize),
|
|
|
|
DWORD(server.bufferSize),
|
|
|
|
DWORD(0), nil)
|
|
|
|
if pipeHandle == INVALID_HANDLE_VALUE:
|
|
|
|
raiseTransportOsError(osLastError())
|
|
|
|
server.sock = AsyncFD(pipeHandle)
|
|
|
|
server.aovl.data.fd = AsyncFD(pipeHandle)
|
|
|
|
register(server.sock)
|
|
|
|
let res = connectNamedPipe(pipeHandle,
|
|
|
|
cast[POVERLAPPED](addr server.aovl))
|
|
|
|
if res == 0:
|
|
|
|
let err = osLastError()
|
|
|
|
if int32(err) == ERROR_OPERATION_ABORTED:
|
|
|
|
server.apending = false
|
|
|
|
break
|
|
|
|
elif int32(err) == ERROR_IO_PENDING:
|
|
|
|
discard
|
|
|
|
elif int32(err) == ERROR_PIPE_CONNECTED:
|
|
|
|
discard
|
|
|
|
else:
|
|
|
|
raiseTransportOsError(err)
|
2018-10-25 10:19:19 +00:00
|
|
|
break
|
2019-03-30 22:31:10 +00:00
|
|
|
else:
|
|
|
|
# Server close happens in callback, and we are not started new
|
|
|
|
# connectNamedPipe session.
|
2020-07-15 08:09:34 +00:00
|
|
|
if not(server.loopFuture.finished()):
|
|
|
|
server.clean()
|
|
|
|
break
|
2018-10-25 10:19:19 +00:00
|
|
|
|
2018-06-04 09:57:17 +00:00
|
|
|
proc acceptLoop(udata: pointer) {.gcsafe, nimcall.} =
|
|
|
|
var ovl = cast[PtrCustomOverlapped](udata)
|
|
|
|
var server = cast[StreamServer](ovl.data.udata)
|
|
|
|
var loop = getGlobalDispatcher()
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-06-04 09:57:17 +00:00
|
|
|
while true:
|
|
|
|
if server.apending:
|
|
|
|
## Continuation
|
|
|
|
server.apending = false
|
2020-07-15 08:09:34 +00:00
|
|
|
if server.status notin {ServerStatus.Stopped, ServerStatus.Closed}:
|
|
|
|
if ovl.data.errCode == OSErrorCode(-1):
|
|
|
|
if setsockopt(SocketHandle(server.asock), cint(SOL_SOCKET),
|
|
|
|
cint(SO_UPDATE_ACCEPT_CONTEXT), addr server.sock,
|
|
|
|
SockLen(sizeof(SocketHandle))) != 0'i32:
|
|
|
|
let err = OSErrorCode(wsaGetLastError())
|
|
|
|
server.asock.closeSocket()
|
|
|
|
raiseTransportOsError(err)
|
2019-03-30 22:31:10 +00:00
|
|
|
else:
|
2020-07-15 08:09:34 +00:00
|
|
|
var ntransp: StreamTransport
|
|
|
|
if not isNil(server.init):
|
|
|
|
let transp = server.init(server, server.asock)
|
|
|
|
ntransp = newStreamSocketTransport(server.asock,
|
|
|
|
server.bufferSize,
|
|
|
|
transp)
|
|
|
|
else:
|
|
|
|
ntransp = newStreamSocketTransport(server.asock,
|
|
|
|
server.bufferSize, nil)
|
|
|
|
# Start tracking transport
|
|
|
|
trackStream(ntransp)
|
|
|
|
asyncCheck server.function(server, ntransp)
|
2019-03-30 22:31:10 +00:00
|
|
|
|
2020-07-15 08:09:34 +00:00
|
|
|
elif int32(ovl.data.errCode) == ERROR_OPERATION_ABORTED:
|
|
|
|
# CancelIO() interrupt or close.
|
|
|
|
server.asock.closeSocket()
|
|
|
|
if server.status in {ServerStatus.Closed, ServerStatus.Stopped}:
|
|
|
|
# Stop tracking server
|
|
|
|
if not(server.loopFuture.finished()):
|
|
|
|
server.clean()
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
server.asock.closeSocket()
|
|
|
|
raiseTransportOsError(ovl.data.errCode)
|
2019-03-30 22:31:10 +00:00
|
|
|
else:
|
2020-07-15 08:09:34 +00:00
|
|
|
# Server close happens in callback, and we are not started new
|
|
|
|
# AcceptEx session.
|
|
|
|
if not(server.loopFuture.finished()):
|
|
|
|
server.clean()
|
|
|
|
break
|
2018-06-04 09:57:17 +00:00
|
|
|
else:
|
|
|
|
## Initiation
|
2019-03-30 22:31:10 +00:00
|
|
|
if server.status notin {ServerStatus.Stopped, ServerStatus.Closed}:
|
|
|
|
server.apending = true
|
|
|
|
server.asock = createAsyncSocket(server.domain, SockType.SOCK_STREAM,
|
|
|
|
Protocol.IPPROTO_TCP)
|
|
|
|
if server.asock == asyncInvalidSocket:
|
|
|
|
raiseTransportOsError(OSErrorCode(wsaGetLastError()))
|
|
|
|
|
|
|
|
var dwBytesReceived = DWORD(0)
|
|
|
|
let dwReceiveDataLength = DWORD(0)
|
|
|
|
let dwLocalAddressLength = DWORD(sizeof(Sockaddr_in6) + 16)
|
|
|
|
let dwRemoteAddressLength = DWORD(sizeof(Sockaddr_in6) + 16)
|
|
|
|
|
|
|
|
let res = loop.acceptEx(SocketHandle(server.sock),
|
|
|
|
SocketHandle(server.asock),
|
|
|
|
addr server.abuffer[0],
|
|
|
|
dwReceiveDataLength, dwLocalAddressLength,
|
|
|
|
dwRemoteAddressLength, addr dwBytesReceived,
|
|
|
|
cast[POVERLAPPED](addr server.aovl))
|
|
|
|
if not res:
|
|
|
|
let err = osLastError()
|
|
|
|
if int32(err) == ERROR_OPERATION_ABORTED:
|
|
|
|
server.apending = false
|
|
|
|
break
|
|
|
|
elif int32(err) == ERROR_IO_PENDING:
|
|
|
|
discard
|
|
|
|
else:
|
|
|
|
raiseTransportOsError(err)
|
2018-06-10 23:08:17 +00:00
|
|
|
break
|
2019-03-30 22:31:10 +00:00
|
|
|
else:
|
|
|
|
# Server close happens in callback, and we are not started new
|
|
|
|
# AcceptEx session.
|
2020-07-15 08:09:34 +00:00
|
|
|
if not(server.loopFuture.finished()):
|
|
|
|
server.clean()
|
|
|
|
break
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
proc resumeRead(transp: StreamTransport) {.inline.} =
|
2020-03-05 09:59:10 +00:00
|
|
|
if ReadPaused in transp.state:
|
|
|
|
transp.state.excl(ReadPaused)
|
|
|
|
readStreamLoop(cast[pointer](addr transp.rovl))
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
proc resumeWrite(transp: StreamTransport) {.inline.} =
|
2020-03-05 09:59:10 +00:00
|
|
|
if WritePaused in transp.state:
|
|
|
|
transp.state.excl(WritePaused)
|
|
|
|
writeStreamLoop(cast[pointer](addr transp.wovl))
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-06-07 08:05:06 +00:00
|
|
|
proc pauseAccept(server: StreamServer) {.inline.} =
|
2018-06-04 09:57:17 +00:00
|
|
|
if server.apending:
|
|
|
|
discard cancelIO(Handle(server.sock))
|
|
|
|
|
2018-06-07 08:05:06 +00:00
|
|
|
proc resumeAccept(server: StreamServer) {.inline.} =
|
2018-06-04 09:57:17 +00:00
|
|
|
if not server.apending:
|
2018-10-25 10:19:19 +00:00
|
|
|
server.aovl.data.cb(addr server.aovl)
|
2018-06-11 07:16:08 +00:00
|
|
|
|
2020-06-24 08:21:52 +00:00
|
|
|
proc accept*(server: StreamServer): Future[StreamTransport] =
|
|
|
|
var retFuture = newFuture[StreamTransport]("stream.server.accept")
|
|
|
|
|
|
|
|
doAssert(server.status != ServerStatus.Running,
|
|
|
|
"You could not use accept() if server was already started")
|
|
|
|
|
|
|
|
if server.status == ServerStatus.Closed:
|
|
|
|
retFuture.fail(getServerUseClosedError())
|
|
|
|
return retFuture
|
|
|
|
|
|
|
|
proc continuationSocket(udata: pointer) {.gcsafe.} =
|
|
|
|
var ovl = cast[PtrCustomOverlapped](udata)
|
|
|
|
var server = cast[StreamServer](ovl.data.udata)
|
|
|
|
|
|
|
|
server.apending = false
|
2020-07-15 08:09:34 +00:00
|
|
|
if server.status in {ServerStatus.Stopped, ServerStatus.Closed}:
|
2020-06-24 08:21:52 +00:00
|
|
|
server.asock.closeSocket()
|
|
|
|
retFuture.fail(getServerUseClosedError())
|
|
|
|
server.clean()
|
|
|
|
else:
|
2020-07-15 08:09:34 +00:00
|
|
|
if ovl.data.errCode == OSErrorCode(-1):
|
|
|
|
if setsockopt(SocketHandle(server.asock), cint(SOL_SOCKET),
|
|
|
|
cint(SO_UPDATE_ACCEPT_CONTEXT), addr server.sock,
|
|
|
|
SockLen(sizeof(SocketHandle))) != 0'i32:
|
|
|
|
let err = OSErrorCode(wsaGetLastError())
|
|
|
|
server.asock.closeSocket()
|
|
|
|
if int32(err) == WSAENOTSOCK:
|
|
|
|
# This can be happened when server get closed, but continuation was
|
|
|
|
# already scheduled, so we failing it not with OS error.
|
|
|
|
retFuture.fail(getServerUseClosedError())
|
|
|
|
else:
|
|
|
|
retFuture.fail(getTransportOsError(err))
|
|
|
|
else:
|
|
|
|
var ntransp: StreamTransport
|
|
|
|
if not isNil(server.init):
|
|
|
|
let transp = server.init(server, server.asock)
|
|
|
|
ntransp = newStreamSocketTransport(server.asock,
|
|
|
|
server.bufferSize,
|
|
|
|
transp)
|
|
|
|
else:
|
|
|
|
ntransp = newStreamSocketTransport(server.asock,
|
|
|
|
server.bufferSize, nil)
|
|
|
|
# Start tracking transport
|
|
|
|
trackStream(ntransp)
|
|
|
|
retFuture.complete(ntransp)
|
|
|
|
elif int32(ovl.data.errCode) == ERROR_OPERATION_ABORTED:
|
|
|
|
# CancelIO() interrupt or close.
|
|
|
|
server.asock.closeSocket()
|
|
|
|
retFuture.fail(getServerUseClosedError())
|
|
|
|
server.clean()
|
|
|
|
else:
|
|
|
|
server.asock.closeSocket()
|
|
|
|
retFuture.fail(getTransportOsError(ovl.data.errCode))
|
2020-06-24 08:21:52 +00:00
|
|
|
|
|
|
|
proc cancellationSocket(udata: pointer) {.gcsafe.} =
|
|
|
|
server.asock.closeSocket()
|
|
|
|
|
|
|
|
proc continuationPipe(udata: pointer) {.gcsafe.} =
|
|
|
|
var ovl = cast[PtrCustomOverlapped](udata)
|
|
|
|
var server = cast[StreamServer](ovl.data.udata)
|
|
|
|
|
|
|
|
server.apending = false
|
2020-07-15 08:09:34 +00:00
|
|
|
if server.status in {ServerStatus.Stopped, ServerStatus.Closed}:
|
2020-06-24 08:21:52 +00:00
|
|
|
retFuture.fail(getServerUseClosedError())
|
|
|
|
server.clean()
|
|
|
|
else:
|
2020-07-15 08:09:34 +00:00
|
|
|
if ovl.data.errCode == OSErrorCode(-1):
|
|
|
|
var ntransp: StreamTransport
|
|
|
|
var flags = {WinServerPipe}
|
|
|
|
if NoPipeFlash in server.flags:
|
|
|
|
flags.incl(WinNoPipeFlash)
|
|
|
|
if not isNil(server.init):
|
|
|
|
var transp = server.init(server, server.sock)
|
|
|
|
ntransp = newStreamPipeTransport(server.sock, server.bufferSize,
|
|
|
|
transp, flags)
|
|
|
|
else:
|
|
|
|
ntransp = newStreamPipeTransport(server.sock, server.bufferSize,
|
|
|
|
nil, flags)
|
|
|
|
# Start tracking transport
|
|
|
|
trackStream(ntransp)
|
|
|
|
server.createAcceptPipe()
|
|
|
|
retFuture.complete(ntransp)
|
|
|
|
|
|
|
|
elif int32(ovl.data.errCode) in {ERROR_OPERATION_ABORTED,
|
|
|
|
ERROR_PIPE_NOT_CONNECTED}:
|
|
|
|
# CancelIO() interrupt or close call.
|
|
|
|
retFuture.fail(getServerUseClosedError())
|
|
|
|
server.clean()
|
|
|
|
else:
|
|
|
|
let sock = server.sock
|
|
|
|
server.createAcceptPipe()
|
|
|
|
closeHandle(sock)
|
|
|
|
retFuture.fail(getTransportOsError(ovl.data.errCode))
|
2020-06-24 08:21:52 +00:00
|
|
|
|
|
|
|
proc cancellationPipe(udata: pointer) {.gcsafe.} =
|
|
|
|
server.sock.closeHandle()
|
|
|
|
|
|
|
|
if server.local.family in {AddressFamily.IPv4, AddressFamily.IPv6}:
|
|
|
|
# TCP Sockets part
|
|
|
|
var loop = getGlobalDispatcher()
|
|
|
|
server.asock = createAsyncSocket(server.domain, SockType.SOCK_STREAM,
|
|
|
|
Protocol.IPPROTO_TCP)
|
|
|
|
if server.asock == asyncInvalidSocket:
|
|
|
|
let err = osLastError()
|
|
|
|
if int32(err) == ERROR_TOO_MANY_OPEN_FILES:
|
|
|
|
retFuture.fail(getTransportTooManyError())
|
|
|
|
else:
|
|
|
|
retFuture.fail(getTransportOsError(err))
|
|
|
|
return retFuture
|
|
|
|
|
|
|
|
var dwBytesReceived = DWORD(0)
|
|
|
|
let dwReceiveDataLength = DWORD(0)
|
|
|
|
let dwLocalAddressLength = DWORD(sizeof(Sockaddr_in6) + 16)
|
|
|
|
let dwRemoteAddressLength = DWORD(sizeof(Sockaddr_in6) + 16)
|
|
|
|
|
|
|
|
server.aovl.data = CompletionData(fd: server.sock,
|
|
|
|
cb: continuationSocket,
|
|
|
|
udata: cast[pointer](server))
|
|
|
|
server.apending = true
|
|
|
|
let res = loop.acceptEx(SocketHandle(server.sock),
|
|
|
|
SocketHandle(server.asock),
|
|
|
|
addr server.abuffer[0],
|
|
|
|
dwReceiveDataLength, dwLocalAddressLength,
|
|
|
|
dwRemoteAddressLength, addr dwBytesReceived,
|
|
|
|
cast[POVERLAPPED](addr server.aovl))
|
|
|
|
if not res:
|
|
|
|
let err = osLastError()
|
|
|
|
if int32(err) == ERROR_OPERATION_ABORTED:
|
|
|
|
server.apending = false
|
|
|
|
retFuture.fail(getServerUseClosedError())
|
|
|
|
return retFuture
|
|
|
|
elif int32(err) == ERROR_IO_PENDING:
|
|
|
|
discard
|
|
|
|
else:
|
|
|
|
server.apending = false
|
|
|
|
retFuture.fail(getTransportOsError(err))
|
|
|
|
return retFuture
|
|
|
|
|
|
|
|
retFuture.cancelCallback = cancellationSocket
|
|
|
|
|
|
|
|
elif server.local.family in {AddressFamily.Unix}:
|
|
|
|
# Unix domain sockets emulation via Windows Named pipes part.
|
|
|
|
server.apending = true
|
|
|
|
if server.sock == asyncInvalidPipe:
|
|
|
|
let err = server.errorCode
|
|
|
|
if int32(err) == ERROR_TOO_MANY_OPEN_FILES:
|
|
|
|
retFuture.fail(getTransportTooManyError())
|
|
|
|
else:
|
|
|
|
retFuture.fail(getTransportOsError(err))
|
|
|
|
return retFuture
|
|
|
|
|
|
|
|
server.aovl.data = CompletionData(fd: server.sock,
|
|
|
|
cb: continuationPipe,
|
|
|
|
udata: cast[pointer](server))
|
|
|
|
server.apending = true
|
|
|
|
let res = connectNamedPipe(HANDLE(server.sock),
|
|
|
|
cast[POVERLAPPED](addr server.aovl))
|
|
|
|
if res == 0:
|
|
|
|
let err = osLastError()
|
|
|
|
if int32(err) == ERROR_OPERATION_ABORTED:
|
|
|
|
server.apending = false
|
|
|
|
retFuture.fail(getServerUseClosedError())
|
|
|
|
return retFuture
|
|
|
|
elif int32(err) in {ERROR_IO_PENDING, ERROR_PIPE_CONNECTED}:
|
|
|
|
discard
|
|
|
|
else:
|
|
|
|
server.apending = false
|
|
|
|
retFuture.fail(getTransportOsError(err))
|
|
|
|
return retFuture
|
|
|
|
|
|
|
|
retFuture.cancelCallback = cancellationPipe
|
|
|
|
|
|
|
|
return retFuture
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
2019-10-29 21:19:41 +00:00
|
|
|
import ../sendfile
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2019-05-28 06:29:00 +00:00
|
|
|
proc isConnResetError(err: OSErrorCode): bool {.inline.} =
|
|
|
|
result = (err == OSErrorCode(ECONNRESET)) or
|
|
|
|
(err == OSErrorCode(EPIPE))
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
proc writeStreamLoop(udata: pointer) {.gcsafe.} =
|
|
|
|
var cdata = cast[ptr CompletionData](udata)
|
2018-06-10 23:08:17 +00:00
|
|
|
var transp = cast[StreamTransport](cdata.udata)
|
2018-05-16 08:22:34 +00:00
|
|
|
let fd = SocketHandle(cdata.fd)
|
2018-08-24 12:20:08 +00:00
|
|
|
|
2020-06-25 06:32:41 +00:00
|
|
|
if int(fd) == 0 or isNil(transp):
|
2018-08-24 12:20:08 +00:00
|
|
|
## This situation can be happen, when there events present
|
|
|
|
## after transport was closed.
|
|
|
|
return
|
|
|
|
|
2018-05-28 23:35:15 +00:00
|
|
|
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:
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
2020-02-12 20:54:05 +00:00
|
|
|
vector.writer.complete(vector.size)
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
2018-05-28 23:35:15 +00:00
|
|
|
vector.shiftVectorBuffer(res)
|
|
|
|
transp.queue.addFirst(vector)
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
2018-05-28 23:35:15 +00:00
|
|
|
let err = osLastError()
|
|
|
|
if int(err) == EINTR:
|
|
|
|
continue
|
2018-05-21 21:52:57 +00:00
|
|
|
else:
|
2019-08-28 04:57:06 +00:00
|
|
|
transp.fd.removeWriter()
|
2019-05-28 06:29:00 +00:00
|
|
|
if isConnResetError(err):
|
|
|
|
# Soft error happens which indicates that remote peer got
|
|
|
|
# disconnected, complete all pending writes in queue with 0.
|
|
|
|
transp.state.incl({WriteEof, WritePaused})
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(0)
|
2019-05-28 06:29:00 +00:00
|
|
|
completePendingWriteQueue(transp.queue, 0)
|
|
|
|
else:
|
2019-08-28 04:57:06 +00:00
|
|
|
transp.state.incl({WriteError, WritePaused})
|
|
|
|
let error = getTransportOsError(err)
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
2019-08-28 04:57:06 +00:00
|
|
|
vector.writer.fail(error)
|
|
|
|
failPendingWriteQueue(transp.queue, error)
|
2018-05-28 23:35:15 +00:00
|
|
|
else:
|
2018-10-25 19:59:40 +00:00
|
|
|
var nbytes = cast[int](vector.buf)
|
2018-05-28 23:35:15 +00:00
|
|
|
let res = sendfile(int(fd), cast[int](vector.buflen),
|
|
|
|
int(vector.offset),
|
2018-10-25 19:59:40 +00:00
|
|
|
nbytes)
|
2018-05-28 23:35:15 +00:00
|
|
|
if res >= 0:
|
2018-10-25 19:59:40 +00:00
|
|
|
if cast[int](vector.buf) - nbytes == 0:
|
|
|
|
vector.size += nbytes
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(vector.size)
|
2018-05-28 23:35:15 +00:00
|
|
|
else:
|
2018-10-25 19:59:40 +00:00
|
|
|
vector.size += nbytes
|
|
|
|
vector.shiftVectorFile(nbytes)
|
2018-05-28 23:35:15 +00:00
|
|
|
transp.queue.addFirst(vector)
|
|
|
|
else:
|
|
|
|
let err = osLastError()
|
|
|
|
if int(err) == EINTR:
|
|
|
|
continue
|
|
|
|
else:
|
2019-08-28 04:57:06 +00:00
|
|
|
transp.fd.removeWriter()
|
2019-05-28 06:29:00 +00:00
|
|
|
if isConnResetError(err):
|
|
|
|
# Soft error happens which indicates that remote peer got
|
|
|
|
# disconnected, complete all pending writes in queue with 0.
|
|
|
|
transp.state.incl({WriteEof, WritePaused})
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(0)
|
2019-05-28 06:29:00 +00:00
|
|
|
completePendingWriteQueue(transp.queue, 0)
|
|
|
|
else:
|
2019-08-28 04:57:06 +00:00
|
|
|
transp.state.incl({WriteError, WritePaused})
|
|
|
|
let error = getTransportOsError(err)
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(vector.writer.finished()):
|
2019-08-28 04:57:06 +00:00
|
|
|
vector.writer.fail(error)
|
|
|
|
failPendingWriteQueue(transp.queue, error)
|
2019-07-15 09:59:42 +00:00
|
|
|
break
|
|
|
|
|
|
|
|
elif transp.kind == TransportKind.Pipe:
|
|
|
|
if vector.kind == VectorKind.DataBuffer:
|
|
|
|
let res = posix.write(cint(fd), vector.buf, vector.buflen)
|
|
|
|
if res >= 0:
|
|
|
|
if vector.buflen - res == 0:
|
|
|
|
if not(vector.writer.finished()):
|
2020-02-12 20:54:05 +00:00
|
|
|
vector.writer.complete(vector.size)
|
2019-07-15 09:59:42 +00:00
|
|
|
else:
|
|
|
|
vector.shiftVectorBuffer(res)
|
|
|
|
transp.queue.addFirst(vector)
|
|
|
|
else:
|
|
|
|
let err = osLastError()
|
|
|
|
if int(err) == EINTR:
|
|
|
|
continue
|
|
|
|
else:
|
2019-08-28 04:57:06 +00:00
|
|
|
transp.fd.removeWriter()
|
2019-07-15 09:59:42 +00:00
|
|
|
if isConnResetError(err):
|
|
|
|
# Soft error happens which indicates that remote peer got
|
|
|
|
# disconnected, complete all pending writes in queue with 0.
|
|
|
|
transp.state.incl({WriteEof, WritePaused})
|
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(0)
|
|
|
|
completePendingWriteQueue(transp.queue, 0)
|
|
|
|
else:
|
2019-08-28 04:57:06 +00:00
|
|
|
transp.state.incl({WriteError, WritePaused})
|
|
|
|
let error = getTransportOsError(err)
|
2019-07-15 09:59:42 +00:00
|
|
|
if not(vector.writer.finished()):
|
2019-08-28 04:57:06 +00:00
|
|
|
vector.writer.fail(error)
|
|
|
|
failPendingWriteQueue(transp.queue, error)
|
2019-07-15 09:59:42 +00:00
|
|
|
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:
|
2019-08-28 04:57:06 +00:00
|
|
|
transp.fd.removeWriter()
|
2019-07-15 09:59:42 +00:00
|
|
|
if isConnResetError(err):
|
|
|
|
# Soft error happens which indicates that remote peer got
|
|
|
|
# disconnected, complete all pending writes in queue with 0.
|
|
|
|
transp.state.incl({WriteEof, WritePaused})
|
|
|
|
if not(vector.writer.finished()):
|
|
|
|
vector.writer.complete(0)
|
|
|
|
completePendingWriteQueue(transp.queue, 0)
|
|
|
|
else:
|
2019-08-28 04:57:06 +00:00
|
|
|
transp.state.incl({WriteError, WritePaused})
|
|
|
|
let error = getTransportOsError(err)
|
2019-07-15 09:59:42 +00:00
|
|
|
if not(vector.writer.finished()):
|
2019-08-28 04:57:06 +00:00
|
|
|
vector.writer.fail(error)
|
|
|
|
failPendingWriteQueue(transp.queue, error)
|
2019-07-15 09:59:42 +00:00
|
|
|
break
|
2018-05-28 23:35:15 +00:00
|
|
|
else:
|
|
|
|
transp.state.incl(WritePaused)
|
|
|
|
transp.fd.removeWriter()
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
proc readStreamLoop(udata: pointer) {.gcsafe.} =
|
|
|
|
var cdata = cast[ptr CompletionData](udata)
|
2018-06-10 23:08:17 +00:00
|
|
|
var transp = cast[StreamTransport](cdata.udata)
|
2018-05-16 08:22:34 +00:00
|
|
|
let fd = SocketHandle(cdata.fd)
|
2020-06-25 06:32:41 +00:00
|
|
|
if int(fd) == 0 or isNil(transp):
|
2018-08-24 12:20:08 +00:00
|
|
|
## This situation can be happen, when there events present
|
|
|
|
## after transport was closed.
|
|
|
|
return
|
|
|
|
|
|
|
|
if ReadClosed in transp.state:
|
|
|
|
transp.state.incl({ReadPaused})
|
2020-03-05 09:59:10 +00:00
|
|
|
transp.completeReader()
|
2018-08-24 12:20:08 +00:00
|
|
|
else:
|
2019-07-15 09:59:42 +00:00
|
|
|
if transp.kind == TransportKind.Socket:
|
|
|
|
while true:
|
|
|
|
var res = posix.recv(fd, addr transp.buffer[transp.offset],
|
|
|
|
len(transp.buffer) - transp.offset, cint(0))
|
|
|
|
if res < 0:
|
|
|
|
let err = osLastError()
|
|
|
|
if int(err) == EINTR:
|
|
|
|
continue
|
|
|
|
elif int(err) in {ECONNRESET}:
|
|
|
|
transp.state.incl({ReadEof, ReadPaused})
|
|
|
|
cdata.fd.removeReader()
|
|
|
|
else:
|
|
|
|
transp.state.incl(ReadPaused)
|
|
|
|
transp.setReadError(err)
|
|
|
|
cdata.fd.removeReader()
|
|
|
|
elif res == 0:
|
2018-08-24 12:20:08 +00:00
|
|
|
transp.state.incl({ReadEof, ReadPaused})
|
|
|
|
cdata.fd.removeReader()
|
|
|
|
else:
|
2019-07-15 09:59:42 +00:00
|
|
|
transp.offset += res
|
|
|
|
if transp.offset == len(transp.buffer):
|
|
|
|
transp.state.incl(ReadPaused)
|
|
|
|
cdata.fd.removeReader()
|
2020-03-05 09:59:10 +00:00
|
|
|
transp.completeReader()
|
2019-07-15 09:59:42 +00:00
|
|
|
break
|
|
|
|
elif transp.kind == TransportKind.Pipe:
|
|
|
|
while true:
|
|
|
|
var res = posix.read(cint(fd), addr transp.buffer[transp.offset],
|
|
|
|
len(transp.buffer) - transp.offset)
|
|
|
|
if res < 0:
|
|
|
|
let err = osLastError()
|
|
|
|
if int(err) == EINTR:
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
transp.state.incl(ReadPaused)
|
|
|
|
transp.setReadError(err)
|
|
|
|
cdata.fd.removeReader()
|
|
|
|
elif res == 0:
|
|
|
|
transp.state.incl({ReadEof, ReadPaused})
|
2018-08-24 12:20:08 +00:00
|
|
|
cdata.fd.removeReader()
|
2019-07-15 09:59:42 +00:00
|
|
|
else:
|
|
|
|
transp.offset += res
|
|
|
|
if transp.offset == len(transp.buffer):
|
|
|
|
transp.state.incl(ReadPaused)
|
|
|
|
cdata.fd.removeReader()
|
2020-03-05 09:59:10 +00:00
|
|
|
transp.completeReader()
|
2019-07-15 09:59:42 +00:00
|
|
|
break
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-06-10 23:08:17 +00:00
|
|
|
proc newStreamSocketTransport(sock: AsyncFD, bufsize: int,
|
|
|
|
child: StreamTransport): StreamTransport =
|
|
|
|
var transp: StreamTransport
|
|
|
|
if not isNil(child):
|
|
|
|
transp = child
|
|
|
|
else:
|
|
|
|
transp = StreamTransport(kind: TransportKind.Socket)
|
|
|
|
|
2018-05-21 21:52:57 +00:00
|
|
|
transp.fd = sock
|
|
|
|
transp.buffer = newSeq[byte](bufsize)
|
|
|
|
transp.state = {ReadPaused, WritePaused}
|
|
|
|
transp.queue = initDeque[StreamVector]()
|
|
|
|
transp.future = newFuture[void]("socket.stream.transport")
|
|
|
|
GC_ref(transp)
|
2018-06-10 23:08:17 +00:00
|
|
|
result = transp
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2019-07-15 09:59:42 +00:00
|
|
|
proc newStreamPipeTransport(fd: AsyncFD, bufsize: int,
|
|
|
|
child: StreamTransport): StreamTransport =
|
|
|
|
var transp: StreamTransport
|
|
|
|
if not isNil(child):
|
|
|
|
transp = child
|
|
|
|
else:
|
|
|
|
transp = StreamTransport(kind: TransportKind.Pipe)
|
|
|
|
|
|
|
|
transp.fd = fd
|
|
|
|
transp.buffer = newSeq[byte](bufsize)
|
|
|
|
transp.state = {ReadPaused, WritePaused}
|
|
|
|
transp.queue = initDeque[StreamVector]()
|
|
|
|
transp.future = newFuture[void]("pipe.stream.transport")
|
|
|
|
GC_ref(transp)
|
|
|
|
result = transp
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
proc connect*(address: TransportAddress,
|
2018-06-10 23:08:17 +00:00
|
|
|
bufferSize = DefaultStreamBufferSize,
|
|
|
|
child: StreamTransport = nil): Future[StreamTransport] =
|
2018-05-27 05:49:47 +00:00
|
|
|
## Open new connection to remote peer with address ``address`` and create
|
|
|
|
## new transport object ``StreamTransport`` for established connection.
|
|
|
|
## ``bufferSize`` - size of internal buffer for transport.
|
2018-05-16 08:22:34 +00:00
|
|
|
var
|
|
|
|
saddr: Sockaddr_storage
|
|
|
|
slen: SockLen
|
|
|
|
sock: AsyncFD
|
2018-10-25 10:19:19 +00:00
|
|
|
proto: Protocol
|
2019-03-30 22:31:10 +00:00
|
|
|
var retFuture = newFuture[StreamTransport]("stream.transport.connect")
|
2018-10-25 10:19:19 +00:00
|
|
|
address.toSAddr(saddr, slen)
|
|
|
|
proto = Protocol.IPPROTO_TCP
|
|
|
|
if address.family == AddressFamily.Unix:
|
|
|
|
# `Protocol` enum is missing `0` value, so we making here cast, until
|
|
|
|
# `Protocol` enum will not support IPPROTO_IP == 0.
|
|
|
|
proto = cast[Protocol](0)
|
|
|
|
sock = createAsyncSocket(address.getDomain(), SockType.SOCK_STREAM,
|
|
|
|
proto)
|
2018-05-16 08:22:34 +00:00
|
|
|
if sock == asyncInvalidSocket:
|
2020-06-24 08:21:52 +00:00
|
|
|
let err = osLastError()
|
|
|
|
if int(err) == EMFILE:
|
|
|
|
retFuture.fail(getTransportTooManyError())
|
|
|
|
else:
|
|
|
|
retFuture.fail(getTransportOsError(err))
|
2018-06-07 19:07:17 +00:00
|
|
|
return retFuture
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2019-06-20 20:30:41 +00:00
|
|
|
proc continuation(udata: pointer) {.gcsafe.} =
|
|
|
|
if not(retFuture.finished()):
|
2019-06-06 18:22:17 +00:00
|
|
|
var data = cast[ptr CompletionData](udata)
|
|
|
|
var err = 0
|
|
|
|
let fd = data.fd
|
|
|
|
fd.removeWriter()
|
|
|
|
if not fd.getSocketError(err):
|
|
|
|
closeSocket(fd)
|
|
|
|
retFuture.fail(getTransportOsError(osLastError()))
|
|
|
|
return
|
|
|
|
if err != 0:
|
|
|
|
closeSocket(fd)
|
|
|
|
retFuture.fail(getTransportOsError(OSErrorCode(err)))
|
|
|
|
return
|
|
|
|
let transp = newStreamSocketTransport(fd, bufferSize, child)
|
|
|
|
# Start tracking transport
|
|
|
|
trackStream(transp)
|
|
|
|
retFuture.complete(transp)
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2019-06-20 20:30:41 +00:00
|
|
|
proc cancel(udata: pointer) {.gcsafe.} =
|
2020-06-05 16:11:51 +00:00
|
|
|
closeSocket(sock)
|
2019-06-20 20:30:41 +00:00
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
while true:
|
|
|
|
var res = posix.connect(SocketHandle(sock),
|
|
|
|
cast[ptr SockAddr](addr saddr), slen)
|
|
|
|
if res == 0:
|
2019-04-04 09:34:23 +00:00
|
|
|
let transp = newStreamSocketTransport(sock, bufferSize, child)
|
|
|
|
# Start tracking transport
|
|
|
|
trackStream(transp)
|
|
|
|
retFuture.complete(transp)
|
2018-05-16 08:22:34 +00:00
|
|
|
break
|
|
|
|
else:
|
|
|
|
let err = osLastError()
|
2019-06-20 20:30:41 +00:00
|
|
|
# If connect() is interrupted by a signal that is caught while blocked
|
|
|
|
# waiting to establish a connection, connect() shall fail and set
|
|
|
|
# connect() to [EINTR], but the connection request shall not be aborted,
|
|
|
|
# and the connection shall be established asynchronously.
|
|
|
|
#
|
|
|
|
# http://www.madore.org/~david/computers/connect-intr.html
|
|
|
|
if int(err) == EINPROGRESS or int(err) == EINTR:
|
2018-05-16 08:22:34 +00:00
|
|
|
sock.addWriter(continuation)
|
2019-06-20 20:30:41 +00:00
|
|
|
retFuture.cancelCallback = cancel
|
2018-05-16 08:22:34 +00:00
|
|
|
break
|
|
|
|
else:
|
2018-08-24 12:20:08 +00:00
|
|
|
sock.closeSocket()
|
2018-10-03 00:44:39 +00:00
|
|
|
retFuture.fail(getTransportOsError(err))
|
2018-05-16 08:22:34 +00:00
|
|
|
break
|
|
|
|
return retFuture
|
|
|
|
|
2018-06-10 23:08:17 +00:00
|
|
|
proc acceptLoop(udata: pointer) =
|
2018-05-16 08:22:34 +00:00
|
|
|
var
|
|
|
|
saddr: Sockaddr_storage
|
|
|
|
slen: SockLen
|
|
|
|
var server = cast[StreamServer](cast[ptr CompletionData](udata).udata)
|
|
|
|
while true:
|
2020-07-15 08:09:34 +00:00
|
|
|
if server.status in {ServerStatus.Stopped, ServerStatus.Closed}:
|
|
|
|
break
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
let res = posix.accept(SocketHandle(server.sock),
|
|
|
|
cast[ptr SockAddr](addr saddr), addr slen)
|
|
|
|
if int(res) > 0:
|
|
|
|
let sock = wrapAsyncSocket(res)
|
|
|
|
if sock != asyncInvalidSocket:
|
2019-04-04 09:34:23 +00:00
|
|
|
var ntransp: StreamTransport
|
2018-06-10 23:08:17 +00:00
|
|
|
if not isNil(server.init):
|
2019-04-04 09:34:23 +00:00
|
|
|
let transp = server.init(server, sock)
|
|
|
|
ntransp = newStreamSocketTransport(sock, server.bufferSize, transp)
|
2018-06-10 23:08:17 +00:00
|
|
|
else:
|
2019-04-04 09:34:23 +00:00
|
|
|
ntransp = newStreamSocketTransport(sock, server.bufferSize, nil)
|
|
|
|
# Start tracking transport
|
|
|
|
trackStream(ntransp)
|
|
|
|
asyncCheck server.function(server, ntransp)
|
2018-10-25 19:59:40 +00:00
|
|
|
break
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
|
|
|
let err = osLastError()
|
|
|
|
if int(err) == EINTR:
|
|
|
|
continue
|
2020-07-14 22:07:03 +00:00
|
|
|
elif int(err) == EAGAIN:
|
|
|
|
# This error appears only when server get closed, while acceptLoop()
|
|
|
|
# reader callback is already scheduled.
|
|
|
|
break
|
2018-06-05 05:51:59 +00:00
|
|
|
else:
|
2018-05-16 08:22:34 +00:00
|
|
|
## Critical unrecoverable error
|
2018-06-10 23:08:17 +00:00
|
|
|
raiseTransportOsError(err)
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-06-07 08:05:06 +00:00
|
|
|
proc resumeAccept(server: StreamServer) =
|
2018-06-10 23:08:17 +00:00
|
|
|
addReader(server.sock, acceptLoop, cast[pointer](server))
|
2018-06-04 09:57:17 +00:00
|
|
|
|
2018-06-07 08:05:06 +00:00
|
|
|
proc pauseAccept(server: StreamServer) =
|
2018-06-04 09:57:17 +00:00
|
|
|
removeReader(server.sock)
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
proc resumeRead(transp: StreamTransport) {.inline.} =
|
2020-03-05 09:59:10 +00:00
|
|
|
if ReadPaused in transp.state:
|
|
|
|
transp.state.excl(ReadPaused)
|
|
|
|
addReader(transp.fd, readStreamLoop, cast[pointer](transp))
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
proc resumeWrite(transp: StreamTransport) {.inline.} =
|
2020-03-05 09:59:10 +00:00
|
|
|
if WritePaused in transp.state:
|
|
|
|
transp.state.excl(WritePaused)
|
|
|
|
addWriter(transp.fd, writeStreamLoop, cast[pointer](transp))
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2020-06-24 08:21:52 +00:00
|
|
|
proc accept*(server: StreamServer): Future[StreamTransport] =
|
|
|
|
var retFuture = newFuture[StreamTransport]("stream.server.accept")
|
|
|
|
|
|
|
|
doAssert(server.status != ServerStatus.Running,
|
|
|
|
"You could not use accept() if server was started with start()")
|
|
|
|
if server.status == ServerStatus.Closed:
|
|
|
|
retFuture.fail(getServerUseClosedError())
|
|
|
|
return retFuture
|
|
|
|
|
|
|
|
proc continuation(udata: pointer) {.gcsafe.} =
|
|
|
|
var
|
|
|
|
saddr: Sockaddr_storage
|
|
|
|
slen: SockLen
|
2020-07-15 08:09:34 +00:00
|
|
|
|
|
|
|
if server.status in {ServerStatus.Stopped, ServerStatus.Closed}:
|
|
|
|
retFuture.fail(getServerUseClosedError())
|
|
|
|
else:
|
|
|
|
while true:
|
|
|
|
let res = posix.accept(SocketHandle(server.sock),
|
|
|
|
cast[ptr SockAddr](addr saddr), addr slen)
|
|
|
|
if int(res) > 0:
|
|
|
|
let sock = wrapAsyncSocket(res)
|
|
|
|
if sock != asyncInvalidSocket:
|
|
|
|
var ntransp: StreamTransport
|
|
|
|
if not isNil(server.init):
|
|
|
|
let transp = server.init(server, sock)
|
|
|
|
ntransp = newStreamSocketTransport(sock, server.bufferSize,
|
|
|
|
transp)
|
|
|
|
else:
|
|
|
|
ntransp = newStreamSocketTransport(sock, server.bufferSize, nil)
|
|
|
|
# Start tracking transport
|
|
|
|
trackStream(ntransp)
|
|
|
|
retFuture.complete(ntransp)
|
2020-06-24 08:21:52 +00:00
|
|
|
else:
|
2020-07-15 08:09:34 +00:00
|
|
|
retFuture.fail(getTransportOsError(osLastError()))
|
2020-06-24 08:21:52 +00:00
|
|
|
else:
|
2020-07-15 08:09:34 +00:00
|
|
|
let err = osLastError()
|
|
|
|
if int(err) == EINTR:
|
|
|
|
continue
|
|
|
|
elif int(err) == EAGAIN:
|
|
|
|
# This error appears only when server get closed, while accept()
|
|
|
|
# continuation is already scheduled.
|
|
|
|
retFuture.fail(getServerUseClosedError())
|
|
|
|
elif int(err) == EMFILE:
|
|
|
|
retFuture.fail(getTransportTooManyError())
|
|
|
|
else:
|
|
|
|
retFuture.fail(getTransportOsError(err))
|
|
|
|
break
|
2020-06-24 08:21:52 +00:00
|
|
|
removeReader(server.sock)
|
|
|
|
|
|
|
|
proc cancellation(udata: pointer) {.gcsafe.} =
|
|
|
|
removeReader(server.sock)
|
|
|
|
|
|
|
|
addReader(server.sock, continuation, nil)
|
|
|
|
retFuture.cancelCallback = cancellation
|
|
|
|
return retFuture
|
|
|
|
|
2018-06-07 08:05:06 +00:00
|
|
|
proc start*(server: StreamServer) =
|
2018-05-21 21:52:57 +00:00
|
|
|
## Starts ``server``.
|
2020-06-24 08:21:52 +00:00
|
|
|
doAssert(not(isNil(server.function)))
|
2018-06-04 09:57:17 +00:00
|
|
|
if server.status == ServerStatus.Starting:
|
|
|
|
server.resumeAccept()
|
|
|
|
server.status = ServerStatus.Running
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-06-07 08:05:06 +00:00
|
|
|
proc stop*(server: StreamServer) =
|
2018-05-22 21:03:13 +00:00
|
|
|
## Stops ``server``.
|
2018-06-04 09:57:17 +00:00
|
|
|
if server.status == ServerStatus.Running:
|
|
|
|
server.pauseAccept()
|
|
|
|
server.status = ServerStatus.Stopped
|
2018-06-07 08:05:06 +00:00
|
|
|
elif server.status == ServerStatus.Starting:
|
|
|
|
server.status = ServerStatus.Stopped
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-08-24 12:20:08 +00:00
|
|
|
proc join*(server: StreamServer): Future[void] =
|
2018-06-04 09:57:17 +00:00
|
|
|
## Waits until ``server`` is not closed.
|
2019-03-30 22:31:10 +00:00
|
|
|
var retFuture = newFuture[void]("stream.transport.server.join")
|
2019-06-20 20:30:41 +00:00
|
|
|
|
|
|
|
proc continuation(udata: pointer) {.gcsafe.} =
|
|
|
|
retFuture.complete()
|
|
|
|
|
|
|
|
proc cancel(udata: pointer) {.gcsafe.} =
|
|
|
|
server.loopFuture.removeCallback(continuation, cast[pointer](retFuture))
|
|
|
|
|
|
|
|
if not(server.loopFuture.finished()):
|
|
|
|
server.loopFuture.addCallback(continuation, cast[pointer](retFuture))
|
|
|
|
retFuture.cancelCallback = cancel
|
2018-08-24 12:20:08 +00:00
|
|
|
else:
|
|
|
|
retFuture.complete()
|
|
|
|
return retFuture
|
2018-05-21 21:52:57 +00:00
|
|
|
|
2018-06-07 08:05:06 +00:00
|
|
|
proc close*(server: StreamServer) =
|
2018-05-21 21:52:57 +00:00
|
|
|
## Release ``server`` resources.
|
2018-08-24 12:20:08 +00:00
|
|
|
##
|
|
|
|
## Please note that release of resources is not completed immediately, to be
|
|
|
|
## sure all resources got released please use ``await server.join()``.
|
2019-06-20 20:30:41 +00:00
|
|
|
proc continuation(udata: pointer) {.gcsafe.} =
|
2019-05-09 17:59:15 +00:00
|
|
|
# Stop tracking server
|
2019-06-20 20:30:41 +00:00
|
|
|
if not(server.loopFuture.finished()):
|
2020-06-24 08:21:52 +00:00
|
|
|
server.clean()
|
|
|
|
|
|
|
|
let r1 = (server.status == ServerStatus.Stopped) and
|
|
|
|
not(isNil(server.function))
|
|
|
|
let r2 = (server.status == ServerStatus.Starting) and isNil(server.function)
|
2019-06-20 20:30:41 +00:00
|
|
|
|
2020-06-24 08:21:52 +00:00
|
|
|
if r1 or r2:
|
2018-08-24 12:20:08 +00:00
|
|
|
server.status = ServerStatus.Closed
|
2018-10-25 10:19:19 +00:00
|
|
|
when defined(windows):
|
|
|
|
if server.local.family in {AddressFamily.IPv4, AddressFamily.IPv6}:
|
2019-05-09 17:59:15 +00:00
|
|
|
if not server.apending:
|
|
|
|
server.sock.closeSocket(continuation)
|
|
|
|
else:
|
2020-06-24 08:21:52 +00:00
|
|
|
server.asock.closeSocket()
|
2019-05-09 17:59:15 +00:00
|
|
|
server.sock.closeSocket()
|
2018-10-25 10:19:19 +00:00
|
|
|
elif server.local.family in {AddressFamily.Unix}:
|
|
|
|
if NoPipeFlash notin server.flags:
|
|
|
|
discard flushFileBuffers(Handle(server.sock))
|
2020-06-24 08:21:52 +00:00
|
|
|
discard disconnectNamedPipe(Handle(server.sock))
|
2019-05-09 17:59:15 +00:00
|
|
|
if not server.apending:
|
|
|
|
server.sock.closeHandle(continuation)
|
|
|
|
else:
|
|
|
|
server.sock.closeHandle()
|
2018-10-25 10:19:19 +00:00
|
|
|
else:
|
|
|
|
server.sock.closeSocket(continuation)
|
2018-08-24 12:20:08 +00:00
|
|
|
|
|
|
|
proc closeWait*(server: StreamServer): Future[void] =
|
|
|
|
## Close server ``server`` and release all resources.
|
2018-09-13 18:15:57 +00:00
|
|
|
server.close()
|
2018-08-24 12:20:08 +00:00
|
|
|
result = server.join()
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
proc createStreamServer*(host: TransportAddress,
|
|
|
|
cbproc: StreamCallback,
|
2018-05-31 08:03:58 +00:00
|
|
|
flags: set[ServerFlags] = {},
|
2018-05-16 08:22:34 +00:00
|
|
|
sock: AsyncFD = asyncInvalidSocket,
|
|
|
|
backlog: int = 100,
|
|
|
|
bufferSize: int = DefaultStreamBufferSize,
|
2018-06-10 23:08:17 +00:00
|
|
|
child: StreamServer = nil,
|
|
|
|
init: TransportInitCallback = nil,
|
2018-05-16 08:22:34 +00:00
|
|
|
udata: pointer = nil): StreamServer =
|
2018-05-28 23:35:15 +00:00
|
|
|
## Create new TCP stream server.
|
2018-06-04 09:57:17 +00:00
|
|
|
##
|
2018-05-27 05:49:47 +00:00
|
|
|
## ``host`` - address to which server will be bound.
|
|
|
|
## ``flags`` - flags to apply to server socket.
|
|
|
|
## ``cbproc`` - callback function which will be called, when new client
|
|
|
|
## connection will be established.
|
2018-05-28 23:35:15 +00:00
|
|
|
## ``sock`` - user-driven socket to use.
|
|
|
|
## ``backlog`` - number of outstanding connections in the socket's listen
|
|
|
|
## queue.
|
|
|
|
## ``bufferSize`` - size of internal buffer for transport.
|
2018-06-10 23:08:17 +00:00
|
|
|
## ``child`` - existing object ``StreamServer``object to initialize, can be
|
|
|
|
## used to initalize ``StreamServer`` inherited objects.
|
2018-05-28 23:35:15 +00:00
|
|
|
## ``udata`` - user-defined pointer.
|
2018-05-16 08:22:34 +00:00
|
|
|
var
|
|
|
|
saddr: Sockaddr_storage
|
|
|
|
slen: SockLen
|
|
|
|
serverSocket: AsyncFD
|
2019-10-09 12:12:19 +00:00
|
|
|
localAddress: TransportAddress
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-10-25 10:19:19 +00:00
|
|
|
when defined(windows):
|
|
|
|
# Windows
|
|
|
|
if host.family in {AddressFamily.IPv4, AddressFamily.IPv6}:
|
|
|
|
if sock == asyncInvalidSocket:
|
|
|
|
serverSocket = createAsyncSocket(host.getDomain(),
|
|
|
|
SockType.SOCK_STREAM,
|
|
|
|
Protocol.IPPROTO_TCP)
|
|
|
|
if serverSocket == asyncInvalidSocket:
|
|
|
|
raiseTransportOsError(osLastError())
|
|
|
|
else:
|
|
|
|
if not setSocketBlocking(SocketHandle(sock), false):
|
|
|
|
raiseTransportOsError(osLastError())
|
|
|
|
register(sock)
|
|
|
|
serverSocket = sock
|
|
|
|
# SO_REUSEADDR is not useful for Unix domain sockets.
|
|
|
|
if ServerFlags.ReuseAddr in flags:
|
|
|
|
if not setSockOpt(serverSocket, SOL_SOCKET, SO_REUSEADDR, 1):
|
|
|
|
let err = osLastError()
|
|
|
|
if sock == asyncInvalidSocket:
|
|
|
|
serverSocket.closeSocket()
|
|
|
|
raiseTransportOsError(err)
|
|
|
|
# TCP flags are not useful for Unix domain sockets.
|
|
|
|
if ServerFlags.TcpNoDelay in flags:
|
|
|
|
if not setSockOpt(serverSocket, handles.IPPROTO_TCP,
|
|
|
|
handles.TCP_NODELAY, 1):
|
|
|
|
let err = osLastError()
|
|
|
|
if sock == asyncInvalidSocket:
|
|
|
|
serverSocket.closeSocket()
|
|
|
|
raiseTransportOsError(err)
|
|
|
|
host.toSAddr(saddr, slen)
|
|
|
|
if bindAddr(SocketHandle(serverSocket), cast[ptr SockAddr](addr saddr),
|
|
|
|
slen) != 0:
|
|
|
|
let err = osLastError()
|
|
|
|
if sock == asyncInvalidSocket:
|
|
|
|
serverSocket.closeSocket()
|
|
|
|
raiseTransportOsError(err)
|
|
|
|
|
2019-10-09 12:12:19 +00:00
|
|
|
slen = SockLen(sizeof(saddr))
|
|
|
|
if getsockname(SocketHandle(serverSocket), cast[ptr SockAddr](addr saddr),
|
|
|
|
addr slen) != 0:
|
|
|
|
let err = osLastError()
|
|
|
|
if sock == asyncInvalidSocket:
|
|
|
|
serverSocket.closeSocket()
|
|
|
|
raiseTransportOsError(err)
|
|
|
|
fromSAddr(addr saddr, slen, localAddress)
|
|
|
|
|
2018-10-25 10:19:19 +00:00
|
|
|
if nativesockets.listen(SocketHandle(serverSocket), cint(backlog)) != 0:
|
|
|
|
let err = osLastError()
|
|
|
|
if sock == asyncInvalidSocket:
|
|
|
|
serverSocket.closeSocket()
|
|
|
|
raiseTransportOsError(err)
|
|
|
|
elif host.family == AddressFamily.Unix:
|
|
|
|
serverSocket = AsyncFD(0)
|
|
|
|
else:
|
|
|
|
# Posix
|
|
|
|
if sock == asyncInvalidSocket:
|
|
|
|
var proto = Protocol.IPPROTO_TCP
|
|
|
|
if host.family == AddressFamily.Unix:
|
|
|
|
# `Protocol` enum is missing `0` value, so we making here cast, until
|
|
|
|
# `Protocol` enum will not support IPPROTO_IP == 0.
|
|
|
|
proto = cast[Protocol](0)
|
|
|
|
serverSocket = createAsyncSocket(host.getDomain(),
|
|
|
|
SockType.SOCK_STREAM,
|
|
|
|
proto)
|
|
|
|
if serverSocket == asyncInvalidSocket:
|
|
|
|
raiseTransportOsError(osLastError())
|
|
|
|
else:
|
|
|
|
if not setSocketBlocking(SocketHandle(sock), false):
|
|
|
|
raiseTransportOsError(osLastError())
|
|
|
|
register(sock)
|
|
|
|
serverSocket = sock
|
|
|
|
|
|
|
|
if host.family in {AddressFamily.IPv4, AddressFamily.IPv6}:
|
2020-07-12 16:13:22 +00:00
|
|
|
# SO_REUSEADDR and SO_REUSEPORT are not useful for Unix domain sockets.
|
2018-10-25 10:19:19 +00:00
|
|
|
if ServerFlags.ReuseAddr in flags:
|
|
|
|
if not setSockOpt(serverSocket, SOL_SOCKET, SO_REUSEADDR, 1):
|
|
|
|
let err = osLastError()
|
|
|
|
if sock == asyncInvalidSocket:
|
2020-07-12 16:13:22 +00:00
|
|
|
serverSocket.closeSocket()
|
|
|
|
raiseTransportOsError(err)
|
|
|
|
if ServerFlags.ReusePort in flags:
|
|
|
|
if not setSockOpt(serverSocket, SOL_SOCKET, SO_REUSEPORT, 1):
|
|
|
|
let err = osLastError()
|
|
|
|
if sock == asyncInvalidSocket:
|
2018-10-25 10:19:19 +00:00
|
|
|
serverSocket.closeSocket()
|
|
|
|
raiseTransportOsError(err)
|
|
|
|
# TCP flags are not useful for Unix domain sockets.
|
|
|
|
if ServerFlags.TcpNoDelay in flags:
|
|
|
|
if not setSockOpt(serverSocket, handles.IPPROTO_TCP,
|
|
|
|
handles.TCP_NODELAY, 1):
|
|
|
|
let err = osLastError()
|
|
|
|
if sock == asyncInvalidSocket:
|
|
|
|
serverSocket.closeSocket()
|
|
|
|
raiseTransportOsError(err)
|
|
|
|
elif host.family in {AddressFamily.Unix}:
|
|
|
|
# We do not care about result here, because if file cannot be removed,
|
|
|
|
# `bindAddr` will return EADDRINUSE.
|
|
|
|
discard posix.unlink(cast[cstring](unsafeAddr host.address_un[0]))
|
|
|
|
|
|
|
|
host.toSAddr(saddr, slen)
|
|
|
|
if bindAddr(SocketHandle(serverSocket), cast[ptr SockAddr](addr saddr),
|
|
|
|
slen) != 0:
|
2018-05-16 08:22:34 +00:00
|
|
|
let err = osLastError()
|
|
|
|
if sock == asyncInvalidSocket:
|
2018-08-24 12:20:08 +00:00
|
|
|
serverSocket.closeSocket()
|
2018-06-10 23:08:17 +00:00
|
|
|
raiseTransportOsError(err)
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2019-10-09 12:12:19 +00:00
|
|
|
# Obtain real address
|
|
|
|
slen = SockLen(sizeof(saddr))
|
|
|
|
if getsockname(SocketHandle(serverSocket), cast[ptr SockAddr](addr saddr),
|
|
|
|
addr slen) != 0:
|
|
|
|
let err = osLastError()
|
|
|
|
if sock == asyncInvalidSocket:
|
|
|
|
serverSocket.closeSocket()
|
|
|
|
raiseTransportOsError(err)
|
|
|
|
fromSAddr(addr saddr, slen, localAddress)
|
|
|
|
|
2018-10-25 10:19:19 +00:00
|
|
|
if nativesockets.listen(SocketHandle(serverSocket), cint(backlog)) != 0:
|
2018-08-27 18:41:11 +00:00
|
|
|
let err = osLastError()
|
|
|
|
if sock == asyncInvalidSocket:
|
|
|
|
serverSocket.closeSocket()
|
|
|
|
raiseTransportOsError(err)
|
|
|
|
|
2018-06-10 23:08:17 +00:00
|
|
|
if not isNil(child):
|
|
|
|
result = child
|
|
|
|
else:
|
|
|
|
result = StreamServer()
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
result.sock = serverSocket
|
2018-10-25 10:19:19 +00:00
|
|
|
result.flags = flags
|
2018-05-16 08:22:34 +00:00
|
|
|
result.function = cbproc
|
2018-06-10 23:08:17 +00:00
|
|
|
result.init = init
|
2018-05-16 08:22:34 +00:00
|
|
|
result.bufferSize = bufferSize
|
|
|
|
result.status = Starting
|
2019-03-30 22:31:10 +00:00
|
|
|
result.loopFuture = newFuture[void]("stream.transport.server")
|
2018-05-16 08:22:34 +00:00
|
|
|
result.udata = udata
|
2019-10-09 12:12:19 +00:00
|
|
|
if localAddress.family == AddressFamily.None:
|
|
|
|
result.local = host
|
|
|
|
else:
|
|
|
|
result.local = localAddress
|
2018-06-04 09:57:17 +00:00
|
|
|
|
|
|
|
when defined(windows):
|
2018-10-25 10:19:19 +00:00
|
|
|
var cb: CallbackFunc
|
|
|
|
if host.family in {AddressFamily.IPv4, AddressFamily.IPv6}:
|
|
|
|
cb = acceptLoop
|
|
|
|
elif host.family == AddressFamily.Unix:
|
|
|
|
cb = acceptPipeLoop
|
|
|
|
|
2020-06-24 08:21:52 +00:00
|
|
|
if not(isNil(cbproc)):
|
|
|
|
result.aovl.data = CompletionData(fd: serverSocket, cb: cb,
|
|
|
|
udata: cast[pointer](result))
|
|
|
|
else:
|
|
|
|
if host.family == AddressFamily.Unix:
|
|
|
|
result.createAcceptPipe()
|
|
|
|
|
2018-10-25 10:19:19 +00:00
|
|
|
result.domain = host.getDomain()
|
2018-06-04 09:57:17 +00:00
|
|
|
result.apending = false
|
2018-10-25 10:19:19 +00:00
|
|
|
|
2019-04-04 09:34:23 +00:00
|
|
|
# Start tracking server
|
|
|
|
trackServer(result)
|
2018-05-21 21:52:57 +00:00
|
|
|
GC_ref(result)
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2020-06-24 08:21:52 +00:00
|
|
|
proc createStreamServer*(host: TransportAddress,
|
|
|
|
flags: set[ServerFlags] = {},
|
|
|
|
sock: AsyncFD = asyncInvalidSocket,
|
|
|
|
backlog: int = 100,
|
|
|
|
bufferSize: int = DefaultStreamBufferSize,
|
|
|
|
child: StreamServer = nil,
|
|
|
|
init: TransportInitCallback = nil,
|
|
|
|
udata: pointer = nil): StreamServer =
|
|
|
|
result = createStreamServer(host, nil, flags, sock, backlog, bufferSize,
|
|
|
|
child, init, cast[pointer](udata))
|
|
|
|
|
2018-06-06 21:29:37 +00:00
|
|
|
proc createStreamServer*[T](host: TransportAddress,
|
|
|
|
cbproc: StreamCallback,
|
|
|
|
flags: set[ServerFlags] = {},
|
2018-06-10 23:08:17 +00:00
|
|
|
udata: ref T,
|
2018-06-06 21:29:37 +00:00
|
|
|
sock: AsyncFD = asyncInvalidSocket,
|
|
|
|
backlog: int = 100,
|
|
|
|
bufferSize: int = DefaultStreamBufferSize,
|
2018-06-11 19:54:08 +00:00
|
|
|
child: StreamServer = nil,
|
|
|
|
init: TransportInitCallback = nil): StreamServer =
|
2018-06-06 21:29:37 +00:00
|
|
|
var fflags = flags + {GCUserData}
|
|
|
|
GC_ref(udata)
|
2019-10-29 21:19:41 +00:00
|
|
|
result = createStreamServer(host, cbproc, fflags, sock, backlog, bufferSize,
|
2018-06-11 19:54:08 +00:00
|
|
|
child, init, cast[pointer](udata))
|
2018-06-06 21:29:37 +00:00
|
|
|
|
2020-06-24 08:21:52 +00:00
|
|
|
proc createStreamServer*[T](host: TransportAddress,
|
|
|
|
flags: set[ServerFlags] = {},
|
|
|
|
udata: ref T,
|
|
|
|
sock: AsyncFD = asyncInvalidSocket,
|
|
|
|
backlog: int = 100,
|
|
|
|
bufferSize: int = DefaultStreamBufferSize,
|
|
|
|
child: StreamServer = nil,
|
|
|
|
init: TransportInitCallback = nil): StreamServer =
|
|
|
|
var fflags = flags + {GCUserData}
|
|
|
|
GC_ref(udata)
|
|
|
|
result = createStreamServer(host, nil, fflags, sock, backlog, bufferSize,
|
|
|
|
child, init, cast[pointer](udata))
|
|
|
|
|
2018-06-07 06:17:59 +00:00
|
|
|
proc getUserData*[T](server: StreamServer): T {.inline.} =
|
|
|
|
## Obtain user data stored in ``server`` object.
|
|
|
|
result = cast[T](server.udata)
|
2018-06-06 21:29:37 +00:00
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
proc write*(transp: StreamTransport, pbytes: pointer,
|
2018-06-05 20:21:07 +00:00
|
|
|
nbytes: int): Future[int] =
|
2018-05-27 05:49:47 +00:00
|
|
|
## Write data from buffer ``pbytes`` with size ``nbytes`` using transport
|
2018-05-22 21:03:13 +00:00
|
|
|
## ``transp``.
|
2019-03-30 22:31:10 +00:00
|
|
|
var retFuture = newFuture[int]("stream.transport.write(pointer)")
|
2018-06-05 20:21:07 +00:00
|
|
|
transp.checkClosed(retFuture)
|
2019-10-23 11:13:23 +00:00
|
|
|
transp.checkWriteEof(retFuture)
|
2018-06-05 20:21:07 +00:00
|
|
|
var vector = StreamVector(kind: DataBuffer, writer: retFuture,
|
2020-02-12 20:54:05 +00:00
|
|
|
buf: pbytes, buflen: nbytes, size: nbytes)
|
2018-05-16 08:22:34 +00:00
|
|
|
transp.queue.addLast(vector)
|
2020-03-05 09:59:10 +00:00
|
|
|
transp.resumeWrite()
|
2018-06-05 20:21:07 +00:00
|
|
|
return retFuture
|
|
|
|
|
2018-06-15 12:09:44 +00:00
|
|
|
proc write*(transp: StreamTransport, msg: string, msglen = -1): Future[int] =
|
2018-06-05 20:21:07 +00:00
|
|
|
## Write data from string ``msg`` using transport ``transp``.
|
2019-03-30 22:31:10 +00:00
|
|
|
var retFuture = newFutureStr[int]("stream.transport.write(string)")
|
2018-06-05 20:21:07 +00:00
|
|
|
transp.checkClosed(retFuture)
|
2019-10-23 11:13:23 +00:00
|
|
|
transp.checkWriteEof(retFuture)
|
2018-06-15 12:09:44 +00:00
|
|
|
if not isLiteral(msg):
|
|
|
|
shallowCopy(retFuture.gcholder, msg)
|
2018-08-06 18:12:16 +00:00
|
|
|
else:
|
|
|
|
retFuture.gcholder = msg
|
2018-06-15 12:09:44 +00:00
|
|
|
let length = if msglen <= 0: len(msg) else: msglen
|
2018-06-05 20:21:07 +00:00
|
|
|
var vector = StreamVector(kind: DataBuffer,
|
|
|
|
writer: cast[Future[int]](retFuture),
|
2020-02-12 20:54:05 +00:00
|
|
|
buf: addr retFuture.gcholder[0], buflen: length,
|
|
|
|
size: length)
|
2018-06-05 20:21:07 +00:00
|
|
|
transp.queue.addLast(vector)
|
2020-03-05 09:59:10 +00:00
|
|
|
transp.resumeWrite()
|
2018-06-05 20:21:07 +00:00
|
|
|
return retFuture
|
|
|
|
|
2018-06-15 12:09:44 +00:00
|
|
|
proc write*[T](transp: StreamTransport, msg: seq[T], msglen = -1): Future[int] =
|
2018-06-05 20:21:07 +00:00
|
|
|
## Write sequence ``msg`` using transport ``transp``.
|
2019-03-30 22:31:10 +00:00
|
|
|
var retFuture = newFutureSeq[int, T]("stream.transport.write(seq)")
|
2018-06-05 20:21:07 +00:00
|
|
|
transp.checkClosed(retFuture)
|
2019-10-23 11:13:23 +00:00
|
|
|
transp.checkWriteEof(retFuture)
|
2018-06-15 12:09:44 +00:00
|
|
|
if not isLiteral(msg):
|
|
|
|
shallowCopy(retFuture.gcholder, msg)
|
2018-08-06 18:12:16 +00:00
|
|
|
else:
|
|
|
|
retFuture.gcholder = msg
|
2018-06-15 12:09:44 +00:00
|
|
|
let length = if msglen <= 0: (len(msg) * sizeof(T)) else: (msglen * sizeof(T))
|
2018-06-05 20:21:07 +00:00
|
|
|
var vector = StreamVector(kind: DataBuffer,
|
|
|
|
writer: cast[Future[int]](retFuture),
|
2018-06-15 12:09:44 +00:00
|
|
|
buf: addr retFuture.gcholder[0],
|
2020-02-12 20:54:05 +00:00
|
|
|
buflen: length, size: length)
|
2018-06-05 20:21:07 +00:00
|
|
|
transp.queue.addLast(vector)
|
2020-03-05 09:59:10 +00:00
|
|
|
transp.resumeWrite()
|
2018-06-05 20:21:07 +00:00
|
|
|
return retFuture
|
2018-06-04 16:42:54 +00:00
|
|
|
|
2018-05-21 21:52:57 +00:00
|
|
|
proc writeFile*(transp: StreamTransport, handle: int,
|
2018-06-05 22:48:03 +00:00
|
|
|
offset: uint = 0, size: int = 0): Future[int] =
|
2018-05-22 21:03:13 +00:00
|
|
|
## Write data from file descriptor ``handle`` to transport ``transp``.
|
2018-06-04 09:57:17 +00:00
|
|
|
##
|
2018-05-22 21:03:13 +00:00
|
|
|
## You can specify starting ``offset`` in opened file and number of bytes
|
|
|
|
## to transfer from file to transport via ``size``.
|
2018-10-25 10:19:19 +00:00
|
|
|
when defined(windows):
|
|
|
|
if transp.kind != TransportKind.Socket:
|
|
|
|
raise newException(TransportNoSupport, "writeFile() is not supported!")
|
2019-03-30 22:31:10 +00:00
|
|
|
var retFuture = newFuture[int]("stream.transport.writeFile")
|
2018-06-05 22:48:03 +00:00
|
|
|
transp.checkClosed(retFuture)
|
2019-10-23 11:13:23 +00:00
|
|
|
transp.checkWriteEof(retFuture)
|
2018-06-05 20:21:07 +00:00
|
|
|
var vector = StreamVector(kind: DataFile, writer: retFuture,
|
2018-05-21 21:52:57 +00:00
|
|
|
buf: cast[pointer](size), offset: offset,
|
|
|
|
buflen: handle)
|
|
|
|
transp.queue.addLast(vector)
|
2020-03-05 09:59:10 +00:00
|
|
|
transp.resumeWrite()
|
2018-06-05 20:21:07 +00:00
|
|
|
return retFuture
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-06-04 16:42:54 +00:00
|
|
|
proc atEof*(transp: StreamTransport): bool {.inline.} =
|
|
|
|
## Returns ``true`` if ``transp`` is at EOF.
|
|
|
|
result = (transp.offset == 0) and (ReadEof in transp.state) and
|
|
|
|
(ReadPaused in transp.state)
|
|
|
|
|
2020-03-05 09:59:10 +00:00
|
|
|
template prepareReader(transp: StreamTransport,
|
|
|
|
name: static string): Future[void] =
|
|
|
|
checkPending(transp)
|
|
|
|
var fut = newFuture[void](name)
|
|
|
|
transp.reader = fut
|
|
|
|
resumeRead(transp)
|
|
|
|
fut
|
|
|
|
|
|
|
|
template readLoop(name, body: untyped): untyped =
|
|
|
|
# Read data until a predicate is satisfied - the body should return a tuple
|
|
|
|
# signalling how many bytes have been processed and whether we're done reading
|
|
|
|
checkClosed(transp)
|
|
|
|
checkPending(transp)
|
|
|
|
while true:
|
|
|
|
if ReadClosed in transp.state:
|
|
|
|
raise newException(TransportUseClosedError,
|
|
|
|
"Attempt to read data from closed stream")
|
|
|
|
if transp.offset == 0:
|
|
|
|
# We going to raise an error, only if transport buffer is empty.
|
|
|
|
if ReadError in transp.state:
|
|
|
|
raise transp.getError()
|
|
|
|
|
|
|
|
let (consumed, done) = body
|
|
|
|
transp.shiftBuffer(consumed)
|
|
|
|
if done:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
await transp.prepareReader(name)
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
proc readExactly*(transp: StreamTransport, pbytes: pointer,
|
2018-05-21 21:52:57 +00:00
|
|
|
nbytes: int) {.async.} =
|
2018-05-22 21:03:13 +00:00
|
|
|
## Read exactly ``nbytes`` bytes from transport ``transp`` and store it to
|
2020-09-10 00:50:06 +00:00
|
|
|
## ``pbytes``. ``pbytes`` must not be ``nil`` pointer and ``nbytes`` should
|
|
|
|
## be Natural.
|
|
|
|
##
|
|
|
|
## If ``nbytes == 0`` this operation will return immediately.
|
2018-06-04 09:57:17 +00:00
|
|
|
##
|
2018-05-22 21:03:13 +00:00
|
|
|
## If EOF is received and ``nbytes`` is not yet readed, the procedure
|
2020-03-05 09:59:10 +00:00
|
|
|
## will raise ``TransportIncompleteError``, potentially with some bytes
|
|
|
|
## already written.
|
2020-09-10 00:50:06 +00:00
|
|
|
doAssert(not(isNil(pbytes)), "pbytes must not be nil")
|
|
|
|
doAssert(nbytes >= 0, "nbytes must be non-negative integer")
|
|
|
|
|
|
|
|
if nbytes == 0:
|
|
|
|
return
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
var index = 0
|
2020-03-05 09:59:10 +00:00
|
|
|
var pbuffer = cast[ptr UncheckedArray[byte]](pbytes)
|
|
|
|
readLoop("stream.transport.readExactly"):
|
2018-05-16 08:22:34 +00:00
|
|
|
if transp.offset == 0:
|
2020-03-05 09:59:10 +00:00
|
|
|
if transp.atEof():
|
2018-05-16 08:22:34 +00:00
|
|
|
raise newException(TransportIncompleteError, "Data incomplete!")
|
2020-03-05 09:59:10 +00:00
|
|
|
let count = min(nbytes - index, transp.offset)
|
|
|
|
if count > 0:
|
|
|
|
copyMem(addr pbuffer[index], addr(transp.buffer[0]), count)
|
|
|
|
index += count
|
|
|
|
(consumed: count, done: index == nbytes)
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
proc readOnce*(transp: StreamTransport, pbytes: pointer,
|
|
|
|
nbytes: int): Future[int] {.async.} =
|
|
|
|
## Perform one read operation on transport ``transp``.
|
2018-06-04 09:57:17 +00:00
|
|
|
##
|
2018-05-27 05:49:47 +00:00
|
|
|
## If internal buffer is not empty, ``nbytes`` bytes will be transferred from
|
|
|
|
## internal buffer, otherwise it will wait until some bytes will be received.
|
2020-09-10 00:50:06 +00:00
|
|
|
doAssert(not(isNil(pbytes)), "pbytes must not be nil")
|
|
|
|
doAssert(nbytes > 0, "nbytes must be positive integer")
|
|
|
|
|
2020-03-05 09:59:10 +00:00
|
|
|
var count = 0
|
|
|
|
readLoop("stream.transport.readOnce"):
|
2018-05-16 08:22:34 +00:00
|
|
|
if transp.offset == 0:
|
2020-03-05 09:59:10 +00:00
|
|
|
(0, transp.atEof())
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
2020-03-05 09:59:10 +00:00
|
|
|
count = min(transp.offset, nbytes)
|
|
|
|
copyMem(pbytes, addr(transp.buffer[0]), count)
|
|
|
|
(count, true)
|
|
|
|
return count
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
proc readUntil*(transp: StreamTransport, pbytes: pointer, nbytes: int,
|
|
|
|
sep: seq[byte]): Future[int] {.async.} =
|
2018-05-22 21:03:13 +00:00
|
|
|
## Read data from the transport ``transp`` until separator ``sep`` is found.
|
2018-06-04 09:57:17 +00:00
|
|
|
##
|
2018-05-22 21:03:13 +00:00
|
|
|
## On success, the data and separator will be removed from the internal
|
2019-05-28 06:29:00 +00:00
|
|
|
## buffer (consumed). Returned data will include the separator at the end.
|
2018-06-04 09:57:17 +00:00
|
|
|
##
|
2018-05-28 23:35:15 +00:00
|
|
|
## If EOF is received, and `sep` was not found, procedure will raise
|
|
|
|
## ``TransportIncompleteError``.
|
2018-06-04 09:57:17 +00:00
|
|
|
##
|
2018-05-28 23:35:15 +00:00
|
|
|
## If ``nbytes`` bytes has been received and `sep` was not found, procedure
|
|
|
|
## will raise ``TransportLimitError``.
|
2018-06-04 09:57:17 +00:00
|
|
|
##
|
2018-05-28 23:35:15 +00:00
|
|
|
## Procedure returns actual number of bytes read.
|
2020-09-10 00:50:06 +00:00
|
|
|
doAssert(not(isNil(pbytes)), "pbytes must not be nil")
|
|
|
|
doAssert(len(sep) > 0, "separator must not be empty")
|
|
|
|
doAssert(nbytes >= 0, "nbytes must be non-negative integer")
|
|
|
|
|
|
|
|
if nbytes == 0:
|
|
|
|
raise newException(TransportLimitError, "Limit reached!")
|
|
|
|
|
2020-03-05 09:59:10 +00:00
|
|
|
var pbuffer = cast[ptr UncheckedArray[byte]](pbytes)
|
2018-05-16 08:22:34 +00:00
|
|
|
var state = 0
|
|
|
|
var k = 0
|
|
|
|
|
2020-03-05 09:59:10 +00:00
|
|
|
readLoop("stream.transport.readUntil"):
|
|
|
|
if transp.atEof():
|
2018-06-05 05:51:59 +00:00
|
|
|
raise newException(TransportIncompleteError, "Data incomplete!")
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2020-03-05 09:59:10 +00:00
|
|
|
var index = 0
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
while index < transp.offset:
|
2020-03-05 09:59:10 +00:00
|
|
|
if k >= nbytes:
|
|
|
|
raise newException(TransportLimitError, "Limit reached!")
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
let ch = transp.buffer[index]
|
2020-03-05 09:59:10 +00:00
|
|
|
inc(index)
|
|
|
|
|
|
|
|
pbuffer[k] = ch
|
|
|
|
inc(k)
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
if sep[state] == ch:
|
|
|
|
inc(state)
|
2020-03-05 09:59:10 +00:00
|
|
|
if state == len(sep):
|
|
|
|
break
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
|
|
|
state = 0
|
|
|
|
|
2020-03-05 09:59:10 +00:00
|
|
|
(index, state == len(sep))
|
|
|
|
|
|
|
|
return k
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
proc readLine*(transp: StreamTransport, limit = 0,
|
|
|
|
sep = "\r\n"): Future[string] {.async.} =
|
2018-05-22 21:03:13 +00:00
|
|
|
## Read one line from transport ``transp``, where "line" is a sequence of
|
|
|
|
## bytes ending with ``sep`` (default is "\r\n").
|
2018-06-04 09:57:17 +00:00
|
|
|
##
|
2018-05-22 21:03:13 +00:00
|
|
|
## If EOF is received, and ``sep`` was not found, the method will return the
|
|
|
|
## partial read bytes.
|
2018-06-04 09:57:17 +00:00
|
|
|
##
|
2018-05-22 21:03:13 +00:00
|
|
|
## If the EOF was received and the internal buffer is empty, return an
|
|
|
|
## empty string.
|
2018-06-04 09:57:17 +00:00
|
|
|
##
|
2018-05-22 21:03:13 +00:00
|
|
|
## If ``limit`` more then 0, then read is limited to ``limit`` bytes.
|
2020-03-05 09:59:10 +00:00
|
|
|
let lim = if limit <= 0: -1 else: limit
|
2018-05-16 08:22:34 +00:00
|
|
|
var state = 0
|
|
|
|
|
2020-03-05 09:59:10 +00:00
|
|
|
readLoop("stream.transport.readLine"):
|
|
|
|
if transp.atEof():
|
|
|
|
(0, true)
|
|
|
|
else:
|
|
|
|
var index = 0
|
|
|
|
while index < transp.offset:
|
|
|
|
let ch = char(transp.buffer[index])
|
|
|
|
index += 1
|
|
|
|
|
|
|
|
if sep[state] == ch:
|
|
|
|
inc(state)
|
|
|
|
if state == len(sep):
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
if state != 0:
|
|
|
|
if limit > 0:
|
|
|
|
let missing = min(state, lim - len(result) - 1)
|
|
|
|
result.add(sep[0 ..< missing])
|
|
|
|
else:
|
|
|
|
result.add(sep[0 ..< state])
|
|
|
|
state = 0
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2020-03-05 09:59:10 +00:00
|
|
|
result.add(ch)
|
|
|
|
if len(result) == lim:
|
|
|
|
break
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2020-03-05 09:59:10 +00:00
|
|
|
(index, (state == len(sep)) or (lim == len(result)))
|
|
|
|
|
|
|
|
proc read*(transp: StreamTransport): Future[seq[byte]] {.async.} =
|
|
|
|
## Read all bytes from transport ``transp``.
|
|
|
|
##
|
|
|
|
## This procedure allocates buffer seq[byte] and return it as result.
|
|
|
|
readLoop("stream.transport.read"):
|
|
|
|
if transp.atEof():
|
|
|
|
(0, true)
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
2020-03-05 09:59:10 +00:00
|
|
|
result.add(transp.buffer.toOpenArray(0, transp.offset - 1))
|
|
|
|
(transp.offset, false)
|
|
|
|
|
|
|
|
proc read*(transp: StreamTransport, n: int): Future[seq[byte]] {.async.} =
|
2019-05-28 17:12:00 +00:00
|
|
|
## Read all bytes (n <= 0) or exactly `n` bytes from transport ``transp``.
|
2018-06-04 09:57:17 +00:00
|
|
|
##
|
2018-05-28 23:35:15 +00:00
|
|
|
## This procedure allocates buffer seq[byte] and return it as result.
|
2020-03-05 09:59:10 +00:00
|
|
|
if n <= 0:
|
|
|
|
return await transp.read()
|
|
|
|
else:
|
|
|
|
readLoop("stream.transport.read"):
|
|
|
|
if transp.atEof():
|
|
|
|
(0, true)
|
2018-05-16 08:22:34 +00:00
|
|
|
else:
|
2020-03-05 09:59:10 +00:00
|
|
|
let count = min(transp.offset, n - len(result))
|
|
|
|
result.add(transp.buffer.toOpenArray(0, count - 1))
|
|
|
|
(count, len(result) == n)
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2020-03-05 09:59:10 +00:00
|
|
|
proc consume*(transp: StreamTransport): Future[int] {.async.} =
|
|
|
|
## Consume all bytes from transport ``transp`` and discard it.
|
2018-06-04 16:42:54 +00:00
|
|
|
##
|
2020-03-05 09:59:10 +00:00
|
|
|
## Return number of bytes actually consumed and discarded.
|
|
|
|
readLoop("stream.transport.consume"):
|
|
|
|
if transp.atEof():
|
|
|
|
(0, true)
|
|
|
|
else:
|
|
|
|
result += transp.offset
|
|
|
|
(transp.offset, false)
|
2018-06-05 05:51:59 +00:00
|
|
|
|
2020-03-05 09:59:10 +00:00
|
|
|
proc consume*(transp: StreamTransport, n: int): Future[int] {.async.} =
|
|
|
|
## Consume all bytes (n <= 0) or ``n`` bytes from transport ``transp`` and
|
|
|
|
## discard it.
|
|
|
|
##
|
|
|
|
## Return number of bytes actually consumed and discarded.
|
|
|
|
if n <= 0:
|
|
|
|
return await transp.consume()
|
|
|
|
else:
|
|
|
|
readLoop("stream.transport.consume"):
|
|
|
|
if transp.atEof():
|
|
|
|
(0, true)
|
2018-06-04 16:42:54 +00:00
|
|
|
else:
|
2020-03-05 09:59:10 +00:00
|
|
|
let count = min(transp.offset, n - result)
|
|
|
|
result += count
|
|
|
|
(count, result == n)
|
|
|
|
|
|
|
|
proc readMessage*(transp: StreamTransport,
|
|
|
|
predicate: ReadMessagePredicate) {.async.} =
|
|
|
|
## Read all bytes from transport ``transp`` until ``predicate`` callback
|
|
|
|
## will not be satisfied.
|
|
|
|
##
|
|
|
|
## ``predicate`` callback should return tuple ``(consumed, result)``, where
|
|
|
|
## ``consumed`` is the number of bytes processed and ``result`` is a
|
|
|
|
## completion flag (``true`` if readMessage() should stop reading data,
|
|
|
|
## or ``false`` if readMessage() should continue to read data from transport).
|
|
|
|
##
|
|
|
|
## ``predicate`` callback must copy all the data from ``data`` array and
|
|
|
|
## return number of bytes it is going to consume.
|
|
|
|
## ``predicate`` callback will receive (zero-length) openarray, if transport
|
|
|
|
## is at EOF.
|
|
|
|
readLoop("stream.transport.readMessage"):
|
|
|
|
if transp.offset == 0:
|
|
|
|
if transp.atEof():
|
|
|
|
predicate([])
|
|
|
|
else:
|
|
|
|
# Case, when transport's buffer is not yet filled with data.
|
|
|
|
(0, false)
|
|
|
|
else:
|
|
|
|
predicate(transp.buffer.toOpenArray(0, transp.offset - 1))
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-08-24 12:20:08 +00:00
|
|
|
proc join*(transp: StreamTransport): Future[void] =
|
2018-05-16 08:22:34 +00:00
|
|
|
## Wait until ``transp`` will not be closed.
|
2019-03-30 22:31:10 +00:00
|
|
|
var retFuture = newFuture[void]("stream.transport.join")
|
2019-06-20 20:30:41 +00:00
|
|
|
|
|
|
|
proc continuation(udata: pointer) {.gcsafe.} =
|
|
|
|
retFuture.complete()
|
|
|
|
|
|
|
|
proc cancel(udata: pointer) {.gcsafe.} =
|
|
|
|
transp.future.removeCallback(continuation, cast[pointer](retFuture))
|
|
|
|
|
|
|
|
if not(transp.future.finished()):
|
|
|
|
transp.future.addCallback(continuation, cast[pointer](retFuture))
|
|
|
|
retFuture.cancelCallback = cancel
|
2018-08-24 12:20:08 +00:00
|
|
|
else:
|
|
|
|
retFuture.complete()
|
|
|
|
return retFuture
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
proc close*(transp: StreamTransport) =
|
|
|
|
## Closes and frees resources of transport ``transp``.
|
2018-08-24 12:20:08 +00:00
|
|
|
##
|
|
|
|
## Please note that release of resources is not completed immediately, to be
|
|
|
|
## sure all resources got released please use ``await transp.join()``.
|
2019-06-20 20:30:41 +00:00
|
|
|
proc continuation(udata: pointer) {.gcsafe.} =
|
2020-06-24 08:21:52 +00:00
|
|
|
transp.clean()
|
2018-08-24 12:20:08 +00:00
|
|
|
|
2018-06-04 09:57:17 +00:00
|
|
|
if {ReadClosed, WriteClosed} * transp.state == {}:
|
2018-08-24 12:20:08 +00:00
|
|
|
transp.state.incl({WriteClosed, ReadClosed})
|
2018-05-16 08:22:34 +00:00
|
|
|
when defined(windows):
|
2018-10-25 10:19:19 +00:00
|
|
|
if transp.kind == TransportKind.Pipe:
|
|
|
|
if WinServerPipe in transp.flags:
|
|
|
|
if WinNoPipeFlash notin transp.flags:
|
|
|
|
discard flushFileBuffers(Handle(transp.fd))
|
2020-06-24 08:21:52 +00:00
|
|
|
discard disconnectNamedPipe(Handle(transp.fd))
|
2018-10-25 10:19:19 +00:00
|
|
|
else:
|
|
|
|
if WinNoPipeFlash notin transp.flags:
|
|
|
|
discard flushFileBuffers(Handle(transp.fd))
|
2019-03-30 22:31:10 +00:00
|
|
|
if ReadPaused in transp.state:
|
|
|
|
# If readStreamLoop() is not running we need to finish in
|
|
|
|
# continuation step.
|
|
|
|
closeHandle(transp.fd, continuation)
|
|
|
|
else:
|
|
|
|
# If readStreamLoop() is running, it will be properly finished inside
|
|
|
|
# of readStreamLoop().
|
|
|
|
closeHandle(transp.fd)
|
2018-10-25 10:19:19 +00:00
|
|
|
elif transp.kind == TransportKind.Socket:
|
2019-03-30 22:31:10 +00:00
|
|
|
if ReadPaused in transp.state:
|
|
|
|
# If readStreamLoop() is not running we need to finish in
|
|
|
|
# continuation step.
|
|
|
|
closeSocket(transp.fd, continuation)
|
|
|
|
else:
|
|
|
|
# If readStreamLoop() is running, it will be properly finished inside
|
|
|
|
# of readStreamLoop().
|
|
|
|
closeSocket(transp.fd)
|
2018-10-25 10:19:19 +00:00
|
|
|
else:
|
2019-07-15 09:59:42 +00:00
|
|
|
if transp.kind == TransportKind.Pipe:
|
|
|
|
closeHandle(transp.fd, continuation)
|
|
|
|
elif transp.kind == TransportKind.Socket:
|
|
|
|
closeSocket(transp.fd, continuation)
|
2018-08-24 12:20:08 +00:00
|
|
|
|
|
|
|
proc closeWait*(transp: StreamTransport): Future[void] =
|
|
|
|
## Close and frees resources of transport ``transp``.
|
2018-09-13 18:15:57 +00:00
|
|
|
transp.close()
|
2018-08-24 12:20:08 +00:00
|
|
|
result = transp.join()
|
2018-07-13 08:24:52 +00:00
|
|
|
|
|
|
|
proc closed*(transp: StreamTransport): bool {.inline.} =
|
|
|
|
## Returns ``true`` if transport in closed state.
|
|
|
|
result = ({ReadClosed, WriteClosed} * transp.state != {})
|
2019-07-15 09:59:42 +00:00
|
|
|
|
|
|
|
proc fromPipe*(fd: AsyncFD, child: StreamTransport = nil,
|
|
|
|
bufferSize = DefaultStreamBufferSize): StreamTransport =
|
|
|
|
## Create new transport object using pipe's file descriptor.
|
|
|
|
##
|
|
|
|
## ``bufferSize`` is size of internal buffer for transport.
|
|
|
|
register(fd)
|
|
|
|
result = newStreamPipeTransport(fd, bufferSize, child)
|
|
|
|
# Start tracking transport
|
|
|
|
trackStream(result)
|