Skip to content

Ruby: Release the GVL during native parsing - #2263

Open
joelhawksley wants to merge 1 commit into
marcoroth:mainfrom
joelhawksley:joelhawksley-release-gvl-during-parse
Open

Ruby: Release the GVL during native parsing#2263
joelhawksley wants to merge 1 commit into
marcoroth:mainfrom
joelhawksley:joelhawksley-release-gvl-during-parse

Conversation

@joelhawksley

Copy link
Copy Markdown
Contributor

Summary

Release Ruby's GVL while herb_parse processes a template so independent parses can run concurrently in Ruby threads.

This came out of profiling Action View template precompilation with ReActionView. Native parsing was a significant hotspot, but threaded precompilation could not parallelize it while the extension held the GVL.

Implementation

  • run herb_parse through rb_thread_call_without_gvl
  • temporarily lock mutable Ruby strings while the native parser reads their buffers
  • skip temporary locking for nil and frozen inputs
  • use rb_ensure to unlock the source and free the parser arena if parsing or Ruby object conversion raises
  • add a regression test that observes the parser thread outside the GVL and confirms mutable input is unlocked afterward

Only the native parse runs without the GVL. Converting the C AST into Ruby objects still runs with the GVL, so this improves threaded throughput without claiming linear scaling.

Benchmark

I measured precompilation of 100 synthetic 100-line Action View templates using ReActionView/Herb with four threads on Ruby 3.4.10 (arm64 macOS). Herb location tracking and HTML parsing were disabled to match the rendering-only fast path under investigation.

In an alternating five-run comparison, patched Herb had an 89 ms median versus 120 ms for stock Herb. A separate, steadier stock series had a 104 ms median, putting the observed wall-clock improvement at roughly 14-26%. These short runs are noisy, so the range is more representative than a single percentage.

Validation

  • Ruby 4.0.2 full test suite: 3,621 runs, 9,569 assertions, 0 failures, 0 errors
  • clang-format --dry-run --Werror ext/herb/extension.c
  • regression coverage for GVL release, mutable-string unlocking, frozen strings, and nil input through the existing parser suite

Run the native parser without the Ruby GVL so independent parses can execute concurrently. Temporarily lock mutable source strings while native parsing reads their buffers, and ensure cleanup on exceptions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 63a5a5bc-fa32-4a66-8dcd-944e49c575c6
@github-actions github-actions Bot added parser HTML+ERB parser c C source for the core parser, lexer, and AST c-extension Ruby C extension in ext/ labels Aug 17, 2026
@joelhawksley
joelhawksley marked this pull request as ready for review August 17, 2026 20:22
@marcoroth marcoroth changed the title Release the GVL during native parsing Ruby: Release the GVL during native parsing Aug 18, 2026
@marcoroth marcoroth added ruby Ruby source for the gem and its libraries rubygem The herb RubyGem and its packaging labels Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c C source for the core parser, lexer, and AST c-extension Ruby C extension in ext/ parser HTML+ERB parser ruby Ruby source for the gem and its libraries rubygem The herb RubyGem and its packaging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants