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):
|
||||
{.push raises: [Defect].}
|
||||
{.pragma: callbackFunc, stdcall, gcsafe, raises: [Defect].}
|
||||
else:
|
||||
{.push raises: [].}
|
||||
{.pragma: callbackFunc, stdcall, gcsafe, raises: [].}
|
||||
|
||||
from nativesockets import Port
|
||||
import std/[tables, strutils, heapqueue, deques]
|
||||
|
@ -289,6 +291,7 @@ func toException*(v: OSErrorCode): ref OSError = newOSError(v)
|
|||
# Result[T, OSErrorCode] values.
|
||||
|
||||
when defined(windows):
|
||||
export SIGINT, SIGQUIT, SIGTERM
|
||||
type
|
||||
CompletionKey = ULONG_PTR
|
||||
|
||||
|
@ -301,11 +304,8 @@ when defined(windows):
|
|||
CustomOverlapped* = object of OVERLAPPED
|
||||
data*: CompletionData
|
||||
|
||||
OVERLAPPED_ENTRY* = object
|
||||
lpCompletionKey*: ULONG_PTR
|
||||
lpOverlapped*: ptr CustomOverlapped
|
||||
internal: ULONG_PTR
|
||||
dwNumberOfBytesTransferred: DWORD
|
||||
DispatcherFlag* = enum
|
||||
SignalHandlerInstalled
|
||||
|
||||
PDispatcher* = ref object of PDispatcherBase
|
||||
ioPort: HANDLE
|
||||
|
@ -315,6 +315,7 @@ when defined(windows):
|
|||
getAcceptExSockAddrs*: WSAPROC_GETACCEPTEXSOCKADDRS
|
||||
transmitFile*: WSAPROC_TRANSMITFILE
|
||||
getQueuedCompletionStatusEx*: LPFN_GETQUEUEDCOMPLETIONSTATUSEX
|
||||
flags: set[DispatcherFlag]
|
||||
|
||||
PtrCustomOverlapped* = ptr CustomOverlapped
|
||||
|
||||
|
@ -330,6 +331,7 @@ when defined(windows):
|
|||
|
||||
WaitableHandle* = ref PostCallbackData
|
||||
ProcessHandle* = distinct WaitableHandle
|
||||
SignalHandle* = distinct WaitableHandle
|
||||
|
||||
WaitableResult* {.pure.} = enum
|
||||
Ok, Timeout
|
||||
|
@ -444,7 +446,7 @@ when defined(windows):
|
|||
|
||||
{.push stackTrace: off.}
|
||||
proc waitableCallback(param: pointer, timerOrWaitFired: WINBOOL) {.
|
||||
stdcall, gcsafe.} =
|
||||
callbackFunc.} =
|
||||
# This procedure will be executed in `wait thread`, so it must not use
|
||||
# GC related objects.
|
||||
# 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``.
|
||||
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*() =
|
||||
## Perform single asynchronous step, processing timers and completing
|
||||
## 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
|
||||
## closing socket, while operation pending, socket will be closed as
|
||||
## soon as all pending operations will be notified.
|
||||
## You can execute ``aftercb`` before actual socket close operation.
|
||||
let loop = getThreadDispatcher()
|
||||
|
||||
proc continuation(udata: pointer) =
|
||||
|
@ -1168,41 +1249,44 @@ proc callIdle*(cbproc: CallbackFunc) =
|
|||
|
||||
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 =
|
||||
newException(AsyncError, "Could not manipulate signal handler, " &
|
||||
"reason [" & $int(e) & "]: " & osErrorMsg(e))
|
||||
when defined(macosx) or defined(macos) or defined(freebsd) or
|
||||
defined(netbsd) or defined(openbsd) or defined(dragonfly) or
|
||||
defined(linux) or defined(windows):
|
||||
|
||||
proc continuation(udata: pointer) {.gcsafe.} =
|
||||
if not(retFuture.finished()):
|
||||
if signalHandle.isSome():
|
||||
let res = removeSignal2(signalHandle.get())
|
||||
if res.isErr():
|
||||
retFuture.fail(getSignalException(res.error()))
|
||||
else:
|
||||
retFuture.complete()
|
||||
proc waitSignal*(signal: int): Future[void] {.raises: [Defect].} =
|
||||
var retFuture = newFuture[void]("chronos.waitSignal()")
|
||||
var signalHandle: Opt[SignalHandle]
|
||||
|
||||
proc cancellation(udata: pointer) {.gcsafe.} =
|
||||
if not(retFuture.finished()):
|
||||
if signalHandle.isSome():
|
||||
let res = removeSignal2(signalHandle.get())
|
||||
if res.isErr():
|
||||
retFuture.fail(getSignalException(res.error()))
|
||||
template getSignalException(e: OSErrorCode): untyped =
|
||||
newException(AsyncError, "Could not manipulate signal handler, " &
|
||||
"reason [" & $int(e) & "]: " & osErrorMsg(e))
|
||||
|
||||
signalHandle =
|
||||
block:
|
||||
let res = addSignal2(signal, continuation)
|
||||
proc continuation(udata: pointer) {.gcsafe.} =
|
||||
if not(retFuture.finished()):
|
||||
if signalHandle.isSome():
|
||||
let res = removeSignal2(signalHandle.get())
|
||||
if res.isErr():
|
||||
retFuture.fail(getSignalException(res.error()))
|
||||
Opt.some(res.get())
|
||||
else:
|
||||
retFuture.complete()
|
||||
|
||||
retFuture.cancelCallback = cancellation
|
||||
retFuture
|
||||
proc cancellation(udata: pointer) {.gcsafe.} =
|
||||
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] =
|
||||
## Suspends the execution of the current async procedure for the next
|
||||
|
|
|
@ -15,7 +15,7 @@ else:
|
|||
import "."/[asyncloop, osdefs, osutils]
|
||||
import stew/results
|
||||
from nativesockets import Domain, Protocol, SockType, toInt
|
||||
export Domain, Protocol, SockType, results
|
||||
export Domain, Protocol, SockType, results, osutils
|
||||
|
||||
const
|
||||
asyncInvalidSocket* = AsyncFD(osdefs.INVALID_SOCKET)
|
||||
|
|
|
@ -61,6 +61,12 @@ when defined(windows):
|
|||
INADDR_BROADCAST* = 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_OBJECT_0* = 0x00'u32
|
||||
WAIT_TIMEOUT* = 0x102'u32
|
||||
|
@ -299,7 +305,10 @@ when defined(windows):
|
|||
|
||||
POVERLAPPED_COMPLETION_ROUTINE* = proc (para1: DWORD, para2: DWORD,
|
||||
para3: POVERLAPPED) {.
|
||||
stdcall, gcsafe, raises: [].}
|
||||
stdcall, gcsafe, raises: [].}
|
||||
|
||||
PHANDLER_ROUTINE* = proc (dwCtrlType: DWORD): WINBOOL {.
|
||||
stdcall, gcsafe, raises: [Defect].}
|
||||
|
||||
OSVERSIONINFO* {.final, pure.} = object
|
||||
dwOSVersionInfoSize*: DWORD
|
||||
|
@ -494,6 +503,8 @@ when defined(windows):
|
|||
dwMilliseconds: DWORD, fAlertable: WINBOOL): WINBOOL {.
|
||||
stdcall, gcsafe, raises: [].}
|
||||
|
||||
WindowsSigHandler = proc (a: cint) {.noconv, raises: [], gcsafe.}
|
||||
|
||||
proc getVersionEx*(lpVersionInfo: ptr OSVERSIONINFO): WINBOOL {.
|
||||
stdcall, dynlib: "kernel32", importc: "GetVersionExW", sideEffect.}
|
||||
|
||||
|
@ -593,6 +604,9 @@ when defined(windows):
|
|||
proc getCurrentProcess*(): HANDLE {.
|
||||
stdcall, dynlib: "kernel32", importc: "GetCurrentProcess", sideEffect.}
|
||||
|
||||
proc getCurrentProcessId*(): DWORD {.
|
||||
stdcall, dynlib: "kernel32", importc: "GetCurrentProcessId", sideEffect.}
|
||||
|
||||
proc getSystemTimeAsFileTime*(lpSystemTimeAsFileTime: var FILETIME) {.
|
||||
stdcall, dynlib: "kernel32", importc: "GetSystemTimeAsFileTime",
|
||||
sideEffect.}
|
||||
|
@ -710,7 +724,7 @@ when defined(windows):
|
|||
|
||||
proc createEvent*(lpEventAttributes: ptr SECURITY_ATTRIBUTES,
|
||||
bManualReset: DWORD, bInitialState: DWORD,
|
||||
lpName: LPWSTR): HANDLE {.
|
||||
lpName: ptr WCHAR): HANDLE {.
|
||||
stdcall, dynlib: "kernel32", importc: "CreateEventW", sideEffect.}
|
||||
|
||||
proc setEvent*(hEvent: HANDLE): WINBOOL {.
|
||||
|
@ -811,9 +825,37 @@ when defined(windows):
|
|||
proc rtlNtStatusToDosError*(code: uint64): ULONG {.
|
||||
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: 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 =
|
||||
SECURITY_ATTRIBUTES(
|
||||
nLength: DWORD(sizeof(SECURITY_ATTRIBUTES)),
|
||||
|
|
|
@ -1315,6 +1315,8 @@ elif defined(windows):
|
|||
ERROR_FILE_NOT_FOUND* = OSErrorCode(2)
|
||||
ERROR_TOO_MANY_OPEN_FILES* = OSErrorCode(4)
|
||||
ERROR_ACCESS_DENIED* = OSErrorCode(5)
|
||||
ERROR_ALREADY_EXISTS* = OSErrorCode(183)
|
||||
ERROR_NOT_SUPPORTED* = OSErrorCode(50)
|
||||
ERROR_BROKEN_PIPE* = OSErrorCode(109)
|
||||
ERROR_BUFFER_OVERFLOW* = OSErrorCode(111)
|
||||
ERROR_PIPE_BUSY* = OSErrorCode(231)
|
||||
|
|
|
@ -18,7 +18,12 @@ else:
|
|||
|
||||
when defined(windows) or defined(nimdoc):
|
||||
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
|
||||
DescriptorFlag* {.pure.} = enum
|
||||
|
@ -74,6 +79,26 @@ when defined(windows):
|
|||
proc closeFd*(s: HANDLE): int =
|
||||
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] =
|
||||
if len(s) == 0:
|
||||
ok(cast[LPWSTR](alloc0(sizeof(WCHAR))))
|
||||
|
@ -209,6 +234,96 @@ when defined(windows):
|
|||
return err(errorCode)
|
||||
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:
|
||||
|
||||
template handleEintr*(body: untyped): untyped =
|
||||
|
|
|
@ -1896,7 +1896,7 @@ proc close*(server: StreamServer) =
|
|||
proc closeWait*(server: StreamServer): Future[void] =
|
||||
## Close server ``server`` and release all resources.
|
||||
server.close()
|
||||
result = server.join()
|
||||
server.join()
|
||||
|
||||
proc createStreamServer*(host: TransportAddress,
|
||||
cbproc: StreamCallback,
|
||||
|
@ -2055,7 +2055,7 @@ proc createStreamServer*(host: TransportAddress,
|
|||
addr slen) != 0:
|
||||
let err = osLastError()
|
||||
if sock == asyncInvalidSocket:
|
||||
serverSocket.closeSocket()
|
||||
discard unregisterAndCloseFd(serverSocket)
|
||||
raiseTransportOsError(err)
|
||||
fromSAddr(addr saddr, slen, localAddress)
|
||||
|
||||
|
@ -2150,7 +2150,7 @@ proc createStreamServer*[T](host: TransportAddress,
|
|||
|
||||
proc getUserData*[T](server: StreamServer): T {.inline.} =
|
||||
## 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,
|
||||
nbytes: int) =
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# MIT license (LICENSE-MIT)
|
||||
import unittest2
|
||||
import ../chronos
|
||||
import ../chronos, ../chronos/oserrno
|
||||
|
||||
{.used.}
|
||||
|
||||
|
@ -14,57 +14,76 @@ when not defined(windows):
|
|||
import posix
|
||||
|
||||
suite "Signal handling test suite":
|
||||
when not defined(windows):
|
||||
proc testSignal(signal, value: int): Future[bool] {.async.} =
|
||||
var
|
||||
signalCounter = 0
|
||||
sigfd: SignalHandle
|
||||
sigFd: SignalHandle
|
||||
handlerFut = newFuture[void]("signal.handler")
|
||||
|
||||
proc signalProc(udata: pointer) =
|
||||
proc signalHandler(udata: pointer) {.gcsafe.} =
|
||||
signalCounter = cast[int](udata)
|
||||
try:
|
||||
removeSignal(sigfd)
|
||||
except Exception as exc:
|
||||
raiseAssert exc.msg
|
||||
let res = removeSignal2(sigFd)
|
||||
if res.isErr():
|
||||
handlerFut.fail(newException(ValueError, osErrorMsg(res.error())))
|
||||
else:
|
||||
handlerFut.complete()
|
||||
|
||||
proc asyncProc() {.async.} =
|
||||
await sleepAsync(500.milliseconds)
|
||||
sigFd =
|
||||
block:
|
||||
let res = addSignal2(signal, signalHandler, cast[pointer](value))
|
||||
if res.isErr():
|
||||
raiseAssert osErrorMsg(res.error())
|
||||
res.get()
|
||||
|
||||
proc test(signal, value: int): bool =
|
||||
try:
|
||||
sigfd = addSignal(signal, signalProc, cast[pointer](value))
|
||||
except Exception as exc:
|
||||
raiseAssert exc.msg
|
||||
var fut = asyncProc()
|
||||
when defined(windows):
|
||||
discard raiseSignal(cint(signal))
|
||||
else:
|
||||
discard posix.kill(posix.getpid(), cint(signal))
|
||||
waitFor(fut)
|
||||
signalCounter == value
|
||||
|
||||
proc testWait(signal: int): bool =
|
||||
var fut = waitSignal(signal)
|
||||
await handlerFut.wait(5.seconds)
|
||||
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))
|
||||
waitFor(fut)
|
||||
true
|
||||
await fut.wait(5.seconds)
|
||||
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":
|
||||
when not defined(windows):
|
||||
check test(SIGINT, 31337) == true
|
||||
else:
|
||||
skip()
|
||||
let res = waitFor testSignal(SIGINT, 31337)
|
||||
check res == true
|
||||
|
||||
test "SIGTERM test":
|
||||
when defined(windows):
|
||||
skip()
|
||||
else:
|
||||
check test(SIGTERM, 65537) == true
|
||||
let res = waitFor testSignal(SIGTERM, 65537)
|
||||
check res == true
|
||||
|
||||
test "waitSignal(SIGINT) test":
|
||||
when defined(windows):
|
||||
skip()
|
||||
else:
|
||||
check testWait(SIGINT) == true
|
||||
let res = waitFor testWait(SIGINT)
|
||||
check res == true
|
||||
|
||||
test "waitSignal(SIGTERM) test":
|
||||
when defined(windows):
|
||||
skip()
|
||||
else:
|
||||
check testWait(SIGTERM) == true
|
||||
let res = waitFor testWait(SIGTERM)
|
||||
check res == 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