|
64 | 64 | ticks*: Deque[AsyncCallback]
|
65 | 65 | trackers*: Table[string, TrackerBase]
|
66 | 66 | counters*: Table[string, TrackerCounter]
|
67 |
| - |
68 |
| -proc sentinelCallbackImpl(arg: pointer) {.gcsafe, noreturn.} = |
69 |
| - raiseAssert "Sentinel callback MUST not be scheduled" |
70 |
| - |
71 |
| -const |
72 |
| - SentinelCallback = AsyncCallback(function: sentinelCallbackImpl, |
73 |
| - udata: nil) |
74 |
| - |
75 |
| -proc isSentinel(acb: AsyncCallback): bool = |
76 |
| - acb == SentinelCallback |
| 67 | + polling*: bool |
| 68 | + ## The event loop is currently running |
77 | 69 |
|
78 | 70 | proc `<`(a, b: TimerCallback): bool =
|
79 | 71 | result = a.finishAt < b.finishAt
|
80 | 72 |
|
| 73 | +template preparePoll(loop: PDispatcherBase) = |
| 74 | + # If you hit this assert, you've called `poll`, `runForever` or `waitFor` |
| 75 | + # from within an async function which is not supported due to the difficulty |
| 76 | + # to control stack depth and event ordering |
| 77 | + # If you're using `waitFor`, switch to `await` and / or propagate the |
| 78 | + # up the call stack. |
| 79 | + doAssert not loop.polling, "The event loop and chronos functions in general are not reentrant" |
| 80 | + |
| 81 | + loop.polling = true |
| 82 | + defer: loop.polling = false |
| 83 | + |
81 | 84 | func getAsyncTimestamp*(a: Duration): auto {.inline.} =
|
82 | 85 | ## Return rounded up value of duration with milliseconds resolution.
|
83 | 86 | ##
|
@@ -142,10 +145,10 @@ template processTicks(loop: untyped) =
|
142 | 145 | loop.callbacks.addLast(loop.ticks.popFirst())
|
143 | 146 |
|
144 | 147 | template processCallbacks(loop: untyped) =
|
145 |
| - while true: |
146 |
| - let callable = loop.callbacks.popFirst() # len must be > 0 due to sentinel |
147 |
| - if isSentinel(callable): |
148 |
| - break |
| 148 | + # Process existing callbacks but not those that follow, to allow the network |
| 149 | + # to regain control regularly |
| 150 | + for _ in 0..<loop.callbacks.len(): |
| 151 | + let callable = loop.callbacks.popFirst() |
149 | 152 | if not(isNil(callable.function)):
|
150 | 153 | callable.function(callable.udata)
|
151 | 154 |
|
@@ -337,7 +340,6 @@ elif defined(windows):
|
337 | 340 | trackers: initTable[string, TrackerBase](),
|
338 | 341 | counters: initTable[string, TrackerCounter]()
|
339 | 342 | )
|
340 |
| - res.callbacks.addLast(SentinelCallback) |
341 | 343 | initAPI(res)
|
342 | 344 | res
|
343 | 345 |
|
@@ -585,16 +587,13 @@ elif defined(windows):
|
585 | 587 |
|
586 | 588 | proc poll*() =
|
587 | 589 | let loop = getThreadDispatcher()
|
| 590 | + loop.preparePoll() |
| 591 | +
|
588 | 592 | var
|
589 | 593 | curTime = Moment.now()
|
590 | 594 | curTimeout = DWORD(0)
|
591 | 595 | events: array[MaxEventsCount, osdefs.OVERLAPPED_ENTRY]
|
592 | 596 |
|
593 |
| - # On reentrant `poll` calls from `processCallbacks`, e.g., `waitFor`, |
594 |
| - # complete pending work of the outer `processCallbacks` call. |
595 |
| - # On non-reentrant `poll` calls, this only removes sentinel element. |
596 |
| - processCallbacks(loop) |
597 |
| -
|
598 | 597 | # Moving expired timers to `loop.callbacks` and calculate timeout
|
599 | 598 | loop.processTimersGetTimeout(curTimeout)
|
600 | 599 |
|
@@ -660,14 +659,10 @@ elif defined(windows):
|
660 | 659 | # We move tick callbacks to `loop.callbacks` always.
|
661 | 660 | processTicks(loop)
|
662 | 661 |
|
663 |
| - # All callbacks which will be added during `processCallbacks` will be |
664 |
| - # scheduled after the sentinel and are processed on next `poll()` call. |
665 |
| - loop.callbacks.addLast(SentinelCallback) |
| 662 | + # Process the callbacks currently scheduled - new callbacks scheduled during |
| 663 | + # callback execution will run in the next poll iteration |
666 | 664 | processCallbacks(loop)
|
667 | 665 |
|
668 |
| - # All callbacks done, skip `processCallbacks` at start. |
669 |
| - loop.callbacks.addFirst(SentinelCallback) |
670 |
| - |
671 | 666 | proc closeSocket*(fd: AsyncFD, aftercb: CallbackFunc = nil) =
|
672 | 667 | ## Closes a socket and ensures that it is unregistered.
|
673 | 668 | let loop = getThreadDispatcher()
|
@@ -758,7 +753,6 @@ elif defined(macosx) or defined(freebsd) or defined(netbsd) or
|
758 | 753 | trackers: initTable[string, TrackerBase](),
|
759 | 754 | counters: initTable[string, TrackerCounter]()
|
760 | 755 | )
|
761 |
| - res.callbacks.addLast(SentinelCallback) |
762 | 756 | initAPI(res)
|
763 | 757 | res
|
764 | 758 |
|
@@ -1014,14 +1008,11 @@ elif defined(macosx) or defined(freebsd) or defined(netbsd) or
|
1014 | 1008 | proc poll*() {.gcsafe.} =
|
1015 | 1009 | ## Perform single asynchronous step.
|
1016 | 1010 | let loop = getThreadDispatcher()
|
| 1011 | + loop.preparePoll() |
| 1012 | + |
1017 | 1013 | var curTime = Moment.now()
|
1018 | 1014 | var curTimeout = 0
|
1019 | 1015 |
|
1020 |
| - # On reentrant `poll` calls from `processCallbacks`, e.g., `waitFor`, |
1021 |
| - # complete pending work of the outer `processCallbacks` call. |
1022 |
| - # On non-reentrant `poll` calls, this only removes sentinel element. |
1023 |
| - processCallbacks(loop) |
1024 |
| - |
1025 | 1016 | # Moving expired timers to `loop.callbacks` and calculate timeout.
|
1026 | 1017 | loop.processTimersGetTimeout(curTimeout)
|
1027 | 1018 |
|
@@ -1068,14 +1059,10 @@ elif defined(macosx) or defined(freebsd) or defined(netbsd) or
|
1068 | 1059 | # We move tick callbacks to `loop.callbacks` always.
|
1069 | 1060 | processTicks(loop)
|
1070 | 1061 |
|
1071 |
| - # All callbacks which will be added during `processCallbacks` will be |
1072 |
| - # scheduled after the sentinel and are processed on next `poll()` call. |
1073 |
| - loop.callbacks.addLast(SentinelCallback) |
| 1062 | + # Process the callbacks currently scheduled - new callbacks scheduled during |
| 1063 | + # callback execution will run in the next poll iteration |
1074 | 1064 | processCallbacks(loop)
|
1075 | 1065 |
|
1076 |
| - # All callbacks done, skip `processCallbacks` at start. |
1077 |
| - loop.callbacks.addFirst(SentinelCallback) |
1078 |
| - |
1079 | 1066 | else:
|
1080 | 1067 | proc initAPI() = discard
|
1081 | 1068 | proc globalInit() = discard
|
|
0 commit comments