From 897b64fb7dcc4f39df8f8c5f71e7f40285a1d749 Mon Sep 17 00:00:00 2001 From: Ivan Folgueira Bande Date: Fri, 16 Jan 2026 15:45:57 +0100 Subject: [PATCH 1/4] add shutdown API proc --- chronos/internal/asyncengine.nim | 23 ++++++++++++++++++++++- chronos/internal/asyncfutures.nim | 11 +++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/chronos/internal/asyncengine.nim b/chronos/internal/asyncengine.nim index 45197a038..01e7097b2 100644 --- a/chronos/internal/asyncengine.nim +++ b/chronos/internal/asyncengine.nim @@ -185,7 +185,7 @@ when defined(nimdoc): ## Perform single asynchronous step, processing timers and completing ## tasks. Blocks until at least one event has completed. ## - ## Exceptions raised during `async` task exection are stored as outcome + ## Exceptions raised during `async` task exception are stored as outcome ## in the corresponding `Future` - `poll` itself does not raise. proc register2*(fd: AsyncFD): Result[void, OSErrorCode] = discard @@ -697,6 +697,19 @@ elif defined(windows): if not(isNil(aftercb)): loop.callbacks.addLast(AsyncCallback(function: aftercb, udata: param)) + proc safeCloseHandle(h: HANDLE): Result[void, string] = + let res = closeHandle(h) + if res == 0: # WINBOOL FALSE + return err("Failed to close handle error code: " & osErrorMsg(osLastError())) + ok() + + proc closeDispatcher*(loop: PDispatcher): Result[void, string] = + ? safeCloseHandle(loop.ioPort) + for i in loop.handles.items: + closeHandle(i) + loop.handles.clear() + ok() + proc unregisterAndCloseFd*(fd: AsyncFD): Result[void, OSErrorCode] = ## Unregister from system queue and close asynchronous socket. ## @@ -936,6 +949,14 @@ elif defined(macosx) or defined(freebsd) or defined(netbsd) or ## You can execute ``aftercb`` before actual socket close operation. closeSocket(fd, aftercb) + proc closeDispatcher*(loop: PDispatcher): Result[void, string] = + ## Close selector associated with current thread's dispatcher. + try: + loop.selector.close() + except IOSelectorsException as e: + return err("Exception in closeDispatcher: " & e.msg) + ok() + when chronosEventEngine in ["epoll", "kqueue"]: type ProcessHandle* = distinct int diff --git a/chronos/internal/asyncfutures.nim b/chronos/internal/asyncfutures.nim index 51622f14d..07771af3e 100644 --- a/chronos/internal/asyncfutures.nim +++ b/chronos/internal/asyncfutures.nim @@ -641,6 +641,17 @@ proc pollFor[F: Future | InternalRaisesFuture](fut: F): F {.raises: [].} = fut +proc shutdown*(): Result[void, string] {.raises: [].} = + ## Continues polling the dispatcher until shutdown completion, then + ## performs final cleanup of all dispatcher resources. + ## Notice that this should be called only when sure that no new async tasks will be scheduled. + ## + ## This routine shall be called only after `pollFor` has completed. Upon + ## invocation, all streams are assumed to have been closed. + + let disp = getThreadDispatcher() + ? disp.closeDispatcher() + proc waitFor*[T: not void](fut: Future[T]): lent T {.raises: [CatchableError].} = ## Blocks the current thread of execution until `fut` has finished, returning ## its value. From d682931cb4cceea53b999d57c31498d95e19d631 Mon Sep 17 00:00:00 2001 From: Ivan Folgueira Bande Date: Tue, 20 Jan 2026 15:20:25 +0100 Subject: [PATCH 2/4] add missing ok() at the end of shutdown if no err occurs --- chronos/internal/asyncfutures.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/chronos/internal/asyncfutures.nim b/chronos/internal/asyncfutures.nim index 07771af3e..a4265bf2a 100644 --- a/chronos/internal/asyncfutures.nim +++ b/chronos/internal/asyncfutures.nim @@ -651,6 +651,7 @@ proc shutdown*(): Result[void, string] {.raises: [].} = let disp = getThreadDispatcher() ? disp.closeDispatcher() + ok() proc waitFor*[T: not void](fut: Future[T]): lent T {.raises: [CatchableError].} = ## Blocks the current thread of execution until `fut` has finished, returning From 1343e457e1dad33dc40352b0d993e5c293dd4233 Mon Sep 17 00:00:00 2001 From: Ivan Folgueira Bande Date: Wed, 21 Jan 2026 18:45:11 +0100 Subject: [PATCH 3/4] Enhance shutdown proc comment --- chronos/internal/asyncfutures.nim | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/chronos/internal/asyncfutures.nim b/chronos/internal/asyncfutures.nim index a4265bf2a..6ec1aa98b 100644 --- a/chronos/internal/asyncfutures.nim +++ b/chronos/internal/asyncfutures.nim @@ -642,8 +642,7 @@ proc pollFor[F: Future | InternalRaisesFuture](fut: F): F {.raises: [].} = fut proc shutdown*(): Result[void, string] {.raises: [].} = - ## Continues polling the dispatcher until shutdown completion, then - ## performs final cleanup of all dispatcher resources. + ## Performs the shutdown and cleanup of all dispatcher resources. ## Notice that this should be called only when sure that no new async tasks will be scheduled. ## ## This routine shall be called only after `pollFor` has completed. Upon From b78c4b62fc820b7d90baf5a6b0d3812b4a69947f Mon Sep 17 00:00:00 2001 From: Ivan Folgueira Bande Date: Wed, 21 Jan 2026 19:14:20 +0100 Subject: [PATCH 4/4] fix compilation issue ioselectors_poll close2 pollfds.setLen(0) --- chronos/ioselects/ioselectors_poll.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chronos/ioselects/ioselectors_poll.nim b/chronos/ioselects/ioselectors_poll.nim index 51f21bbfd..1f6a3eb9b 100644 --- a/chronos/ioselects/ioselectors_poll.nim +++ b/chronos/ioselects/ioselectors_poll.nim @@ -56,7 +56,7 @@ proc new*(t: typedesc[Selector], T: typedesc): SelectResult[Selector[T]] = proc close2*[T](s: Selector[T]): SelectResult[void] = s.fds.clear() - s.pollfds.clear() + s.pollfds.setLen(0) proc new*(t: typedesc[SelectEvent]): SelectResult[SelectEvent] = let flags = {DescriptorFlag.NonBlock, DescriptorFlag.CloseOnExec}