Skip to content

Commit

Permalink
status-im#542 Add first draft for chronos-multithreading docs
Browse files Browse the repository at this point in the history
  • Loading branch information
= committed May 21, 2024
1 parent 8a30676 commit 65f0080
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 0 deletions.
34 changes: 34 additions & 0 deletions docs/examples/multithreading.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import std/[os]
import chronos
import chronos/threadsync

proc cleanupThread*() {.gcsafe, raises: [].}=
{.cast(gcsafe).}:
try:
`=destroy`(getThreadDispatcher())
when defined(gcOrc):
GC_fullCollect()
except Exception as e:
echo "Exception during cleanup: ", e[]

proc processAsync() {.async.} =
await sleepAsync(1000) # Waiting on an HTTP Request or the like
echo "Done with async work"

proc threadProc(threadSignal: ThreadSignalPtr) {.thread.} =
block:
asyncSpawn processAsync() # Start some async work

waitFor threadSignal.wait() # Do async work and then go to sleep until main thread wakes you up

cleanupThread() # Clean up thread local variables, e.g. dispatcher

var thread: Thread[ThreadSignalPtr]
let signal = new(ThreadSignalPtr)[]

createThread(thread, threadProc, signal)
sleep(10)
discard signal.fireSync()

joinThread(thread)
discard signal.close()
10 changes: 10 additions & 0 deletions docs/src/multithreading_async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Multithreading with async event-loops

At times you may want to run asynchronous work on another thread for whatever reason,
then wait while working through the asynchronous work until you receive a signal to continue.

This kind of setup can be facilitated by using `threadSync/ThreadSignalPtr`

```nim
{{#include ../examples/multithreading.nim}}
```
18 changes: 18 additions & 0 deletions docs/src/threads.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,21 @@ to notify other threads of progress from within an async procedure.
```nim
{{#include ../examples/signalling.nim}}
```

### Memory management in multithreading scenarios

Keep in mind that nim _does not clean up thread-variables_ when the thread exits.
This is problematic for thread variables that are ref-types or contain ref-types, as those will not be cleaned up and thus start causing memory leaks.

This means, that the dispatcher, which is a thread variable and a ref-type, needs to be cleaned up manually. The same is true for the thread-local variables `localInstance` and `utcInstance` of std/times, which will be used if you use any logging library such as chronicles.
Not just that, you will also need to invoke ORC's cycle-collector manually, as chronos' data-structures are at times cyclical and thus need to be collected as well.

You can do so by calling `=destroy` on it and forcing ORC to do a collect of cyclical data-structures like so:

```nim
`=destroy`(times.localInstance)
`=destroy`(times.utcInstance)
`=destroy`(getThreadDispatcher())
when defined(gcOrc):
GC_fullCollect()
```

0 comments on commit 65f0080

Please sign in to comment.