Skip to content

Commit ba7a72e

Browse files
committed
feat: add global MiniRacer pause gate
Add MiniRacer.pause/resume to quiesce operations process-wide with timeout handling and nested pauses. Expose PauseTimeoutError and opt-in Process._fork hooks so fork can wait for MiniRacer to drain before parent and child continue. Document the fork coordination APIs and cover pause, timeout, hook, and single-threaded fork behavior with tests.
1 parent 76568d3 commit ba7a72e

7 files changed

Lines changed: 985 additions & 56 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,17 @@ jobs:
3131
matrix:
3232
os:
3333
- "macos-latest"
34-
- "ubuntu-latest"
34+
- "ubuntu-24.04"
3535
ruby:
3636
- "truffleruby+graalvm"
3737

3838
name: ${{ matrix.os }} - ${{ matrix.ruby }}
3939
runs-on: ${{ matrix.os }}
4040
timeout-minutes: 10
4141

42+
env:
43+
TRUFFLERUBYOPT: "--jvm --polyglot"
44+
4245
steps:
4346
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v4
4447
with:

CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- Avoid finalizer hangs when a forked child garbage-collects a non-idle inherited `:single_threaded` context
1010
- Allow Ruby thread interrupts, process shutdown, and cross-thread `Context#dispose` to terminate busy `:single_threaded` JavaScript execution instead of hanging
1111
- Make `Context#dispose` while an attached Ruby callback is active either terminate safely or raise instead of deadlocking
12+
- Add `MiniRacer.pause(timeout:)` / `MiniRacer.resume` to quiesce MiniRacer globally, plus opt-in fork hooks built on that pause gate
1213

1314
- 0.21.2 - 11-06-2026
1415
- Add `Context#perform_microtask_checkpoint` to synchronously drain the V8 microtask queue, useful for spec-compliant `dispatchEvent` sequencing inside Ruby callbacks

README.md

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,41 @@ When using pre-fork `MiniRacer::Context` objects in `:single_threaded` mode,
143143
ensure the process only forks while MiniRacer is quiescent: no thread may be
144144
evaluating JavaScript, calling into a context, disposing/freeing a context,
145145
running a Ruby callback from JavaScript, or otherwise using MiniRacer at the
146-
instant of `fork`. In multi-threaded applications, guard all MiniRacer context
147-
operations and the `fork` itself with the same application-level lock. Forking
148-
while a MiniRacer operation is in progress can leave inherited pthread mutexes
149-
in an unusable state in the child process.
146+
instant of `fork`. Forking while a MiniRacer operation is in progress can leave
147+
inherited pthread mutexes in an unusable state in the child process.
148+
149+
`MiniRacer.pause(timeout:)` is a process-global quiesce gate. It prevents new
150+
MiniRacer operations from starting, waits for operations already in progress to
151+
finish, and then keeps MiniRacer paused until `MiniRacer.resume` is called.
152+
`timeout:` is in seconds; if MiniRacer cannot drain in time,
153+
`MiniRacer::PauseTimeoutError` is raised and the pause is rolled back. Omitting
154+
`timeout:` waits indefinitely, which is useful only when the caller knows active
155+
JavaScript cannot get stuck.
156+
157+
```ruby
158+
MiniRacer.pause(timeout: 5)
159+
begin
160+
pid = fork do
161+
MiniRacer.resume # child: reset inherited pause state
162+
# child process work
163+
end
164+
ensure
165+
MiniRacer.resume # parent: release the pause
166+
end
167+
```
168+
169+
For normal Ruby forks you can install an opt-in `Process._fork` hook which uses
170+
that same pause gate automatically:
171+
172+
```ruby
173+
MiniRacer.install_fork_hooks!(timeout: 5)
174+
```
175+
176+
The hook covers `Kernel#fork`, `Process.fork`, and `IO.popen("-")` on Rubies
177+
that expose `Process._fork`. It intentionally does not cover `Process.daemon` or
178+
raw native `fork(2)` calls from other C extensions. When the hook is installed,
179+
do not call `MiniRacer.resume` again in the child block; the hook already resumes
180+
in both parent and child before user child code runs.
150181

151182
If you want to ensure your application does not leak memory after fork either:
152183

0 commit comments

Comments
 (0)