Async/await latency spikes under concurrent requests in Vala
0 reputation · 06 Jul 2025, 06:45 UTC
0 reputation · 06 Jul 2025, 06:45 UTC
The goal is to pinpoint the source of latency spikes that appear only when several Vala async functions are invoked concurrently. The application uses GLib 2.64, POSIX threads, and the async/await pattern compiled to GTask and GMainContext. The issue manifests as unpredictable delays, but no single error is logged.
Possible contributors include lock contention inside GMainContext, contention in GLib's memory allocator when many threads allocate, and GObject signal emission overhead when multiple objects emit signals simultaneously.
Because the latency is only observable under concurrent load, the root cause may be subtle, and existing benchmarks for Vala concurrency are sparse.
What specific aspects of GMainContext lock contention could be responsible for the observed delays? Does GLib's allocator create contention when multiple threads perform frequent allocations? To what extent do concurrent GObject signal emissions add to the latency?
The latency spikes are most often caused by serialization inside a shared GMainContext. All Vala async continuations are resumed via GTask callbacks that are dispatched by the same main-loop thread. When many tasks complete at nearly the same time, the context's wake-up pipe/eventfd is written many times, the ready-list is rebuilt, and each callback is run one after another. A single slow or blocking callback (even a short g_usleep or I/O) stalls the dispatcher, making every other pending request wait, which appears as an unpredictable latency spike without any error log.
GMainContext can be owned/iterated by only one thread at a time; ownership is acquired with g_main_context_acquire and released after each iteration.Frequent allocations/frees for GTask, closures, GObject instances, or GVariant can stress g_malloc/g_slice and the system allocator, especially when objects are allocated on one thread and freed on another. This usually shows as CPU time in malloc/free or futex waits, not as async-specific latency, unless G_SLICE forces a global lock or arena limits are hit.
Signal emission adds latency proportional to the number of connected handlers, the complexity of marshalling, any accumulator or return-value handling, and re-entrancy. If handlers do only quick work, emissions scale linearly and rarely cause spikes. Problems arise when handlers block, emit signals re-entrantly, or when classes are being modified while the load runs.
GMainContext, acquire it, set it as thread-default before creating any async tasks, and run the same load.G_SLICE=always-malloc, MALLOC_ARENA_MAX) and check signal handlers for blocking work.Are the async callbacks performing any blocking I/O, mutex waits, or lengthy computations? Knowing this changes the recommendation: if they do, move that work off the main context; if they are already non-blocking, focus on context allocation or allocator tuning.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.