MINGW-packages icon indicating copy to clipboard operation
MINGW-packages copied to clipboard

Cairo text rendering cannot be multithreaded after commit 305ebda

Open Doublonmousse opened this issue 1 month ago • 6 comments

Description / Steps to reproduce the issue

On rnote, we use cairo for text rendering (text -> image). To speed up performance a threadpool queue is used with rayon to do this in a multithreaded manner. When this happens, a fresh new cairo context is created for each thread run.

Note: we are using piet and piet-cairo for the cairo calls.

But this crashes post https://github.com/msys2/MINGW-packages/commit/305ebda98c3041d9986d6fae498b45d2b2b9f4e8

Issue on rnote's side : https://github.com/flxzt/rnote/issues/1536 Building with the same PKGBUILD as latest on msys minus the D2D patch 0001-DWrite-Get-glyph-bitmap-with-D2D-in-selected-cases.patch works (see https://github.com/flxzt/rnote/pull/1561)

I believe creating multiple threads that all

  • create a cairo context
  • draw text using the piet draw_text will exhibit the issue.

Expected behavior

No crash

Actual behavior

Crashes

Stacktrace :

 # Child-SP          RetAddr               Call Site
00 000000dd`f8bfa868 00007ffa`47e8d008     ntdll!RtlpBreakPointHeap+0x16
01 000000dd`f8bfa870 00007ffa`47ec9980     ntdll!RtlpValidateHeapEntry+0x5d7e8
02 000000dd`f8bfa8b0 00007ffa`47df1d2f     ntdll!RtlDebugReAllocateHeap+0x120
03 000000dd`f8bfa930 00007ffa`47df38e3     ntdll!RtlpReAllocateHeap+0x34b
04 000000dd`f8bfaab0 00007ffa`47df36bd     ntdll!RtlpReAllocateHeapInternal+0x1c3
05 000000dd`f8bfac10 00007ffa`472b9e07     ntdll!RtlReAllocateHeap+0x7d
06 000000dd`f8bfac50 00007ffa`3f602ce7     msvcrt_7ffa472a0000!realloc+0x57
07 000000dd`f8bfac80 00007ffa`3f603b11     d2d1!CArray<CachedGlyphOutline,CPodTraits<CachedGlyphOutline>,CDefaultAllocator>::EnsureLargerCapacity+0x57
08 000000dd`f8bfacb0 00007ffa`3f603a17     d2d1!TextOutlineCache::AddCachedGlyphInternal+0x91
09 000000dd`f8bface0 00007ffa`3f6040fa     d2d1!TextOutlineCacheSubEntry::AddCachedGlyph+0x6f
0a 000000dd`f8bfad20 00007ffa`3f604f68     d2d1!<lambda_655c9741aced175d50f7ec4a5b30606b>::operator()+0xda
0b 000000dd`f8bfada0 00007ffa`3f60477e     d2d1!GlyphOutlineRenderer::RenderSingleGlyphs+0x1a4
0c 000000dd`f8bfaec0 00007ffa`3f5c1dab     d2d1!GlyphOutlineRenderer::RenderGlyphRun+0x1de
0d 000000dd`f8bfaf50 00007ffa`3f6fd51e     d2d1!CHwSurfaceRenderTarget::DrawGlyphRun+0x69b
0e 000000dd`f8bfb1d0 00007ffa`3f5c560e     d2d1!WarpRenderTarget::DrawGlyphRun+0x5ae
0f 000000dd`f8bfb420 00007ffa`3f5b3db5     d2d1!BrushRedirectionCompatibleCommand<CCommand_DrawGlyphRun,0>::Execute+0xee
10 000000dd`f8bfb530 00007ffa`3f5f3adf     d2d1!CHwSurfaceRenderTarget::ProcessBatch+0x65
11 000000dd`f8bfb580 00007ffa`3f5b4b58     d2d1!CBatchSerializer::FlushInternal+0xcf
12 000000dd`f8bfb610 00007ffa`3f6dec80     d2d1!DrawingContext::Flush+0x88
13 000000dd`f8bfb670 00007ffa`3f6ca16d     d2d1!DrawingContext::EndDraw+0x4c
14 000000dd`f8bfb6c0 00007ff9`f5de8bbe     d2d1!D2DDeviceContextBase<ID2D1BitmapRenderTarget,ID2D1BitmapRenderTarget,ID2D1DeviceContext6>::EndDraw+0xad
15 000000dd`f8bfb710 00007ff9`f5dea81b     libcairo_2!cairo_win32_scaled_font_get_device_to_logical+0xbee
16 000000dd`f8bfb900 00007ff9`f5d8cb78     libcairo_2!cairo_win32_scaled_font_get_device_to_logical+0x284b
17 000000dd`f8bfba30 00007ff9`f5d51849     libcairo_2!cairo_scaled_font_extents+0xcc8
18 000000dd`f8bfbad0 00007ff9`f5da5539     libcairo_2!cairo_font_options_get_custom_palette_color+0x6cd9
19 000000dd`f8bfc440 00007ff9`f5da59a5     libcairo_2!cairo_toy_font_face_get_weight+0x20c9
1a 000000dd`f8bfc530 00007ff9`f5d44ae7     libcairo_2!cairo_toy_font_face_get_weight+0x2535
1b 000000dd`f8bfc5d0 00007ff9`f5d591e2     libcairo_2!cairo_rectangle_list_destroy+0x1d77
1c 000000dd`f8bfc950 00007ff9`f5d9ea89     libcairo_2!cairo_font_options_get_custom_palette_color+0xe672
1d 000000dd`f8bfc9a0 00007ff9`f5d4f5a5     libcairo_2!cairo_surface_has_show_text_glyphs+0x469
1e 000000dd`f8bfca40 00007ff9`f5dace69     libcairo_2!cairo_font_options_get_custom_palette_color+0x4a35
1f 000000dd`f8bfde60 00007ffa`13005b74     libcairo_2!cairo_show_glyphs+0x29
20 000000dd`f8bfdea0 00007ffa`13005dad     libpangocairo_1_0_0!pango_cairo_font_map_get_font_type+0x1e54
21 000000dd`f8bfe780 00007ffa`11ff2c0c     libpangocairo_1_0_0!pango_cairo_font_map_get_font_type+0x208d
22 000000dd`f8bff090 00007ffa`11ff330c     libpango_1_0_0!pango_renderer_draw_glyph_item+0x3c
23 000000dd`f8bff0f0 00007ffa`11ff3abf     libpango_1_0_0!pango_renderer_draw_layout_line+0x65c
24 000000dd`f8bff260 00007ffa`13006594     libpango_1_0_0!pango_renderer_draw_layout+0xef
25 000000dd`f8bff340 00007ff6`307bd955     libpangocairo_1_0_0!pango_cairo_show_layout+0xa4
26 (Inline Function) --------`--------     rnote!pangocairo::auto::functions::show_layout+0x8 [C:/testernotebisect/rnote/_mesonbuild/cargo-home/registry/src/index.crates.io-1949cf8c6b5b557f/pangocairo-0.20.7/src/auto/functions.rs @ 142] 
27 (Inline Function) --------`--------     rnote!piet_cairo::{impl#0}::draw_text<kurbo::point::Point>+0x1f [C:/testernotebisect/rnote/_mesonbuild/cargo-home/registry/src/index.crates.io-1949cf8c6b5b557f/piet-cairo-0.7.0/src/lib.rs @ 178] 
28 000000dd`f8bff380 00007ff6`30842bc7     rnote!rnote_engine::strokes::textstroke::{impl#13}::draw<piet_cairo::CairoRenderContext>+0x1e5 [C:/testernotebisect/rnote/crates/rnote-engine/src/strokes/textstroke.rs @ 534] 
29 (Inline Function) --------`--------     rnote!rnote_engine::strokes::content::Content::gen_images::{closure#1}<rnote_engine::strokes::textstroke::TextStroke>+0x8 [C:/testernotebisect/rnote/crates/rnote-engine/src/strokes/content.rs @ 58] 
2a (Inline Function) --------`--------     rnote!rnote_engine::render::{impl#9}::gen_with_piet::{closure#0}<rnote_engine::strokes::content::Content::gen_images::{closure_env#1}<rnote_engine::strokes::textstroke::TextStroke>>+0x00000001`4000001b [C:/testernotebisect/rnote/crates/rnote-engine/src/render.rs @ 391] 
2b (Inline Function) --------`--------     rnote!rnote_engine::render::Image::gen_with_cairo<rnote_engine::render::{impl#9}::gen_with_piet::{closure_env#0}<rnote_engine::strokes::content::Content::gen_images::{closure_env#1}<rnote_engine::strokes::textstroke::TextStroke>>>+0x00000001`400005f9 [C:/testernotebisect/rnote/crates/rnote-engine/src/render.rs @ 361] 
2c (Inline Function) --------`--------     rnote!rnote_engine::render::Image::gen_with_piet<rnote_engine::strokes::content::Content::gen_images::{closure_env#1}<rnote_engine::strokes::textstroke::TextStroke>>+0x00000001`400005f9 [C:/testernotebisect/rnote/crates/rnote-engine/src/render.rs @ 398] 
2d (Inline Function) --------`--------     rnote!rnote_engine::strokes::content::Content::gen_images<rnote_engine::strokes::textstroke::TextStroke>+0x00000001`40000a25 [C:/testernotebisect/rnote/crates/rnote-engine/src/strokes/content.rs @ 57] 
2e 000000dd`f8bff4c0 00007ff6`30841c2b     rnote!rnote_engine::strokes::stroke::{impl#0}::gen_images+0xb27 [C:/testernotebisect/rnote/crates/rnote-engine/src/strokes/stroke.rs @ 57] 
2f (Inline Function) --------`--------     rnote!rnote_engine::store::render_comp::{impl#2}::regenerate_rendering_in_viewport_threaded::{closure#0}+0x00000001`40000025 [C:/testernotebisect/rnote/crates/rnote-engine/src/store/render_comp.rs @ 300] 
30 (Inline Function) --------`--------     rnote!core::panic::unwind_safe::{impl#25}::call_once<(), rnote_engine::store::render_comp::{impl#2}::regenerate_rendering_in_viewport_threaded::{closure_env#0}>+0x00000001`40000035 [/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/core/src/panic/unwind_safe.rs @ 272] 
31 (Inline Function) --------`--------     rnote!std::panicking::catch_unwind::do_call<core::panic::unwind_safe::AssertUnwindSafe<rnote_engine::store::render_comp::{impl#2}::regenerate_rendering_in_viewport_threaded::{closure_env#0}>, ()>+0x00000001`40000035 [/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/panicking.rs @ 589] 
32 (Inline Function) --------`--------     rnote!std::panicking::catch_unwind<(), core::panic::unwind_safe::AssertUnwindSafe<rnote_engine::store::render_comp::{impl#2}::regenerate_rendering_in_viewport_threaded::{closure_env#0}>>+0x00000001`40000035 [/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/panicking.rs @ 552] 
33 (Inline Function) --------`--------     rnote!std::panic::catch_unwind<core::panic::unwind_safe::AssertUnwindSafe<rnote_engine::store::render_comp::{impl#2}::regenerate_rendering_in_viewport_threaded::{closure_env#0}>, ()>+0x00000001`40000035 [/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/panic.rs @ 359] 
34 (Inline Function) --------`--------     rnote!rayon_core::unwind::halt_unwinding<rnote_engine::store::render_comp::{impl#2}::regenerate_rendering_in_viewport_threaded::{closure_env#0}, ()>+0x00000001`40000047 [C:/testernotebisect/rnote/_mesonbuild/cargo-home/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.12.1/src/unwind.rs @ 17] 
35 (Inline Function) --------`--------     rnote!rayon_core::registry::Registry::catch_unwind<rnote_engine::store::render_comp::{impl#2}::regenerate_rendering_in_viewport_threaded::{closure_env#0}>+0x00000001`40000047 [C:/testernotebisect/rnote/_mesonbuild/cargo-home/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.12.1/src/registry.rs @ 367] 
36 (Inline Function) --------`--------     rnote!rayon_core::spawn::spawn_job::{closure#0}<rnote_engine::store::render_comp::{impl#2}::regenerate_rendering_in_viewport_threaded::{closure_env#0}>+0x00000001`40000083 [C:/testernotebisect/rnote/_mesonbuild/cargo-home/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.12.1/src/spawn/mod.rs @ 97] 
37 000000dd`f8bff6b0 00007ff6`3062a6db     rnote!rayon_core::job::{impl#6}::execute<rayon_core::spawn::spawn_job::{closure_env#0}<rnote_engine::store::render_comp::{impl#2}::regenerate_rendering_in_viewport_threaded::{closure_env#0}>>+0xab [C:/testernotebisect/rnote/_mesonbuild/cargo-home/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.12.1/src/job.rs @ 169] 
38 (Inline Function) --------`--------     rnote!rayon_core::job::JobRef::execute+0x5 [C:/testernotebisect/rnote/_mesonbuild/cargo-home/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.12.1/src/job.rs @ 64] 
39 (Inline Function) --------`--------     rnote!rayon_core::registry::WorkerThread::execute+0x5 [C:/testernotebisect/rnote/_mesonbuild/cargo-home/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.12.1/src/registry.rs @ 860] 
3a 000000dd`f8bff8d0 00007ff6`30629989     rnote!rayon_core::registry::WorkerThread::wait_until_cold+0x53b [C:/testernotebisect/rnote/_mesonbuild/cargo-home/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.12.1/src/registry.rs @ 782] 
3b (Inline Function) --------`--------     rnote!rayon_core::registry::WorkerThread::wait_until<rayon_core::latch::OnceLatch>+0x00000001`40000098 [C:/testernotebisect/rnote/_mesonbuild/cargo-home/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.12.1/src/registry.rs @ 769] 
3c (Inline Function) --------`--------     rnote!rayon_core::registry::WorkerThread::wait_until_out_of_work+0x00000001`400000c3 [C:/testernotebisect/rnote/_mesonbuild/cargo-home/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.12.1/src/registry.rs @ 818] 
3d (Inline Function) --------`--------     rnote!rayon_core::registry::main_loop+0x00000001`40000133 [C:/testernotebisect/rnote/_mesonbuild/cargo-home/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.12.1/src/registry.rs @ 923] 
3e (Inline Function) --------`--------     rnote!rayon_core::registry::ThreadBuilder::run+0x00000001`40000133 [C:/testernotebisect/rnote/_mesonbuild/cargo-home/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.12.1/src/registry.rs @ 53] 
3f (Inline Function) --------`--------     rnote!rayon_core::registry::{impl#2}::spawn::{closure#0}+0x00000001`40000133 [C:/testernotebisect/rnote/_mesonbuild/cargo-home/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.12.1/src/registry.rs @ 98] 
40 000000dd`f8bff980 00007ff6`3062c5b5     rnote!std::sys::backtrace::__rust_begin_short_backtrace<rayon_core::registry::{impl#2}::spawn::{closure_env#0}, ()>+0x159 [/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/sys/backtrace.rs @ 158] 
41 (Inline Function) --------`--------     rnote!std::thread::{impl#0}::spawn_unchecked_::{closure#1}::{closure#0}<rayon_core::registry::{impl#2}::spawn::{closure_env#0}, ()>+0x00000001`40000017 [/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/thread/mod.rs @ 559] 
42 (Inline Function) --------`--------     rnote!core::panic::unwind_safe::{impl#25}::call_once<(), std::thread::{impl#0}::spawn_unchecked_::{closure#1}::{closure_env#0}<rayon_core::registry::{impl#2}::spawn::{closure_env#0}, ()>>+0x00000001`40000017 [/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/core/src/panic/unwind_safe.rs @ 272] 
43 (Inline Function) --------`--------     rnote!std::panicking::catch_unwind::do_call<core::panic::unwind_safe::AssertUnwindSafe<std::thread::{impl#0}::spawn_unchecked_::{closure#1}::{closure_env#0}<rayon_core::registry::{impl#2}::spawn::{closure_env#0}, ()>>, ()>+0x00000001`4000001c [/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/panicking.rs @ 589] 
44 (Inline Function) --------`--------     rnote!std::panicking::catch_unwind<(), core::panic::unwind_safe::AssertUnwindSafe<std::thread::{impl#0}::spawn_unchecked_::{closure#1}::{closure_env#0}<rayon_core::registry::{impl#2}::spawn::{closure_env#0}, ()>>>+0x00000001`4000001c [/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/panicking.rs @ 552] 
45 (Inline Function) --------`--------     rnote!std::panic::catch_unwind<core::panic::unwind_safe::AssertUnwindSafe<std::thread::{impl#0}::spawn_unchecked_::{closure#1}::{closure_env#0}<rayon_core::registry::{impl#2}::spawn::{closure_env#0}, ()>>, ()>+0x00000001`4000001c [/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/panic.rs @ 359] 
46 (Inline Function) --------`--------     rnote!std::thread::{impl#0}::spawn_unchecked_::{closure#1}<rayon_core::registry::{impl#2}::spawn::{closure_env#0}, ()>+0x00000001`40000117 [/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/thread/mod.rs @ 557] 
47 000000dd`f8bffc20 00007ff6`30b9ce0d     rnote!core::ops::function::FnOnce::call_once<std::thread::{impl#0}::spawn_unchecked_::{closure_env#1}<rayon_core::registry::{impl#2}::spawn::{closure_env#0}, ()>, ()>+0x135 [/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/core/src/ops/function.rs @ 253] 
48 (Inline Function) --------`--------     rnote!alloc::boxed::{impl#28}::call_once<(), dyn core::ops::function::FnOnce<(), Output=()>, alloc::alloc::Global>+0x00000001`40000006 [/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/alloc/src/boxed.rs @ 1971] 
49 (Inline Function) --------`--------     rnote!alloc::boxed::{impl#28}::call_once<(), alloc::boxed::Box<dyn core::ops::function::FnOnce<(), Output=()>, alloc::alloc::Global>, alloc::alloc::Global>+0x00000001`4000000d [/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/alloc/src/boxed.rs @ 1971] 
4a 000000dd`f8bffdb0 00007ffa`47507374     rnote!std::sys::pal::windows::thread::{impl#0}::new::thread_start+0x2d [/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/sys/pal/windows/thread.rs @ 60] 
4b 000000dd`f8bffe00 00007ffa`47e1cc91     KERNEL32!BaseThreadInitThunk+0x14
4c 000000dd`f8bffe30 00000000`00000000     ntdll!RtlUserThreadStart+0x21

There are also some occasional errors popping

IWICBitmapSource::CopyPixels() failed: There is already a read or write lock pending.

Verification

  • [x] I have verified that my MSYS2 is up-to-date before submitting the report (see https://www.msys2.org/docs/updating/)

Windows Version

MINGW64_NT-10.0-26100

MINGW environments affected

  • [x] MINGW64
  • [ ] MINGW32
  • [ ] UCRT64
  • [ ] CLANG64
  • [ ] CLANGARM64

Are you willing to submit a PR?

No response

Doublonmousse avatar Nov 02 '25 16:11 Doublonmousse

Hello @Doublonmousse!

The underlying issue is https://gitlab.freedesktop.org/cairo/cairo/-/issues/886. DWrite support for Cairo was imported from Firefox, which presumably used Cairo from a single thread only, so it uses the single-threaded D2D1 factory.

Technically Cairo was unsafe even before the patch, but now uses D2D even more...

I'll open a PR to fix this issue!

lb90 avatar Nov 03 '25 12:11 lb90

I'll open a PR to fix this issue!

Thanks.

For reference, that's the corresponding PR: https://gitlab.freedesktop.org/cairo/cairo/-/merge_requests/641

striezel avatar Nov 07 '25 19:11 striezel

Can the DWrite backend be deactivated through an environment variable in the meantime ?

I know it can be excluded from the build with a meson configure but it'd be a little simpler not to have to compile cairo to workaround the issue

Doublonmousse avatar Nov 08 '25 10:11 Doublonmousse

Can the DWrite backend be deactivated through an environment variable in the meantime ?

Looks like piet uses PangoCairo, so you might set the environment variable PANGOCAIRO_BACKEND=fc, where "fc" stands for "fontconfig". Note however that this affects GTK as well, and the naming of fonts might change. I don't know if font family / face names are hardcoded in CSS or present in user configuration files.

lb90 avatar Nov 08 '25 13:11 lb90

@striezel yes, that would help but there are other things which are not thread safe. I should open a final MR next week ;)

lb90 avatar Nov 08 '25 13:11 lb90

Can the DWrite backend be deactivated through an environment variable in the meantime ?

Looks like piet uses PangoCairo, so you might set the environment variable PANGOCAIRO_BACKEND=fc, where "fc" stands for "fontconfig". Note however that this affects GTK as well, and the naming of fonts might change. I don't know if font family / face names are hardcoded in CSS or present in user configuration files.

Thanks !

Setting the env variable in powershell with

$env:PANGOCAIRO_BACKEND="fc"
& 'C:\Program Files\Rnote\bin\rnote.exe'

works (no crash). The only side effect seems to be on rendered emojis in rnote.

without env variable with fc
Image Image

Doublonmousse avatar Nov 08 '25 13:11 Doublonmousse