Cairo context corruption when shared across threads without external locking
0 reputation · 04 Jul 2020, 11:51 UTC
0 reputation · 04 Jul 2020, 11:51 UTC
The goal is to enable safe concurrent drawing operations on a single Cairo surface from multiple threads. Cairo's documented contract treats cairo_t as single-threaded; the mutable path, source, and transformation state inside the context have no internal synchronization and assume exclusive access. While cairo_surface_t reference counting is thread-safe, sharing a context across threads without external locking produces undefined behavior that manifests as rendering artifacts, memory corruption, or crashes depending on the backend.
Constraints include the absence of a lock parameter or runtime error for concurrent context misuse, backend-specific tolerance for accidental sharing that varies across platforms, and the performance cost of serializing all drawing work behind a global mutex. Using separate cairo_t instances per thread on the same surface avoids context-state races but may still introduce surface-memory races unless the specific backend guarantees thread-safe rasterization.
What synchronization strategy preserves correctness across backends without serializing unrelated drawing work? Does creating one cairo_t per thread on a shared image surface eliminate all data races, or do surface-level races persist? Are there backend-specific guarantees for thread-safe rasterization that can be relied upon in production?
34775 reputation · 04 Jul 2020, 22:36 UTC
Creating one cairo_t per thread on a shared image surface does not eliminate data races. Surface-level races persist because all contexts write into the same pixel buffer and share surface-level state (status, snapshots, fallback images). No Cairo backend documents a guarantee of thread-safe rasterization into the same surface from multiple contexts; accidental tolerance on image or platform backends is not a production contract.
The portable correctness strategy is to confine each cairo_t to a single thread and never allow two contexts to target the same surface concurrently. To avoid serializing unrelated drawing work, partition the scene into independent surfaces per thread (or per tile), then composite them sequentially under a single owner thread or a short lock around only the composition step.
cairo_t owns mutable path, source, matrix, clip, and font state with no internal synchronization; the documented contract is single-thread per context.cairo_surface_t reference counting is thread-safe. Drawing operations, status changes, snapshots, and backend rasterization are not covered by that guarantee.cairo_image_surface_create) and draws exclusively on it with a thread-local cairo_t.cairo_set_source_surface / cairo_paint.cairo_t operation sequence (create, draw, destroy).If the backend supports it (e.g., image surface with known stride), you can assign non-overlapping rectangular tiles to each thread, each with its own cairo_t clipped to its tile. However, Cairo does not expose a formal "disjoint tile" contract, and surface-level state (status, snapshots) is still shared, so this remains unsupported and fragile. Prefer Option A.
None. The Cairo manual makes no thread-safety promise for rasterization into a shared surface from multiple contexts. Image, Xlib, Quartz, Win32, PDF, SVG, and PostScript backends all exhibit undefined behavior under concurrent use. Observed "clean" output on one platform or version is not evidence of safety; races may be latent, appear only under load, or surface with sanitizers.
cairo.h or the Cairo manual for that release.cairo_t and run it under ThreadSanitizer (-fsanitize=thread) and Helgrind to observe whether surface-level races are reported.cairo_t is reachable from two threads simultaneously and that every shared surface is guarded by one mutex or owned by a single thread.Which Cairo version and which backends (image, xlib, quartz, win32, PDF, SVG, etc.) are you targeting in production? The exact version matters because internal rasterization paths, fallback images, and snapshot behavior can change between releases.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.