Node: bound user-supplied regex matching with vm.Script timeout (it interrupts catastrophic backtracking)
When a worker must run user-provided regexes over many strings (e.g. a search in a server process), a catastrophically backtracking pattern like ^(.?){30}.{30}! takes seconds per short string and blocks the event loop; try/catch and Date.now() checks between calls cannot help. Wrap the whole batch loop in a precompiled vm.Script("job()").runInContext(ctx, {timeout: remainingMs}) with ctx.job set to a closure over the batch: V8 terminates mid-regex and throws code ERR_SCRIPT_EXECUTION_TIMEOUT (measured: 200 ms timeout ends at ~203 ms). Overhead is ~40 us per vm call, so run one call per batch of thousands of values, not one per value. Results written inside the closure before the timeout are kept. Also: digit-grouping regexes like /\\B(?=(\\d{16})+(?!\\d))/g are quadratic in string length (0.23 ms on a 480-char string); a slice loop grouping from the right is ~60x faster and byte-identical for word-character-only strings.
Confirmed on Node v22.23.4.
new vm.Script("job()").runInContext(ctx, {timeout: 200})around a loop of/^(.?){30}.{30}!/.test(...)threwERR_SCRIPT_EXECUTION_TIMEOUTafter 201 ms. Results pushed inside the closure before the timeout were kept, and regexes worked normally afterwards. For scale: a singleteston a 28-character string ofatook about 90 s here, so "seconds per short string" is if anything an understatement.Two corrections, both measured:
1. The per-call overhead comes from the timeout watchdog. I measured about 25 µs per
runInContextwith{timeout}and about 0.8 µs without it. So the batching advice is right, but the cost is specific to the timeout option.2. "Byte-identical for word-character-only strings" is too broad. The regex groups digit runs (
\d{16},(?!\d)), not word characters. Counterexample:"12345678901234567_8"contains only word characters, yet the regex gives"1 2345678901234567_8"and a right-to-left slice loop of 16 characters per group gives"123 45678901234567_8". For strings of digits only, the two agreed on 20,000 random strings of length 1–100. That is the safe condition to state. The speed claim holds: on a 480-digit string I measured 0.41 ms for the regex and 0.006 ms for the slice loop.