Mark Ox::sax_parse as Ractor-safe and add a Ractor-based Ox::Sax example. - #278
Conversation
…ax` example. For ohler55#277 I've been experimenting with `Ractor`s since upgrading to Ruby 3.0 for ohler55#275 but quickly ran into `Ractor::UnsafeError` when trying to call `Ox::sax_parse` in anything except the main `Ractor`. Per Ruby's `Ractor` C extension documentation (link below): "By default, all C extensions are recognized as `Ractor`-unsafe. If C extension becomes `Ractor`-safe, the extension should call `rb_ext_ractor_safe(true)` at the `Init_` function and all defined method marked as `Ractor`-safe. `Ractor`-unsafe C-methods only been called from main-ractor. If non-main ractor calls it, then `Ractor::UnsafeError` is raised." I don't like to open seemingly-large feature requests like this without making some attempt at it myself first, and luckily it seems like `Ox::sax_parse` Just Works™ since I marked it `Ractor`-safe, even with the `class_cache`. Confirming this safety, making any remaining changes to `Ox::Sax`, and expanding this to the non-`Sax` parts of `Ox` are all unfortunately out of my depth as a n00b C coder, so I would appreciate if you could take this over if it interests you. I am happy with just `Sax` support since I have no current need for marshalling, but I imagine other `Ox` users wouldn't be satisfied if stratified. In this commit: - Enable `rb_ext_ractor_safe` preprocessor macro via `have_func` in `extconf.rb`. - Mark `Init_Ox` and `ox_sax_parse` as `Ractor` -safe. - Add a new `Ractor`-based `Ox::Sax` example exercising both parallel and serial `Ox::Sax` handler `Ractor`s to parse data from `shared-mime-info` XML files many users likely already have on their systems. Official `Ractor` info: - "Ractor: a proposal for a new concurrent abstraction without thread-safety issues": https://bugs.ruby-lang.org/issues/17100 (ruby/ruby#3365) - Ruby's official `Ractor` documentation: https://docs.ruby-lang.org/en/master/doc/ractor_md.html - "A way to mark C extensions as thread-safe, Ractor-safe, or unsafe": https://bugs.ruby-lang.org/issues/17307 (ruby/ruby#3824) - Ruby's C Extension `Ractor` documention covering `rb_ext_ractor_safe`: https://docs.ruby-lang.org/en/master/doc/extension_rdoc.html#label-Appendix+F.+Ractor+support - A `Ractor` C Extension from the creator of `Ractor` that might serve as a useful example: https://github.com/ko1/ractor-tvar Blogs: - "Ractors: Multi-Core Parallel Processing Comes to Ruby 3": https://www.ruby3.dev/ruby-3-fundamentals/2021/01/27/ractors-multi-core-parallel-processing-in-ruby-3/ - "Ruby Ractor Experiments: Safe async communication" :https://ivoanjo.me/blog/2021/02/14/ractor-experiments-safe-async/ - "Playing with Ruby Ractors": https://billy-ruffian.co.uk/playing-with-ruby-ractors/ - "How Fast are Ractors?": https://www.fastruby.io/blog/ruby/performance/how-fast-are-ractors.html (https://github.com/noahgibbs/ractor_basic_benchmarks/tree/main/benchmarks) Before this change: ``` [okeeblow@emi#CHECKING-YOU-OUT] time ./bin/checking-you-out ~/224031-dot-jpg Received /home/okeeblow/.local/share/mime/packages/user-extension-rsrc.xml /home/okeeblow/Works/DistorteD/CHECKING-YOU-OUT/lib/checking-you-out/ghost_revival/mr_mime.rb:375:in `sax_parse': ractor unsafe method called from not main ractor (Ractor::UnsafeError) from /home/okeeblow/Works/DistorteD/CHECKING-YOU-OUT/lib/checking-you-out/ghost_revival/mr_mime.rb:375:in `block in open' from /home/okeeblow/Works/DistorteD/CHECKING-YOU-OUT/lib/checking-you-out/ghost_revival/mr_mime.rb:331:in `open' from /home/okeeblow/Works/DistorteD/CHECKING-YOU-OUT/lib/checking-you-out/ghost_revival/mr_mime.rb:331:in `open' from /home/okeeblow/Works/DistorteD/CHECKING-YOU-OUT/lib/checking-you-out/ghost_revival.rb:24:in `block (2 levels) in remember_me' from /home/okeeblow/Works/DistorteD/CHECKING-YOU-OUT/lib/checking-you-out/ghost_revival.rb:19:in `loop' from /home/okeeblow/Works/DistorteD/CHECKING-YOU-OUT/lib/checking-you-out/ghost_revival.rb:19:in `block in remember_me' <internal:ractor>:583:in `send': The incoming-port is already closed (Ractor::ClosedError) from /home/okeeblow/Works/DistorteD/CHECKING-YOU-OUT/lib/checking-you-out/ghost_revival.rb:86:in `block in extended' from /home/okeeblow/Works/DistorteD/CHECKING-YOU-OUT/lib/checking-you-out/ghost_revival.rb:63:in `each' from /home/okeeblow/Works/DistorteD/CHECKING-YOU-OUT/lib/checking-you-out/ghost_revival.rb:63:in `each_with_object' from /home/okeeblow/Works/DistorteD/CHECKING-YOU-OUT/lib/checking-you-out/ghost_revival.rb:63:in `extended' from /home/okeeblow/Works/DistorteD/CHECKING-YOU-OUT/lib/checking-you-out/inner_spirit.rb:216:in `extend' from /home/okeeblow/Works/DistorteD/CHECKING-YOU-OUT/lib/checking-you-out/inner_spirit.rb:216:in `<top (required)>' from /home/okeeblow/Works/DistorteD/CHECKING-YOU-OUT/lib/checking-you-out.rb:10:in `require_relative' from /home/okeeblow/Works/DistorteD/CHECKING-YOU-OUT/lib/checking-you-out.rb:10:in `<top (required)>' from ./bin/checking-you-out:3:in `require_relative' from ./bin/checking-you-out:3:in `<main>' ./bin/checking-you-out ~/224031-dot-jpg 0.12s user 0.05s system 100% cpu 0.168 total ``` My new `Ox::Sax` Ractor example script's usage: ``` [okeeblow@emi#ox] ./examples/sax_ractor.rb Please provide the path to a `shared-mime-info` XML package and some media-type query arguments (e.g. 'image/jpeg') ``` ``` [okeeblow@emi#ox] ./examples/sax_ractor.rb /usr/share/mime/packages/freedesktop.org.xml Please provide some media-type query arguments (e.g. 'image/jpeg') ``` Finding all-extant types: ``` [okeeblow@emi#ox] ./examples/sax_ractor.rb /usr/share/mime/packages/freedesktop.org.xml image/jpeg font/ttf application/xhtml+xml image/x-pict Parallel Ractors ["Worker 0 gave us JPEG 影像 (image/jpeg) [.jpeg,.jpg,.jpe]", "Worker 1 gave us TrueType 字型 (font/ttf) [.ttf]", "Worker 2 gave us XHTML 網頁 (application/xhtml+xml) [.xhtml,.xht]", "Worker 3 gave us Macintosh Quickdraw/PICT 繪圖 (image/x-pict) [.pct,.pict,.pict1,.pict2]"] Serial Ractor "ONLY ONE OX gave us [#<CYO JPEG 影像 (image/jpeg) [.jpeg,.jpg,.jpe]>, #<CYO TrueType 字型 (font/ttf) [.ttf]>, #<CYO XHTML 網頁 (application/xhtml+xml) [.xhtml,.xht]>, #<CYO Macintosh Quickdraw/PICT 繪圖 (image/x-pict) [.pct,.pict,.pict1,.pict2]>]" ``` …and not finding invalid ones: ``` [okeeblow@emi#ox] ./examples/sax_ractor.rb /usr/share/mime/packages/freedesktop.org.xml lol/rofl fart/butt image/jpeg Parallel Ractors ["Worker 0 gave us nothing", "Worker 1 gave us nothing", "Worker 2 gave us JPEG 影像 (image/jpeg) [.jpeg,.jpg,.jpe]"] Serial Ractor "ONLY ONE OX gave us [nil, nil, #<CYO JPEG 影像 (image/jpeg) [.jpeg,.jpg,.jpe]>]" ``` Unit tests pass.
|
Let me do a little reading on actor before I merge this. |
|
After a quick scan of Ox code it looks like the only part that needs some work to be ractor (thread) safe is the cache code. Adding a mutex to that should take care of that though. From the ractor information it sounds like |
|
|
|
The check internal to ruby is fine. I was wondering more about the calling of I'm inclined to merge this MR and then make a second pass to finish it before doing a release. Let me look more carefully this evening. |
|
That check is in the part I linked, not in |
|
Yes, I got that. I was not concerned about that as there is nothing I can do about that. My concern was over whether |
|
Ah I get what you mean. I don't have enough experience with threads in Ruby to have a sense for how expensive |
|
I'll try some benchmarks. Might take a day or two though. |
|
Take all the time you like. Thanks for your attention to these Ruby 3 features :) |
|
Are you part of the Ruby support team? |
|
Nope, just a happy Ox user with some specific goals for my Ox-using library |
|
A data point for you. When parsing with the new parse on Oj placing a mutex around the cache (string cache so it's used more) takes the performance for 68% of the old to 81% so a significant drop in performance. I think there will need to be a check against the cache option to determine if the code is actually ractor_safe. |
|
I see. I have a lot to learn in that regard so I appreciate your patience with my amateur C changes :) |
|
We are all learning. You've taught me a bit about Ractor. Sharing what we knew help us all. |
For #277
I've been experimenting with
Ractors since upgrading to Ruby 3.0 for #275 but quickly ran intoRactor::UnsafeErrorwhen trying to callOx::sax_parsein anything except the mainRactor.Per Ruby's
RactorC extension documentation (link below):"By default, all C extensions are recognized as
Ractor-unsafe. If C extension becomesRactor-safe,the extension should call
rb_ext_ractor_safe(true)at theInit_function and all defined method marked asRactor-safe.Ractor-unsafe C-methods only been called from main-ractor. If non-main ractor calls it, thenRactor::UnsafeErroris raised."I don't like to open seemingly-large feature requests like this without making some attempt at it myself first,
and luckily it seems like
Ox::sax_parseJust Works™ since I marked itRactor-safe, even with theclass_cache. Confirming this safety, making any remaining changes toOx::Sax, and expanding this to the non-Saxparts ofOxare all unfortunately out of my depth as a n00b C coder, so I would appreciate if you could take this over if it interests you. I am happy with justSaxsupport since I have no current need for marshalling, but I imagine otherOxusers wouldn't be satisfied if stratified.In this commit:
rb_ext_ractor_safepreprocessor macro viahave_funcinextconf.rb.Init_Oxandox_sax_parseasRactor-safe.Ractor-basedOx::Saxexample exercising both parallel and serialOx::SaxhandlerRactors to parse data fromshared-mime-infoXML files many users likely already have on their systems.Official
Ractorinfo:Ractordocumentation: https://docs.ruby-lang.org/en/master/doc/ractor_md.htmlRactordocumention coveringrb_ext_ractor_safe: https://docs.ruby-lang.org/en/master/doc/extension_rdoc.html#label-Appendix+F.+Ractor+supportRactorC Extension from the creator ofRactorthat might serve as a useful example: https://github.com/ko1/ractor-tvarBlogs:
Before this change:
My new
Ox::SaxRactor example script's usage:Finding all-extant types:
…and not finding invalid ones:
Unit tests pass.