feat(ffi): slab-allocate event payloads instead of c_malloc per dispatch#107
Merged
Conversation
Ivansete-status
approved these changes
Jul 5, 2026
Ivansete-status
left a comment
Collaborator
There was a problem hiding this comment.
LGTM! Thanks for it! 🙌
With regards to the event management, we need to invest efforts in the following, which basically aims to avoid dropping events (avoid fixed-sized ring) while keeping memory steady: #112
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #92.
Under high-rate event emission every
dispatchFFIEvent/dispatchFFIEventCborcost twoc_mallocs and twoc_frees — one for the payload, one for the name string — churning the process-global allocator on the hot path. This replaces the per-event allocations with preallocated, reused per-slot buffers so steady-state emission allocates nothing.Each
EventQueuering slot now owns twoc_malloc-backed buffers, allocated once ininitEventQueueand freed once indeinitEventQueue:MaxEventPayloadBytes(default 512), andMaxEventNameBytes(default 64).tryEnqueueEventcopies the name and payload into the tail slot's buffers instead of adopting caller-ownedc_mallocpointers. A value that overflows its slot budget falls back to a one-offc_malloctaggednameHeapOwned/dataHeapOwnedand freed on commit, so the slab stays small without sizing for the worst case. Buffers remainc_malloc-backed (notallocShared) so the event thread can read a slot after the producing FFI thread's heap is gone — the same TLS hazard documented inalloc.nim.EventQueueCapacity,MaxEventPayloadBytes, andMaxEventNameBytesare{.intdefine.}consts, overridable per built library (-d:MaxEventPayloadBytes=N, etc.).Two intentional divergences from the issue
detect_leaks=1), so instead each slot reuses a name buffer freed atdeinit— still allocation-free in steady state, but leak-clean. A leak-free process-global intern would require cross-thread-mutable GC state, which this codebase deliberately avoids.peekEvent/commitDequeue), keeping the in-flight slot counted (and thus un-reusable) until dispatch returns. This is what keeps the producer/consumer handoff TSan-clean.Tests
test_event_dispatch.nim,test_event_thread.nim,test_event_listener.nimstay green (orc and refc).oversize payload falls back to heapandoversize event name falls back to heap, covering both heap-fallback branches end-to-end.detect_leaks=1); the overflow/backpressure handoff intest_event_thread.nimis TSan clean.