nim-chronos/chronos/handles.nim

218 lines
7.9 KiB
Nim
Raw Normal View History

2018-05-16 08:22:34 +00:00
#
# Chronos Handles
# (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)
2019-10-24 13:21:31 +00:00
import net, nativesockets, asyncloop
2018-05-16 08:22:34 +00:00
when defined(windows):
2019-10-24 13:21:31 +00:00
import os, winlean
2018-05-16 08:22:34 +00:00
const
2018-05-21 21:52:57 +00:00
asyncInvalidSocket* = AsyncFD(-1)
TCP_NODELAY* = 1
IPPROTO_TCP* = 6
PIPE_TYPE_BYTE = 0x00000000'i32
PIPE_READMODE_BYTE = 0x00000000'i32
PIPE_WAIT = 0x00000000'i32
DEFAULT_PIPE_SIZE = 65536'i32
ERROR_PIPE_CONNECTED = 535
ERROR_PIPE_BUSY = 231
pipeHeaderName = r"\\.\pipe\chronos\"
proc connectNamedPipe(hNamedPipe: Handle, lpOverlapped: pointer): WINBOOL
{.importc: "ConnectNamedPipe", stdcall, dynlib: "kernel32".}
2018-05-16 08:22:34 +00:00
else:
import os, posix
2018-05-16 08:22:34 +00:00
const
asyncInvalidSocket* = AsyncFD(posix.INVALID_SOCKET)
TCP_NODELAY* = 1
IPPROTO_TCP* = 6
2018-05-16 08:22:34 +00:00
const
asyncInvalidPipe* = asyncInvalidSocket
2018-05-16 08:22:34 +00:00
proc setSocketBlocking*(s: SocketHandle, blocking: bool): bool =
## Sets blocking mode on socket.
when defined(windows):
result = true
var mode = clong(ord(not blocking))
if ioctlsocket(s, FIONBIO, addr(mode)) == -1:
result = false
else:
result = true
var x: int = fcntl(s, F_GETFL, 0)
if x == -1:
result = false
else:
var mode = if blocking: x and not O_NONBLOCK else: x or O_NONBLOCK
if fcntl(s, F_SETFL, mode) == -1:
result = false
2018-05-21 21:52:57 +00:00
proc setSockOpt*(socket: AsyncFD, level, optname, optval: int): bool =
2018-05-16 08:22:34 +00:00
## `setsockopt()` for integer options.
## Returns ``true`` on success, ``false`` on error.
var value = cint(optval)
2019-05-10 05:27:05 +00:00
result = setsockopt(SocketHandle(socket), cint(level), cint(optname),
addr(value), SockLen(sizeof(value))) >= cint(0)
proc setSockOpt*(socket: AsyncFD, level, optname: int, value: pointer,
valuelen: int): bool =
## `setsockopt()` for custom options (pointer and length).
## Returns ``true`` on success, ``false`` on error.
2019-05-10 05:27:05 +00:00
result = setsockopt(SocketHandle(socket), cint(level), cint(optname), value,
SockLen(valuelen)) >= cint(0)
2018-05-21 21:52:57 +00:00
proc getSockOpt*(socket: AsyncFD, level, optname: int, value: var int): bool =
2018-05-16 08:22:34 +00:00
## `getsockopt()` for integer options.
## Returns ``true`` on success, ``false`` on error.
2018-05-16 08:22:34 +00:00
var res: cint
2019-05-10 05:27:05 +00:00
var size = SockLen(sizeof(res))
2018-05-16 08:22:34 +00:00
if getsockopt(SocketHandle(socket), cint(level), cint(optname),
2019-05-10 05:27:05 +00:00
addr(res), addr(size)) >= cint(0):
value = int(res)
result = true
2018-05-16 08:22:34 +00:00
proc getSockOpt*(socket: AsyncFD, level, optname: int, value: pointer,
valuelen: var int): bool =
## `getsockopt()` for custom options (pointer and length).
## Returns ``true`` on success, ``false`` on error.
2019-05-10 05:27:05 +00:00
result = getsockopt(SocketHandle(socket), cint(level), cint(optname),
value, cast[ptr Socklen](addr valuelen)) >= cint(0)
2018-05-21 21:52:57 +00:00
proc getSocketError*(socket: AsyncFD, err: var int): bool =
## Recover error code associated with socket handle ``socket``.
2019-05-10 05:27:05 +00:00
result = getSockOpt(socket, cint(SOL_SOCKET), cint(SO_ERROR), err)
2018-05-16 08:22:34 +00:00
proc createAsyncSocket*(domain: Domain, sockType: SockType,
protocol: Protocol): AsyncFD =
## Creates new asynchronous socket.
## Returns ``asyncInvalidSocket`` on error.
let handle = createNativeSocket(domain, sockType, protocol)
if handle == osInvalidSocket:
return asyncInvalidSocket
if not setSocketBlocking(handle, false):
close(handle)
return asyncInvalidSocket
when defined(macosx) and not defined(nimdoc):
2018-05-21 21:52:57 +00:00
if not setSockOpt(AsyncFD(handle), SOL_SOCKET, SO_NOSIGPIPE, 1):
2018-05-16 08:22:34 +00:00
close(handle)
return asyncInvalidSocket
result = AsyncFD(handle)
register(result)
proc wrapAsyncSocket*(sock: SocketHandle): AsyncFD =
2018-05-21 21:52:57 +00:00
## Wraps socket to asynchronous socket handle.
2018-05-16 08:22:34 +00:00
## Return ``asyncInvalidSocket`` on error.
if not setSocketBlocking(sock, false):
close(sock)
return asyncInvalidSocket
when defined(macosx) and not defined(nimdoc):
2018-05-21 21:52:57 +00:00
if not setSockOpt(AsyncFD(sock), SOL_SOCKET, SO_NOSIGPIPE, 1):
2018-05-16 08:22:34 +00:00
close(sock)
return asyncInvalidSocket
result = AsyncFD(sock)
register(result)
proc getMaxOpenFiles*(): int =
## Returns maximum file descriptor number that can be opened by this process.
##
## Note: On Windows its impossible to obtain such number, so getMaxOpenFiles()
## will return constant value of 16384. You can get more information on this
## link https://docs.microsoft.com/en-us/archive/blogs/markrussinovich/pushing-the-limits-of-windows-handles
when defined(windows):
result = 16384
else:
var limits: RLimit
if getrlimit(posix.RLIMIT_NOFILE, limits) != 0:
raiseOSError(osLastError())
result = int(limits.rlim_cur)
proc setMaxOpenFiles*(count: int) =
## Set maximum file descriptor number that can be opened by this process.
##
## Note: On Windows its impossible to set this value, so it just a nop call.
when defined(windows):
discard
else:
var limits: RLimit
if getrlimit(posix.RLIMIT_NOFILE, limits) != 0:
raiseOSError(osLastError())
limits.rlim_cur = count
if setrlimit(posix.RLIMIT_NOFILE, limits) != 0:
raiseOSError(osLastError())
proc createAsyncPipe*(): tuple[read: AsyncFD, write: AsyncFD] =
## Create new asynchronouse pipe.
## Returns tuple of read pipe handle and write pipe handle``asyncInvalidPipe`` on error.
when defined(windows):
var pipeIn, pipeOut: Handle
var pipeName: WideCString
var uniq = 0'u64
var sa = SECURITY_ATTRIBUTES(nLength: sizeof(SECURITY_ATTRIBUTES).cint,
lpSecurityDescriptor: nil, bInheritHandle: 0)
while true:
QueryPerformanceCounter(uniq)
pipeName = newWideCString(pipeHeaderName & $uniq)
var openMode = FILE_FLAG_FIRST_PIPE_INSTANCE or FILE_FLAG_OVERLAPPED or
PIPE_ACCESS_INBOUND
var pipeMode = PIPE_TYPE_BYTE or PIPE_READMODE_BYTE or PIPE_WAIT
pipeIn = createNamedPipe(pipeName, openMode, pipeMode, 1'i32,
DEFAULT_PIPE_SIZE, DEFAULT_PIPE_SIZE,
0'i32, addr sa)
if pipeIn == INVALID_HANDLE_VALUE:
let err = osLastError()
# If error in {ERROR_ACCESS_DENIED, ERROR_PIPE_BUSY}, then named pipe
# with such name already exists.
if int32(err) != ERROR_ACCESS_DENIED and int32(err) != ERROR_PIPE_BUSY:
result = (read: asyncInvalidPipe, write: asyncInvalidPipe)
return
continue
else:
break
var openMode = (GENERIC_WRITE or FILE_WRITE_DATA or SYNCHRONIZE)
pipeOut = createFileW(pipeName, openMode, 0, addr(sa), OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, 0)
if pipeOut == INVALID_HANDLE_VALUE:
discard closeHandle(pipeIn)
result = (read: asyncInvalidPipe, write: asyncInvalidPipe)
return
var ovl = OVERLAPPED()
let res = connectNamedPipe(pipeIn, cast[pointer](addr ovl))
if res == 0:
let err = osLastError()
if int32(err) == ERROR_PIPE_CONNECTED:
discard
elif int32(err) == ERROR_IO_PENDING:
var bytesRead = 0.Dword
if getOverlappedResult(pipeIn, addr ovl, bytesRead, 1) == 0:
discard closeHandle(pipeIn)
discard closeHandle(pipeOut)
result = (read: asyncInvalidPipe, write: asyncInvalidPipe)
return
else:
discard closeHandle(pipeIn)
discard closeHandle(pipeOut)
result = (read: asyncInvalidPipe, write: asyncInvalidPipe)
return
result = (read: AsyncFD(pipeIn), write: AsyncFD(pipeOut))
else:
var fds: array[2, cint]
if posix.pipe(fds) == -1:
result = (read: asyncInvalidPipe, write: asyncInvalidPipe)
return
if not(setSocketBlocking(SocketHandle(fds[0]), false)) or
not(setSocketBlocking(SocketHandle(fds[1]), false)):
result = (read: asyncInvalidPipe, write: asyncInvalidPipe)
return
result = (read: AsyncFD(fds[0]), write: AsyncFD(fds[1]))