I'm writing this here as well as on the issue tracker 
(<https://github.com/status-im/nim-chronos/issues/542>) in the hopes that maybe 
somebody with more experience can see the problem.

So I'm playing around with a multithreading lib that has a while-loop going on 
another thread that has an async-event-loop and can receive and process 
messages. The goal is to have the simplest possible way of pushing work from 
thread A to thread B with thread B acting in a server-like manner and enabling 
using async like you would in e.g. JS where you don't have to care yourself 
_when_ the async work gets done. It just happens "eventually". To do that I use 
ThreadSignalPtr to put the thread to sleep when there's nothing to do / do 
async work. However, in the constellation I'm using for that I'm seeing memory 
leaks when compiling with address sanitizer. I managed to condense that down 
into a simple program:
    
    
    import std/[os]
    import chronos
    import chronos/threadsync
    
    proc cleanupThread*() {.gcsafe, raises: [].}=
      {.cast(gcsafe).}:
        try:
          when defined(gcOrc):
            GC_fullCollect() # from orc.nim. Has no destructor.
        except Exception as e:
          echo "Exception during cleanup: ", e[]
    
    proc processAsync() {.async.} =
      await sleepAsync(1)
      echo "Stuff"
    
    proc threadProc(threadSignal: ThreadSignalPtr) {.thread.} =
      asyncSpawn processAsync()
      
      waitFor threadSignal.wait()
      
      cleanupThread()
    
    var thread: Thread[ThreadSignalPtr]
    let signal = new(ThreadSignalPtr)[]
    
    createThread(thread, threadProc, signal)
    sleep(10)
    discard signal.fireSync()
    
    joinThread(thread)
    discard signal.close()
    
    
    Run

Compiled with ` nim r -f --debugger:native --cc:clang -d:useMalloc 
--passc:"-fsanitize=address -fno-omit-frame-pointer 
-mno-omit-leaf-frame-pointer" \--passl:"-fsanitize=address 
-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer" -d:release 
src/playground.nim`

(For reference, the passc/passl flags are just to use address sanitizer and 
provide better stacktraces, this can also be used with gcc, I just prefer 
clang).

And it shows a ton of leaks, all relating to getThreadDispatcher in one way or 
another:
    
    
    ... way more stacktraces ...
    Indirect leak of 16 byte(s) in 1 object(s) allocated from:
        #0 0x6493f5f086f1 in calloc 
(/home/isofruit/.cache/nim/playground_r/playground_CAED38D1B3F5F9549C0D683A5A5C9A6761A36907+0x1266f1)
 (BuildId: 45b58de06b1e762d7de2cf93447bf65a9b6087d7)
        #1 0x6493f5f599eb in newSeqPayload 
/home/isofruit/.choosenim/toolchains/nim-2.0.4/lib/system/seqs_v2.nim:44:773
        #2 0x6493f5f61dff in 
add::add(var<seq<ref<asyncengine::TimerCallbackcolonObjectType_>>>, 
sink<ref<asyncengine::TimerCallbackcolonObjectType_>>) 
/home/isofruit/.choosenim/toolchains/nim-2.0.4/lib/system/seqs_v2.nim:116:26
        #3 0x6493f5f6ae9e in 
push::push(var<HeapQueue<ref<asyncengine::TimerCallbackcolonObjectType_>>>, 
sink<ref<asyncengine::TimerCallbackcolonObjectType_>>) 
/home/isofruit/.choosenim/toolchains/nim-2.0.4/lib/pure/collections/heapqueue.nim:137:2
        #4 0x6493f5f85f59 in asyncengine::setTimer(timer::Moment, 
proc<pointer>, pointer) 
/home/isofruit/.nimble/pkgs2/chronos-4.0.2-5ed2d147ae4760a4ec6f01daf794df761b5229af/chronos/internal/asyncengine.nim:1110:2
        #5 0x6493f5f9154f in asyncfutures::sleepAsync(timer::Duration) 
/home/isofruit/.nimble/pkgs2/chronos-4.0.2-5ed2d147ae4760a4ec6f01daf794df761b5229af/chronos/internal/asyncfutures.nim:1397:163
        #6 0x6493f5f97a41 in asyncfutures::sleepAsync(int) 
/home/isofruit/.nimble/pkgs2/chronos-4.0.2-5ed2d147ae4760a4ec6f01daf794df761b5229af/chronos/internal/asyncfutures.nim:1403:107
        #7 0x6493f5f97a41 in 
processAsync::processAsync(ref<futures::FutureBasecolonObjectType_>) 
/home/isofruit/dev/playground/src/playground.nim:14:27
        #8 0x6493f5f91d47 in 
asyncfutures::futureContinue(ref<futures::FutureBasecolonObjectType_>) 
/home/isofruit/.nimble/pkgs2/chronos-4.0.2-5ed2d147ae4760a4ec6f01daf794df761b5229af/chronos/internal/asyncfutures.nim:382:95
        #9 0x6493f5f98cde in playground::processAsync 
/home/isofruit/dev/playground/src/playground.nim:14:2
        #10 0x6493f5f99465 in 
playground::threadProc(ptr<threadsync::ThreadSignal>) 
/home/isofruit/dev/playground/src/playground.nim:18:15
        #11 0x6493f5f5e579 in 
threadProcWrapDispatch::threadProcWrapDispatch(ptr<Thread<ptr<threadsync::ThreadSignal>>>)
 /home/isofruit/.choosenim/toolchains/nim-2.0.4/lib/system/threadimpl.nim:69:2
        #12 0x6493f5f5e579 in 
threadProcWrapStackFrame::threadProcWrapStackFrame(ptr<Thread<ptr<threadsync::ThreadSignal>>>)
 /home/isofruit/.choosenim/toolchains/nim-2.0.4/lib/system/threadimpl.nim:95:2
        #13 0x6493f5f54de8 in threadProcWrapper::threadProcWrapper(pointer) 
/home/isofruit/.choosenim/toolchains/nim-2.0.4/lib/system/threadimpl.nim:101:2
        #14 0x6493f5e3fa36 in asan_thread_start(void*) asan_interceptors.cpp.o
    
    SUMMARY: AddressSanitizer: 19288 byte(s) leaked in 11 allocation(s).
    
    
    Run

Does anyone see where the leak is in this scenario and how to tackle it?

Reply via email to