Hi,
I am trying async/await with NuttX.
NuttX has a bash-like shell called "nsh" from which commands can be invoked. Unlike Linux and other operating systems with virtual memory, the memory used by the program (task) is not automatically recovered by the operating system when the program (task) is terminated, and the contents remain.
When executing a task that uses asyncdispatch, the first time global dispatcher (gDisp) is created by newSelector() because it is Nil, but when the task terminates and is started a second time, gDisp is not created and does not work properly.
I created a function to explicitly clear the contents of gDisp and explicitly destroy it when the task terminates, and it seems to work properly the second time and thereafter.
proc clearGlobalDispatcher*() =
if gDisp.isNil:
return
gDisp.selector.close()
gDisp = nil
Would it be OK to add such a function (for NuttX only, isolated by when defined(nuttx): )? Or is there a smarter, Nim-like ARC/ORC mechanism for discarding?
Thank you for response.
If gDisp is not a global variable in {.threadvar.}, but an object to be allocated in the heap, I think it could be destroyed with `=destroy` at task exit with ARC/ORC.
Hopefully this can be implemented seamlessly without affecting existing sources and without users being aware of it.
( Like automatically initialize it with NimMain() and automatically release it at the end of the task, and so on).
Is FreeRTOS, Zephyr doing well ?
This is the only way I can think of, and it's a little poorly implemented, but...
Create some object in asyncdispatch and create a function to return it.
AsyncUse* = object
use: int
proc startAsyncDispatch*(): AsyncUse =
result.use = 1
And put the process of clearing gDisp in the destructor `=destroy` of the object, the cleanup process is successfully executed when the NuttX task is terminated.
proc clearGlobalDispatcher() =
if gDisp.isNil:
return
gDisp.selector.close()
gDisp = nil
proc `=destroy`(x: var AsyncUse) =
echo "clear Global Dispatcher"
clearGlobalDispatcher()
proc appMain() =
let x = startAsyncDispatch()
...
It would be nice if there could be a cleaner way to avoid showing the user the process of creating additional odd objects.
Additional investigation revealed that the true cause was not "gDisp is not cleared", but "gDisp is cleared, but the Selector object created by the internal newSelector is not closed() at the end of the task".
Since it is impossible to do any processing when the task is started (since gDisp is cleared, the Selector object cannot be close()), it seems that the only way is to call Selector::close() when the task ends.
I finally found the cause of the problem and have a solution. (It requires explicit initialization, though...)
When the task ends, all opened file descriptors are closed by OS, but the memory is left as it is, so the gDisp is not released and the "value" of the epoll fd inside gDisp is left as it is.
Therefore, when the task was started a second time, it judged that epoll_create1() had already been executed, resulting in an error.
For NuttX only, I was able to deal with this by explicitly registering the process of clearing gDisp with atexit() before using the asyncdispatch function.
(I also tried std/exitprocs, but that also leaves the state, so it cannot be registered a second time, so I call the C function directly).
diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim
index 2bfa8587c..b55eb8b19 100644
--- a/lib/pure/asyncdispatch.nim
+++ b/lib/pure/asyncdispatch.nim
@@ -1216,6 +1216,16 @@ else:
var gDisp{.threadvar.}: owned PDispatcher ## Global dispatcher
+ when defined(nuttx):
+ proc addAtExit(quitProc: proc() {.noconv, cdecl.}) {.
+ importc: "atexit", header: "<stdlib.h>".}
+
+ proc cleanDispacher() {.noconv, cdecl.} =
+ gDisp = nil
+
+ proc initAsyncDispatch*() =
+ addAtExit(cleanDispacher)
+
I will submit a pull request later.
Neat!
I’ll be curious to see how it works with ESP32’s version of FreeRTOS. It’d have similar issues, though I usually don’t destroy threads after creating them.
Hi,
I have sent out a pull request so that the user no longer needs to make changes to the source. (Ummm, CI is giving me an error...)
Since NuttX has a shell, we often create applications that start and exit immediately.
The shell interface looks like this.
nsh> ps
PID GROUP PRI POLICY TYPE NPX STATE EVENT SIGMASK STACK COMMAND
0 0 0 FIFO Kthread N-- Ready 00000000 001000 Idle Task
1 1 224 RR Kthread --- Waiting Semaphore 00000000 001992 hpwork 0x20001460
2 2 100 RR Kthread --- Waiting Semaphore 00000000 001992 lpwork 0x20001448
3 3 100 RR Task --- Running 00000000 002000 nsh_main
nsh> free
total used free largest nused nfree
Umem: 117200 14576 102624 81696 61 5
nsh> ls /dev
/dev:
console
loop
null
random
rtc0
timer0
ttyS0
ttyS1
urandom
zero
nsh>
So, it seems that the exit process may be affected by differences in operating systems. I will do more testing.