Add facility to emulate signals on Windows. (#357)
* Initial Windows asyncproc part. Deprecate usage of net/nativesockets in handles, asyncloop. Introduce primitives with inheritable flag. * Some posix fixes. * More duplicate fixes. * Fix AsyncProcessOption.EchoCommand option. Remove isMainModule code. Fix execCommand to use AsyncProcessOption.EvalCommand. * Initial asyncproc tests commit. * Some Posix fixes. * Fix Posix crash. * Add testsuite. Fix osnet.nim to use proper declarations. Fix timer.nim to use proper declarations. Add Windows environment handling procedures. Fix createAsyncPipe. Add leaks tracking for AsyncProcessRef. * Fix O_CLOEXEC constant value. * Add file descriptors leak test. * Remove commented code. Refactor exceptions. Fix compilation warnings. * No exception ioselectors_kqueue initial commit. * Some BSD fixes. Linux refactoring of selectors.nim. * Some fixes to move further. * Last Linux fixes. * Fixes for asyncloop to use 2nd version of selectors api. * Add osutils.nim. * Some fixes. * Hardening resumeRead(), resumeWrite() and consumers. Add ESRCH handling. Introduce no-exception fromPipe2. * Make Windows part exception-free and fix zombie race issue. * createStreamServer() fixes. * Upgrade asyncproc to use non-exception primitives. Fix ioselectors_kqueue to use closeFd(). * Refactor accept() and acceptLoop() to be exception free. * Deprecated some `result` usage. Last fixes to make stream.nim exception free. Use closeFd(). Refactor some loops to use handleEintr(). * Fix connect() forgot to unregister socket on error. * All createAsyncSocket() sockets should be closed with unregisterAndCloseFd(). * Attempt to fix posix bug with incomplete output. * Change number of runs in big test. * Refactoring pipes creation. Attempt to fix "write error: Resource temporarily unavailable". * Fix waitForExit(duration) code. Fix test exit code. * Fix Windows missing SIGTERM. * Fix mistype. * Fix compilation error. * Attempt to fix Nim 1.6 issue. * Eliminate Nim's WideCString usage to avoid any problems in future. * Deprecate posix usage in osnet. * Eliminate unused imports. * Some debugging statements for investigation. * Remove all the debugging statements. * Remove testhelpers in favor of unittest2/asynctests. * Fix flaky test. * Make test more resilient to timings. * Add memory size dump to CI. * Refactor some blocks to use valueOr. Make waitForExit to use kill() instead of terminate(). * Remove memory dumps. * Fix peekProcessExitCode() blocks issue. * Fix Windows issue. * Add some debugging echoes. * Fix compilation error. * Add some debugging echoes. * Add more helpers. * Fix compilation error. * waitForExit() is primary suspect. * Remove fast-path for waitForExit. Remove loop for reading signal events. * Remove all debugging echoes. * Return one debugging echo. * Fix futures tests. * Add test for multiple processes waiting to attempt stress test. * Refactor ioselectors_epoll for better signalfd and process monitoring. Add more race condition fixes to waitForExit. Fix some Nim 1.6 warnings. * Fix after rebase issues and warnings. * Fix style issues. Fix different Nim versions issues. Workaround `signalfd` style issues. * Add one more Linux signals workaround. Add one more multiple processes test. * Windows fixes. * Remove unixPlatform define. Fix WSAECONNABORTED for devel. * Temporarily disable rate limit tests. Fix more devel issues. * Deprecate `hasThreadSupport` for ioselectors_kqueue. Fix verifySelectParams issue. Add exit codes test for multiple processes. Fix osnet PtrToCstringConv warning. * ioselectors_kqueue refactoring. * Initial commit. * Fix 1.2-1.4 compilation issue. * Fix unused warning for testCtrlC() test. * Post-rebase fixes. * Restore master files. * More fixes. * Remove duplicates. * Fix style mistake. * Add more flexible pragmas.
This commit is contained in:
parent
315a27236c
commit
02fda01bf2
|
@ -10,8 +10,10 @@
|
||||||
|
|
||||||
when (NimMajor, NimMinor) < (1, 4):
|
when (NimMajor, NimMinor) < (1, 4):
|
||||||
{.push raises: [Defect].}
|
{.push raises: [Defect].}
|
||||||
|
{.pragma: callbackFunc, stdcall, gcsafe, raises: [Defect].}
|
||||||
else:
|
else:
|
||||||
{.push raises: [].}
|
{.push raises: [].}
|
||||||
|
{.pragma: callbackFunc, stdcall, gcsafe, raises: [].}
|
||||||
|
|
||||||
from nativesockets import Port
|
from nativesockets import Port
|
||||||
import std/[tables, strutils, heapqueue, deques]
|
import std/[tables, strutils, heapqueue, deques]
|
||||||
|
@ -289,6 +291,7 @@ func toException*(v: OSErrorCode): ref OSError = newOSError(v)
|
||||||
# Result[T, OSErrorCode] values.
|
# Result[T, OSErrorCode] values.
|
||||||
|
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
|
export SIGINT, SIGQUIT, SIGTERM
|
||||||
type
|
type
|
||||||
CompletionKey = ULONG_PTR
|
CompletionKey = ULONG_PTR
|
||||||
|
|
||||||
|
@ -301,11 +304,8 @@ when defined(windows):
|
||||||
CustomOverlapped* = object of OVERLAPPED
|
CustomOverlapped* = object of OVERLAPPED
|
||||||
data*: CompletionData
|
data*: CompletionData
|
||||||
|
|
||||||
OVERLAPPED_ENTRY* = object
|
DispatcherFlag* = enum
|
||||||
lpCompletionKey*: ULONG_PTR
|
SignalHandlerInstalled
|
||||||
lpOverlapped*: ptr CustomOverlapped
|
|
||||||
internal: ULONG_PTR
|
|
||||||
dwNumberOfBytesTransferred: DWORD
|
|
||||||
|
|
||||||
PDispatcher* = ref object of PDispatcherBase
|
PDispatcher* = ref object of PDispatcherBase
|
||||||
ioPort: HANDLE
|
ioPort: HANDLE
|
||||||
|
@ -315,6 +315,7 @@ when defined(windows):
|
||||||
getAcceptExSockAddrs*: WSAPROC_GETACCEPTEXSOCKADDRS
|
getAcceptExSockAddrs*: WSAPROC_GETACCEPTEXSOCKADDRS
|
||||||
transmitFile*: WSAPROC_TRANSMITFILE
|
transmitFile*: WSAPROC_TRANSMITFILE
|
||||||
getQueuedCompletionStatusEx*: LPFN_GETQUEUEDCOMPLETIONSTATUSEX
|
getQueuedCompletionStatusEx*: LPFN_GETQUEUEDCOMPLETIONSTATUSEX
|
||||||
|
flags: set[DispatcherFlag]
|
||||||
|
|
||||||
PtrCustomOverlapped* = ptr CustomOverlapped
|
PtrCustomOverlapped* = ptr CustomOverlapped
|
||||||
|
|
||||||
|
@ -330,6 +331,7 @@ when defined(windows):
|
||||||
|
|
||||||
WaitableHandle* = ref PostCallbackData
|
WaitableHandle* = ref PostCallbackData
|
||||||
ProcessHandle* = distinct WaitableHandle
|
ProcessHandle* = distinct WaitableHandle
|
||||||
|
SignalHandle* = distinct WaitableHandle
|
||||||
|
|
||||||
WaitableResult* {.pure.} = enum
|
WaitableResult* {.pure.} = enum
|
||||||
Ok, Timeout
|
Ok, Timeout
|
||||||
|
@ -444,7 +446,7 @@ when defined(windows):
|
||||||
|
|
||||||
{.push stackTrace: off.}
|
{.push stackTrace: off.}
|
||||||
proc waitableCallback(param: pointer, timerOrWaitFired: WINBOOL) {.
|
proc waitableCallback(param: pointer, timerOrWaitFired: WINBOOL) {.
|
||||||
stdcall, gcsafe.} =
|
callbackFunc.} =
|
||||||
# This procedure will be executed in `wait thread`, so it must not use
|
# This procedure will be executed in `wait thread`, so it must not use
|
||||||
# GC related objects.
|
# GC related objects.
|
||||||
# We going to ignore callbacks which was spawned when `isNil(param) == true`
|
# We going to ignore callbacks which was spawned when `isNil(param) == true`
|
||||||
|
@ -577,6 +579,86 @@ when defined(windows):
|
||||||
## Remove process' watching using process' descriptor ``procHandle``.
|
## Remove process' watching using process' descriptor ``procHandle``.
|
||||||
removeProcess2(procHandle).tryGet()
|
removeProcess2(procHandle).tryGet()
|
||||||
|
|
||||||
|
{.push stackTrace: off.}
|
||||||
|
proc consoleCtrlEventHandler(dwCtrlType: DWORD): uint32 {.callbackFunc.} =
|
||||||
|
## This procedure will be executed in different thread, so it MUST not use
|
||||||
|
## any GC related features (strings, seqs, echo etc.).
|
||||||
|
case dwCtrlType
|
||||||
|
of CTRL_C_EVENT:
|
||||||
|
return
|
||||||
|
(if raiseSignal(SIGINT).valueOr(false): TRUE else: FALSE)
|
||||||
|
of CTRL_BREAK_EVENT:
|
||||||
|
return
|
||||||
|
(if raiseSignal(SIGINT).valueOr(false): TRUE else: FALSE)
|
||||||
|
of CTRL_CLOSE_EVENT:
|
||||||
|
return
|
||||||
|
(if raiseSignal(SIGTERM).valueOr(false): TRUE else: FALSE)
|
||||||
|
of CTRL_LOGOFF_EVENT:
|
||||||
|
return
|
||||||
|
(if raiseSignal(SIGQUIT).valueOr(false): TRUE else: FALSE)
|
||||||
|
else:
|
||||||
|
FALSE
|
||||||
|
{.pop.}
|
||||||
|
|
||||||
|
proc addSignal2*(signal: int, cb: CallbackFunc,
|
||||||
|
udata: pointer = nil): Result[SignalHandle, OSErrorCode] =
|
||||||
|
## Start watching signal ``signal``, and when signal appears, call the
|
||||||
|
## callback ``cb`` with specified argument ``udata``. Returns signal
|
||||||
|
## identifier code, which can be used to remove signal callback
|
||||||
|
## via ``removeSignal``.
|
||||||
|
##
|
||||||
|
## NOTE: On Windows only subset of signals are supported: SIGINT, SIGTERM,
|
||||||
|
## SIGQUIT
|
||||||
|
const supportedSignals = [SIGINT, SIGTERM, SIGQUIT]
|
||||||
|
doAssert(cint(signal) in supportedSignals, "Signal is not supported")
|
||||||
|
let loop = getThreadDispatcher()
|
||||||
|
var hWait: WaitableHandle = nil
|
||||||
|
|
||||||
|
proc continuation(ucdata: pointer) {.gcsafe.} =
|
||||||
|
doAssert(not(isNil(ucdata)))
|
||||||
|
doAssert(not(isNil(hWait)))
|
||||||
|
cb(hWait[].udata)
|
||||||
|
|
||||||
|
if SignalHandlerInstalled notin loop.flags:
|
||||||
|
if getConsoleCP() != 0'u32:
|
||||||
|
# Console application, we going to cleanup Nim default signal handlers.
|
||||||
|
if setConsoleCtrlHandler(consoleCtrlEventHandler, TRUE) == FALSE:
|
||||||
|
return err(osLastError())
|
||||||
|
loop.flags.incl(SignalHandlerInstalled)
|
||||||
|
else:
|
||||||
|
return err(ERROR_NOT_SUPPORTED)
|
||||||
|
|
||||||
|
let
|
||||||
|
flags = WT_EXECUTEINWAITTHREAD
|
||||||
|
hEvent = ? openEvent($getSignalName(signal))
|
||||||
|
|
||||||
|
hWait = registerWaitable(hEvent, flags, InfiniteDuration,
|
||||||
|
continuation, udata).valueOr:
|
||||||
|
discard closeFd(hEvent)
|
||||||
|
return err(error)
|
||||||
|
ok(SignalHandle(hWait))
|
||||||
|
|
||||||
|
proc removeSignal2*(signalHandle: SignalHandle): Result[void, OSErrorCode] =
|
||||||
|
## Remove watching signal ``signal``.
|
||||||
|
? closeWaitable(WaitableHandle(signalHandle))
|
||||||
|
ok()
|
||||||
|
|
||||||
|
proc addSignal*(signal: int, cb: CallbackFunc,
|
||||||
|
udata: pointer = nil): SignalHandle {.
|
||||||
|
raises: [Defect, ValueError].} =
|
||||||
|
## Registers callback ``cb`` to be called when signal ``signal`` will be
|
||||||
|
## raised. Returns signal identifier, which can be used to clear signal
|
||||||
|
## callback via ``removeSignal``.
|
||||||
|
addSignal2(signal, cb, udata).valueOr:
|
||||||
|
raise newException(ValueError, osErrorMsg(error))
|
||||||
|
|
||||||
|
proc removeSignal*(signalHandle: SignalHandle) {.
|
||||||
|
raises: [Defect, ValueError].} =
|
||||||
|
## Remove signal's watching using signal descriptor ``signalfd``.
|
||||||
|
let res = removeSignal2(signalHandle)
|
||||||
|
if res.isErr():
|
||||||
|
raise newException(ValueError, osErrorMsg(res.error()))
|
||||||
|
|
||||||
proc poll*() =
|
proc poll*() =
|
||||||
## Perform single asynchronous step, processing timers and completing
|
## Perform single asynchronous step, processing timers and completing
|
||||||
## tasks. Blocks until at least one event has completed.
|
## tasks. Blocks until at least one event has completed.
|
||||||
|
@ -870,7 +952,6 @@ elif defined(macosx) or defined(freebsd) or defined(netbsd) or
|
||||||
## Please note, that socket is not closed immediately. To avoid bugs with
|
## Please note, that socket is not closed immediately. To avoid bugs with
|
||||||
## closing socket, while operation pending, socket will be closed as
|
## closing socket, while operation pending, socket will be closed as
|
||||||
## soon as all pending operations will be notified.
|
## soon as all pending operations will be notified.
|
||||||
## You can execute ``aftercb`` before actual socket close operation.
|
|
||||||
let loop = getThreadDispatcher()
|
let loop = getThreadDispatcher()
|
||||||
|
|
||||||
proc continuation(udata: pointer) =
|
proc continuation(udata: pointer) =
|
||||||
|
@ -1168,41 +1249,44 @@ proc callIdle*(cbproc: CallbackFunc) =
|
||||||
|
|
||||||
include asyncfutures2
|
include asyncfutures2
|
||||||
|
|
||||||
when not(defined(windows)):
|
|
||||||
when asyncEventEngine in ["epoll", "kqueue"]:
|
|
||||||
proc waitSignal*(signal: int): Future[void] {.raises: [Defect].} =
|
|
||||||
var retFuture = newFuture[void]("chronos.waitSignal()")
|
|
||||||
var signalHandle: Opt[SignalHandle]
|
|
||||||
|
|
||||||
template getSignalException(e: OSErrorCode): untyped =
|
when defined(macosx) or defined(macos) or defined(freebsd) or
|
||||||
newException(AsyncError, "Could not manipulate signal handler, " &
|
defined(netbsd) or defined(openbsd) or defined(dragonfly) or
|
||||||
"reason [" & $int(e) & "]: " & osErrorMsg(e))
|
defined(linux) or defined(windows):
|
||||||
|
|
||||||
proc continuation(udata: pointer) {.gcsafe.} =
|
proc waitSignal*(signal: int): Future[void] {.raises: [Defect].} =
|
||||||
if not(retFuture.finished()):
|
var retFuture = newFuture[void]("chronos.waitSignal()")
|
||||||
if signalHandle.isSome():
|
var signalHandle: Opt[SignalHandle]
|
||||||
let res = removeSignal2(signalHandle.get())
|
|
||||||
if res.isErr():
|
|
||||||
retFuture.fail(getSignalException(res.error()))
|
|
||||||
else:
|
|
||||||
retFuture.complete()
|
|
||||||
|
|
||||||
proc cancellation(udata: pointer) {.gcsafe.} =
|
template getSignalException(e: OSErrorCode): untyped =
|
||||||
if not(retFuture.finished()):
|
newException(AsyncError, "Could not manipulate signal handler, " &
|
||||||
if signalHandle.isSome():
|
"reason [" & $int(e) & "]: " & osErrorMsg(e))
|
||||||
let res = removeSignal2(signalHandle.get())
|
|
||||||
if res.isErr():
|
|
||||||
retFuture.fail(getSignalException(res.error()))
|
|
||||||
|
|
||||||
signalHandle =
|
proc continuation(udata: pointer) {.gcsafe.} =
|
||||||
block:
|
if not(retFuture.finished()):
|
||||||
let res = addSignal2(signal, continuation)
|
if signalHandle.isSome():
|
||||||
|
let res = removeSignal2(signalHandle.get())
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
retFuture.fail(getSignalException(res.error()))
|
retFuture.fail(getSignalException(res.error()))
|
||||||
Opt.some(res.get())
|
else:
|
||||||
|
retFuture.complete()
|
||||||
|
|
||||||
retFuture.cancelCallback = cancellation
|
proc cancellation(udata: pointer) {.gcsafe.} =
|
||||||
retFuture
|
if not(retFuture.finished()):
|
||||||
|
if signalHandle.isSome():
|
||||||
|
let res = removeSignal2(signalHandle.get())
|
||||||
|
if res.isErr():
|
||||||
|
retFuture.fail(getSignalException(res.error()))
|
||||||
|
|
||||||
|
signalHandle =
|
||||||
|
block:
|
||||||
|
let res = addSignal2(signal, continuation)
|
||||||
|
if res.isErr():
|
||||||
|
retFuture.fail(getSignalException(res.error()))
|
||||||
|
Opt.some(res.get())
|
||||||
|
|
||||||
|
retFuture.cancelCallback = cancellation
|
||||||
|
retFuture
|
||||||
|
|
||||||
proc sleepAsync*(duration: Duration): Future[void] =
|
proc sleepAsync*(duration: Duration): Future[void] =
|
||||||
## Suspends the execution of the current async procedure for the next
|
## Suspends the execution of the current async procedure for the next
|
||||||
|
|
|
@ -15,7 +15,7 @@ else:
|
||||||
import "."/[asyncloop, osdefs, osutils]
|
import "."/[asyncloop, osdefs, osutils]
|
||||||
import stew/results
|
import stew/results
|
||||||
from nativesockets import Domain, Protocol, SockType, toInt
|
from nativesockets import Domain, Protocol, SockType, toInt
|
||||||
export Domain, Protocol, SockType, results
|
export Domain, Protocol, SockType, results, osutils
|
||||||
|
|
||||||
const
|
const
|
||||||
asyncInvalidSocket* = AsyncFD(osdefs.INVALID_SOCKET)
|
asyncInvalidSocket* = AsyncFD(osdefs.INVALID_SOCKET)
|
||||||
|
|
|
@ -61,6 +61,12 @@ when defined(windows):
|
||||||
INADDR_BROADCAST* = 0xffff_ffff'u32
|
INADDR_BROADCAST* = 0xffff_ffff'u32
|
||||||
INADDR_NONE* = 0xffff_ffff'u32
|
INADDR_NONE* = 0xffff_ffff'u32
|
||||||
|
|
||||||
|
CTRL_C_EVENT* = 0'u32
|
||||||
|
CTRL_BREAK_EVENT* = 1'u32
|
||||||
|
CTRL_CLOSE_EVENT* = 2'u32
|
||||||
|
CTRL_LOGOFF_EVENT* = 5'u32
|
||||||
|
CTRL_SHUTDOWN_EVENT* = 6'u32
|
||||||
|
|
||||||
WAIT_ABANDONED* = 0x80'u32
|
WAIT_ABANDONED* = 0x80'u32
|
||||||
WAIT_OBJECT_0* = 0x00'u32
|
WAIT_OBJECT_0* = 0x00'u32
|
||||||
WAIT_TIMEOUT* = 0x102'u32
|
WAIT_TIMEOUT* = 0x102'u32
|
||||||
|
@ -299,7 +305,10 @@ when defined(windows):
|
||||||
|
|
||||||
POVERLAPPED_COMPLETION_ROUTINE* = proc (para1: DWORD, para2: DWORD,
|
POVERLAPPED_COMPLETION_ROUTINE* = proc (para1: DWORD, para2: DWORD,
|
||||||
para3: POVERLAPPED) {.
|
para3: POVERLAPPED) {.
|
||||||
stdcall, gcsafe, raises: [].}
|
stdcall, gcsafe, raises: [].}
|
||||||
|
|
||||||
|
PHANDLER_ROUTINE* = proc (dwCtrlType: DWORD): WINBOOL {.
|
||||||
|
stdcall, gcsafe, raises: [Defect].}
|
||||||
|
|
||||||
OSVERSIONINFO* {.final, pure.} = object
|
OSVERSIONINFO* {.final, pure.} = object
|
||||||
dwOSVersionInfoSize*: DWORD
|
dwOSVersionInfoSize*: DWORD
|
||||||
|
@ -494,6 +503,8 @@ when defined(windows):
|
||||||
dwMilliseconds: DWORD, fAlertable: WINBOOL): WINBOOL {.
|
dwMilliseconds: DWORD, fAlertable: WINBOOL): WINBOOL {.
|
||||||
stdcall, gcsafe, raises: [].}
|
stdcall, gcsafe, raises: [].}
|
||||||
|
|
||||||
|
WindowsSigHandler = proc (a: cint) {.noconv, raises: [], gcsafe.}
|
||||||
|
|
||||||
proc getVersionEx*(lpVersionInfo: ptr OSVERSIONINFO): WINBOOL {.
|
proc getVersionEx*(lpVersionInfo: ptr OSVERSIONINFO): WINBOOL {.
|
||||||
stdcall, dynlib: "kernel32", importc: "GetVersionExW", sideEffect.}
|
stdcall, dynlib: "kernel32", importc: "GetVersionExW", sideEffect.}
|
||||||
|
|
||||||
|
@ -593,6 +604,9 @@ when defined(windows):
|
||||||
proc getCurrentProcess*(): HANDLE {.
|
proc getCurrentProcess*(): HANDLE {.
|
||||||
stdcall, dynlib: "kernel32", importc: "GetCurrentProcess", sideEffect.}
|
stdcall, dynlib: "kernel32", importc: "GetCurrentProcess", sideEffect.}
|
||||||
|
|
||||||
|
proc getCurrentProcessId*(): DWORD {.
|
||||||
|
stdcall, dynlib: "kernel32", importc: "GetCurrentProcessId", sideEffect.}
|
||||||
|
|
||||||
proc getSystemTimeAsFileTime*(lpSystemTimeAsFileTime: var FILETIME) {.
|
proc getSystemTimeAsFileTime*(lpSystemTimeAsFileTime: var FILETIME) {.
|
||||||
stdcall, dynlib: "kernel32", importc: "GetSystemTimeAsFileTime",
|
stdcall, dynlib: "kernel32", importc: "GetSystemTimeAsFileTime",
|
||||||
sideEffect.}
|
sideEffect.}
|
||||||
|
@ -710,7 +724,7 @@ when defined(windows):
|
||||||
|
|
||||||
proc createEvent*(lpEventAttributes: ptr SECURITY_ATTRIBUTES,
|
proc createEvent*(lpEventAttributes: ptr SECURITY_ATTRIBUTES,
|
||||||
bManualReset: DWORD, bInitialState: DWORD,
|
bManualReset: DWORD, bInitialState: DWORD,
|
||||||
lpName: LPWSTR): HANDLE {.
|
lpName: ptr WCHAR): HANDLE {.
|
||||||
stdcall, dynlib: "kernel32", importc: "CreateEventW", sideEffect.}
|
stdcall, dynlib: "kernel32", importc: "CreateEventW", sideEffect.}
|
||||||
|
|
||||||
proc setEvent*(hEvent: HANDLE): WINBOOL {.
|
proc setEvent*(hEvent: HANDLE): WINBOOL {.
|
||||||
|
@ -811,9 +825,37 @@ when defined(windows):
|
||||||
proc rtlNtStatusToDosError*(code: uint64): ULONG {.
|
proc rtlNtStatusToDosError*(code: uint64): ULONG {.
|
||||||
stdcall, dynlib: "ntdll", importc: "RtlNtStatusToDosError", sideEffect.}
|
stdcall, dynlib: "ntdll", importc: "RtlNtStatusToDosError", sideEffect.}
|
||||||
|
|
||||||
|
proc getConsoleCP*(): UINT {.
|
||||||
|
stdcall, dynlib: "kernel32", importc: "GetConsoleCP", sideEffect.}
|
||||||
|
|
||||||
|
proc setConsoleCtrlHandler*(handleRoutine: PHANDLER_ROUTINE,
|
||||||
|
add: WINBOOL): WINBOOL {.
|
||||||
|
stdcall, dynlib: "kernel32", importc: "SetConsoleCtrlHandler",
|
||||||
|
sideEffect.}
|
||||||
|
|
||||||
|
proc generateConsoleCtrlEvent*(dwCtrlEvent: DWORD,
|
||||||
|
dwProcessGroupId: DWORD): WINBOOL {.
|
||||||
|
stdcall, dynlib: "kernel32", importc: "GenerateConsoleCtrlEvent",
|
||||||
|
sideEffect.}
|
||||||
|
|
||||||
proc `==`*(x, y: SocketHandle): bool {.borrow.}
|
proc `==`*(x, y: SocketHandle): bool {.borrow.}
|
||||||
proc `==`*(x, y: HANDLE): bool {.borrow.}
|
proc `==`*(x, y: HANDLE): bool {.borrow.}
|
||||||
|
|
||||||
|
proc c_signal*(sign: cint, handler: WindowsSigHandler): WindowsSigHandler {.
|
||||||
|
importc: "signal", header: "<signal.h>", raises: [], sideEffect.}
|
||||||
|
|
||||||
|
const
|
||||||
|
SIGABRT* = cint(22)
|
||||||
|
SIGINT* = cint(2)
|
||||||
|
SIGQUIT* = cint(3)
|
||||||
|
SIGTERM* = cint(15)
|
||||||
|
SIGFPE* = cint(8)
|
||||||
|
SIGILL* = cint(4)
|
||||||
|
SIGSEGV* = cint(11)
|
||||||
|
SIG_DFL* = cast[WindowsSigHandler](0)
|
||||||
|
SIG_IGN* = cast[WindowsSigHandler](1)
|
||||||
|
SIG_ERR* = cast[WindowsSigHandler](-1)
|
||||||
|
|
||||||
proc getSecurityAttributes*(inheritHandle = false): SECURITY_ATTRIBUTES =
|
proc getSecurityAttributes*(inheritHandle = false): SECURITY_ATTRIBUTES =
|
||||||
SECURITY_ATTRIBUTES(
|
SECURITY_ATTRIBUTES(
|
||||||
nLength: DWORD(sizeof(SECURITY_ATTRIBUTES)),
|
nLength: DWORD(sizeof(SECURITY_ATTRIBUTES)),
|
||||||
|
|
|
@ -1315,6 +1315,8 @@ elif defined(windows):
|
||||||
ERROR_FILE_NOT_FOUND* = OSErrorCode(2)
|
ERROR_FILE_NOT_FOUND* = OSErrorCode(2)
|
||||||
ERROR_TOO_MANY_OPEN_FILES* = OSErrorCode(4)
|
ERROR_TOO_MANY_OPEN_FILES* = OSErrorCode(4)
|
||||||
ERROR_ACCESS_DENIED* = OSErrorCode(5)
|
ERROR_ACCESS_DENIED* = OSErrorCode(5)
|
||||||
|
ERROR_ALREADY_EXISTS* = OSErrorCode(183)
|
||||||
|
ERROR_NOT_SUPPORTED* = OSErrorCode(50)
|
||||||
ERROR_BROKEN_PIPE* = OSErrorCode(109)
|
ERROR_BROKEN_PIPE* = OSErrorCode(109)
|
||||||
ERROR_BUFFER_OVERFLOW* = OSErrorCode(111)
|
ERROR_BUFFER_OVERFLOW* = OSErrorCode(111)
|
||||||
ERROR_PIPE_BUSY* = OSErrorCode(231)
|
ERROR_PIPE_BUSY* = OSErrorCode(231)
|
||||||
|
|
|
@ -18,7 +18,12 @@ else:
|
||||||
|
|
||||||
when defined(windows) or defined(nimdoc):
|
when defined(windows) or defined(nimdoc):
|
||||||
import stew/base10
|
import stew/base10
|
||||||
const PipeHeaderName* = r"\\.\pipe\LOCAL\chronos\"
|
const
|
||||||
|
PipeHeaderName* = r"\\.\pipe\LOCAL\chronos\"
|
||||||
|
SignalPrefixName* = cstring(r"Local\chronos-events-")
|
||||||
|
MaxSignalEventLength* = 64
|
||||||
|
MaxSignalSuffixLength* = MaxSignalEventLength -
|
||||||
|
(len(SignalPrefixName) + Base10.maxLen(uint64) + 2)
|
||||||
|
|
||||||
type
|
type
|
||||||
DescriptorFlag* {.pure.} = enum
|
DescriptorFlag* {.pure.} = enum
|
||||||
|
@ -74,6 +79,26 @@ when defined(windows):
|
||||||
proc closeFd*(s: HANDLE): int =
|
proc closeFd*(s: HANDLE): int =
|
||||||
if osdefs.closeHandle(s) == TRUE: 0 else: -1
|
if osdefs.closeHandle(s) == TRUE: 0 else: -1
|
||||||
|
|
||||||
|
proc toWideBuffer*(s: openArray[char],
|
||||||
|
d: var openArray[WCHAR]): Result[int, OSErrorCode] =
|
||||||
|
if len(s) == 0: return ok(0)
|
||||||
|
let res = multiByteToWideChar(CP_UTF8, 0'u32, unsafeAddr s[0], cint(-1),
|
||||||
|
addr d[0], cint(len(d)))
|
||||||
|
if res == 0:
|
||||||
|
err(osLastError())
|
||||||
|
else:
|
||||||
|
ok(res)
|
||||||
|
|
||||||
|
proc toMultibyteBuffer*(s: openArray[WCHAR],
|
||||||
|
d: var openArray[char]): Result[int, OSErrorCode] =
|
||||||
|
if len(s) == 0: return ok(0)
|
||||||
|
let res = wideCharToMultiByte(CP_UTF8, 0'u32, unsafeAddr s[0], cint(-1),
|
||||||
|
addr d[0], cint(len(d)), nil, nil)
|
||||||
|
if res == 0:
|
||||||
|
err(osLastError())
|
||||||
|
else:
|
||||||
|
ok(res)
|
||||||
|
|
||||||
proc toWideString*(s: string): Result[LPWSTR, OSErrorCode] =
|
proc toWideString*(s: string): Result[LPWSTR, OSErrorCode] =
|
||||||
if len(s) == 0:
|
if len(s) == 0:
|
||||||
ok(cast[LPWSTR](alloc0(sizeof(WCHAR))))
|
ok(cast[LPWSTR](alloc0(sizeof(WCHAR))))
|
||||||
|
@ -209,6 +234,96 @@ when defined(windows):
|
||||||
return err(errorCode)
|
return err(errorCode)
|
||||||
ok((read: pipeIn, write: pipeOut))
|
ok((read: pipeIn, write: pipeOut))
|
||||||
|
|
||||||
|
proc getSignalName*(signal: int): cstring =
|
||||||
|
## Convert Windows SIGNAL identifier to string representation.
|
||||||
|
##
|
||||||
|
## This procedure supports only SIGINT, SIGTERM and SIGQUIT values.
|
||||||
|
case signal
|
||||||
|
of SIGINT: cstring("sigint")
|
||||||
|
of SIGTERM: cstring("sigterm")
|
||||||
|
of SIGQUIT: cstring("sigquit")
|
||||||
|
else:
|
||||||
|
raiseAssert "Signal is not supported"
|
||||||
|
|
||||||
|
proc getEventPath*(suffix: cstring): array[MaxSignalEventLength, WCHAR] =
|
||||||
|
## Create Windows' Event object name suffixed by ``suffix``. This name
|
||||||
|
## is create in local session namespace with name like this:
|
||||||
|
## ``Local\chronos-events-<process id>-<suffix>``.
|
||||||
|
##
|
||||||
|
## This procedure is GC-free, so it could be used in other threads.
|
||||||
|
doAssert(len(suffix) < MaxSignalSuffixLength)
|
||||||
|
var
|
||||||
|
resMc: array[MaxSignalEventLength, char]
|
||||||
|
resWc: array[MaxSignalEventLength, WCHAR]
|
||||||
|
|
||||||
|
var offset = 0
|
||||||
|
let
|
||||||
|
pid = osdefs.getCurrentProcessId()
|
||||||
|
pid10 = Base10.toBytes(uint64(pid))
|
||||||
|
copyMem(addr resMc[offset], SignalPrefixName, len(SignalPrefixName))
|
||||||
|
offset += len(SignalPrefixName)
|
||||||
|
copyMem(addr resMc[offset], unsafeAddr pid10.data[0], pid10.len)
|
||||||
|
offset += pid10.len
|
||||||
|
resMc[offset] = '-'
|
||||||
|
offset += 1
|
||||||
|
copyMem(addr resMc[offset], suffix, len(suffix))
|
||||||
|
offset += len(suffix)
|
||||||
|
resMc[offset] = '\x00'
|
||||||
|
let res = toWideBuffer(resMc, resWc)
|
||||||
|
if res.isErr():
|
||||||
|
raiseAssert "Invalid suffix value, got " & osErrorMsg(res.error())
|
||||||
|
resWc
|
||||||
|
|
||||||
|
proc raiseEvent(suffix: cstring): Result[bool, OSErrorCode] =
|
||||||
|
var sa = getSecurityAttributes()
|
||||||
|
let
|
||||||
|
eventName = getEventPath(suffix)
|
||||||
|
# We going to fire event, so we can try to create it already signaled.
|
||||||
|
event = createEvent(addr sa, FALSE, TRUE, unsafeAddr eventName[0])
|
||||||
|
errorCode = osLastError()
|
||||||
|
|
||||||
|
if event == HANDLE(0):
|
||||||
|
err(errorCode)
|
||||||
|
else:
|
||||||
|
if errorCode == ERROR_ALREADY_EXISTS:
|
||||||
|
let res = setEvent(event)
|
||||||
|
if res == FALSE:
|
||||||
|
err(osLastError())
|
||||||
|
else:
|
||||||
|
ok(true)
|
||||||
|
else:
|
||||||
|
ok(false)
|
||||||
|
|
||||||
|
proc raiseSignal*(signal: cint): Result[bool, OSErrorCode] =
|
||||||
|
## This is helper procedure which could help to raise Unix signals in
|
||||||
|
## Windows GUI / Service application. Console applications are handled
|
||||||
|
## automatically.
|
||||||
|
##
|
||||||
|
## This procedure does not use Nim's GC, so it can be placed in any handler
|
||||||
|
## of your application even in code which is running in different thread.
|
||||||
|
raiseEvent(getSignalName(signal))
|
||||||
|
|
||||||
|
proc raiseConsoleCtrlSignal*(groupId = 0'u32): Result[void, OSErrorCode] =
|
||||||
|
## Raise CTRL+C event in current console.
|
||||||
|
if generateConsoleCtrlEvent(CTRL_C_EVENT, groupId) == FALSE:
|
||||||
|
err(osLastError())
|
||||||
|
else:
|
||||||
|
ok()
|
||||||
|
|
||||||
|
proc openEvent*(suffix: string): Result[HANDLE, OSErrorCode] =
|
||||||
|
## Open or create Windows event object with suffix ``suffix``.
|
||||||
|
var sa = getSecurityAttributes()
|
||||||
|
let
|
||||||
|
# We going to wait for created event, so we don't need to create it in
|
||||||
|
# signaled state.
|
||||||
|
eventName = getEventPath(suffix)
|
||||||
|
event = createEvent(addr sa, FALSE, FALSE, unsafeAddr eventName[0])
|
||||||
|
if event == HANDLE(0):
|
||||||
|
let errorCode = osLastError()
|
||||||
|
err(errorCode)
|
||||||
|
else:
|
||||||
|
ok(event)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
template handleEintr*(body: untyped): untyped =
|
template handleEintr*(body: untyped): untyped =
|
||||||
|
|
|
@ -1896,7 +1896,7 @@ proc close*(server: StreamServer) =
|
||||||
proc closeWait*(server: StreamServer): Future[void] =
|
proc closeWait*(server: StreamServer): Future[void] =
|
||||||
## Close server ``server`` and release all resources.
|
## Close server ``server`` and release all resources.
|
||||||
server.close()
|
server.close()
|
||||||
result = server.join()
|
server.join()
|
||||||
|
|
||||||
proc createStreamServer*(host: TransportAddress,
|
proc createStreamServer*(host: TransportAddress,
|
||||||
cbproc: StreamCallback,
|
cbproc: StreamCallback,
|
||||||
|
@ -2055,7 +2055,7 @@ proc createStreamServer*(host: TransportAddress,
|
||||||
addr slen) != 0:
|
addr slen) != 0:
|
||||||
let err = osLastError()
|
let err = osLastError()
|
||||||
if sock == asyncInvalidSocket:
|
if sock == asyncInvalidSocket:
|
||||||
serverSocket.closeSocket()
|
discard unregisterAndCloseFd(serverSocket)
|
||||||
raiseTransportOsError(err)
|
raiseTransportOsError(err)
|
||||||
fromSAddr(addr saddr, slen, localAddress)
|
fromSAddr(addr saddr, slen, localAddress)
|
||||||
|
|
||||||
|
@ -2150,7 +2150,7 @@ proc createStreamServer*[T](host: TransportAddress,
|
||||||
|
|
||||||
proc getUserData*[T](server: StreamServer): T {.inline.} =
|
proc getUserData*[T](server: StreamServer): T {.inline.} =
|
||||||
## Obtain user data stored in ``server`` object.
|
## Obtain user data stored in ``server`` object.
|
||||||
result = cast[T](server.udata)
|
cast[T](server.udata)
|
||||||
|
|
||||||
template fastWrite(transp: auto, pbytes: var ptr byte, rbytes: var int,
|
template fastWrite(transp: auto, pbytes: var ptr byte, rbytes: var int,
|
||||||
nbytes: int) =
|
nbytes: int) =
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||||
# MIT license (LICENSE-MIT)
|
# MIT license (LICENSE-MIT)
|
||||||
import unittest2
|
import unittest2
|
||||||
import ../chronos
|
import ../chronos, ../chronos/oserrno
|
||||||
|
|
||||||
{.used.}
|
{.used.}
|
||||||
|
|
||||||
|
@ -14,57 +14,76 @@ when not defined(windows):
|
||||||
import posix
|
import posix
|
||||||
|
|
||||||
suite "Signal handling test suite":
|
suite "Signal handling test suite":
|
||||||
when not defined(windows):
|
proc testSignal(signal, value: int): Future[bool] {.async.} =
|
||||||
var
|
var
|
||||||
signalCounter = 0
|
signalCounter = 0
|
||||||
sigfd: SignalHandle
|
sigFd: SignalHandle
|
||||||
|
handlerFut = newFuture[void]("signal.handler")
|
||||||
|
|
||||||
proc signalProc(udata: pointer) =
|
proc signalHandler(udata: pointer) {.gcsafe.} =
|
||||||
signalCounter = cast[int](udata)
|
signalCounter = cast[int](udata)
|
||||||
try:
|
let res = removeSignal2(sigFd)
|
||||||
removeSignal(sigfd)
|
if res.isErr():
|
||||||
except Exception as exc:
|
handlerFut.fail(newException(ValueError, osErrorMsg(res.error())))
|
||||||
raiseAssert exc.msg
|
else:
|
||||||
|
handlerFut.complete()
|
||||||
|
|
||||||
proc asyncProc() {.async.} =
|
sigFd =
|
||||||
await sleepAsync(500.milliseconds)
|
block:
|
||||||
|
let res = addSignal2(signal, signalHandler, cast[pointer](value))
|
||||||
|
if res.isErr():
|
||||||
|
raiseAssert osErrorMsg(res.error())
|
||||||
|
res.get()
|
||||||
|
|
||||||
proc test(signal, value: int): bool =
|
when defined(windows):
|
||||||
try:
|
discard raiseSignal(cint(signal))
|
||||||
sigfd = addSignal(signal, signalProc, cast[pointer](value))
|
else:
|
||||||
except Exception as exc:
|
|
||||||
raiseAssert exc.msg
|
|
||||||
var fut = asyncProc()
|
|
||||||
discard posix.kill(posix.getpid(), cint(signal))
|
discard posix.kill(posix.getpid(), cint(signal))
|
||||||
waitFor(fut)
|
|
||||||
signalCounter == value
|
|
||||||
|
|
||||||
proc testWait(signal: int): bool =
|
await handlerFut.wait(5.seconds)
|
||||||
var fut = waitSignal(signal)
|
return signalCounter == value
|
||||||
|
|
||||||
|
proc testWait(signal: int): Future[bool] {.async.} =
|
||||||
|
var fut = waitSignal(signal)
|
||||||
|
when defined(windows):
|
||||||
|
discard raiseSignal(cint(signal))
|
||||||
|
else:
|
||||||
discard posix.kill(posix.getpid(), cint(signal))
|
discard posix.kill(posix.getpid(), cint(signal))
|
||||||
waitFor(fut)
|
await fut.wait(5.seconds)
|
||||||
true
|
return true
|
||||||
|
|
||||||
|
when defined(windows):
|
||||||
|
proc testCtrlC(): Future[bool] {.async, used.} =
|
||||||
|
var fut = waitSignal(SIGINT)
|
||||||
|
let res = raiseConsoleCtrlSignal()
|
||||||
|
if res.isErr():
|
||||||
|
raiseAssert osErrorMsg(res.error())
|
||||||
|
await fut.wait(5.seconds)
|
||||||
|
return true
|
||||||
|
|
||||||
test "SIGINT test":
|
test "SIGINT test":
|
||||||
when not defined(windows):
|
let res = waitFor testSignal(SIGINT, 31337)
|
||||||
check test(SIGINT, 31337) == true
|
check res == true
|
||||||
else:
|
|
||||||
skip()
|
|
||||||
|
|
||||||
test "SIGTERM test":
|
test "SIGTERM test":
|
||||||
when defined(windows):
|
let res = waitFor testSignal(SIGTERM, 65537)
|
||||||
skip()
|
check res == true
|
||||||
else:
|
|
||||||
check test(SIGTERM, 65537) == true
|
|
||||||
|
|
||||||
test "waitSignal(SIGINT) test":
|
test "waitSignal(SIGINT) test":
|
||||||
when defined(windows):
|
let res = waitFor testWait(SIGINT)
|
||||||
skip()
|
check res == true
|
||||||
else:
|
|
||||||
check testWait(SIGINT) == true
|
|
||||||
|
|
||||||
test "waitSignal(SIGTERM) test":
|
test "waitSignal(SIGTERM) test":
|
||||||
when defined(windows):
|
let res = waitFor testWait(SIGTERM)
|
||||||
skip()
|
check res == true
|
||||||
else:
|
|
||||||
check testWait(SIGTERM) == true
|
# This test doesn't work well in test suite, because it generates CTRL+C
|
||||||
|
# event in Windows console, parent process receives this signal and stops
|
||||||
|
# test suite execution.
|
||||||
|
|
||||||
|
# test "Windows [CTRL+C] test":
|
||||||
|
# when defined(windows):
|
||||||
|
# let res = waitFor testCtrlC()
|
||||||
|
# check res == true
|
||||||
|
# else:
|
||||||
|
# skip()
|
||||||
|
|
Loading…
Reference in New Issue