11require " ./lib_pcre2"
2- require " crystal/thread_local_value "
2+ require " crystal/value_with_finalizer "
33
44# :nodoc:
55module Regex::PCRE2
@@ -207,12 +207,14 @@ module Regex::PCRE2
207207 private def match_impl (str , byte_index , options )
208208 match_data = match_data(str, byte_index, options) || return
209209
210- ovector_count = LibPCRE2 .get_ovector_count(match_data)
210+ # We reuse the same `match_data` allocation, so we must reimplement the
211+ # behavior of pcre2_match_data_create_from_pattern (get_ovector_count always
212+ # returns 65535, aka the maximum).
213+ ovector_count = capture_count_impl &+ 1
211214 ovector = Slice .new(LibPCRE2 .get_ovector_pointer(match_data), ovector_count &* 2 )
212215
213216 # We need to dup the ovector because `match_data` is re-used for subsequent
214- # matches (see `@match_data`).
215- # Dup brings the ovector data into the realm of the GC.
217+ # matches. We only dup the match data (not everything).
216218 ovector = ovector.dup
217219
218220 ::Regex ::MatchData .new(self , @re , str, byte_index, ovector.to_unsafe, ovector_count.to_i32 &- 1 )
@@ -228,43 +230,43 @@ module Regex::PCRE2
228230
229231 class_getter match_context : LibPCRE2 ::MatchContext * do
230232 match_context = LibPCRE2 .match_context_create(nil )
231- LibPCRE2 .jit_stack_assign(match_context, - > (_data) { Regex :: PCRE2 .jit_stack }, nil )
233+ LibPCRE2 .jit_stack_assign(match_context, - > (_data) { current_jit_stack.value }, nil )
232234 match_context
233235 end
234236
235- # Returns a JIT stack that's shared in the current thread.
237+ # JIT stack is unique per thread.
236238 #
237- # Only a single `match` function can run per thread at any given time, so there
238- # can't be any concurrent access to the JIT stack.
239- @@jit_stack = Crystal ::ThreadLocalValue (LibPCRE2 ::JITStack * ).new
240-
241- def self.jit_stack
242- @@jit_stack .get do
243- LibPCRE2 .jit_stack_create(32_768 , 1_048_576 , nil ) || raise " Error allocating JIT stack"
244- end
239+ # Only a single `match` function can run per thread at any given time, so
240+ # there can't be any concurrent access to the JIT stack.
241+ thread_local(current_jit_stack : ::Crystal ::ValueWithFinalizer (::LibPCRE2 ::JITStack * )) do
242+ ptr = LibPCRE2 .jit_stack_create(32_768 , 1_048_576 , nil )
243+ raise RuntimeError .new(" Error allocating JIT stack" ) if ptr.null?
244+ ::Crystal ::ValueWithFinalizer .new(ptr, - > (value : ::LibPCRE2 ::JITStack * ) { LibPCRE2 .jit_stack_free(value) })
245245 end
246246
247- # Match data is shared per instance and thread.
247+ # Match data is unique per thread.
248248 #
249- # Match data contains a buffer for backtracking when matching in interpreted mode (non-JIT).
250- # This buffer is heap-allocated and should be re-used for subsequent matches.
251- @match_data = Crystal ::ThreadLocalValue (LibPCRE2 ::MatchData * ).new
252-
253- private def match_data
254- @match_data .get do
255- LibPCRE2 .match_data_create_from_pattern(@re , nil )
256- end
249+ # Match data contains a buffer for backtracking when matching in interpreted
250+ # mode (non-JIT). This buffer is heap-allocated and should be re-used for
251+ # subsequent matches.
252+ #
253+ # Only a single `match` function can run per thread at any given time, so
254+ # there can't be any concurrent access to the match data buffer.
255+ thread_local(current_match_data : ::Crystal ::ValueWithFinalizer (::LibPCRE2 ::MatchData * )) do
256+ # The ovector size is clamped to 65535 pairs; we declare the maximum because
257+ # we allocate the match data buffer once for the thread and need to adapt to
258+ # any regular expression.
259+ ptr = LibPCRE2 .match_data_create(65_535 , nil )
260+ raise RuntimeError .new(" Error allocating match data" ) if ptr.null?
261+ ::Crystal ::ValueWithFinalizer .new(ptr, - > (value : LibPCRE2 ::MatchData * ) { LibPCRE2 .match_data_free(value) })
257262 end
258263
259264 def finalize
260- @match_data .consume_each do |match_data |
261- LibPCRE2 .match_data_free(match_data)
262- end
263265 LibPCRE2 .code_free @re
264266 end
265267
266268 private def match_data (str , byte_index , options )
267- match_data = self .match_data
269+ match_data = Regex :: PCRE2 .current_match_data.value
268270 match_count = LibPCRE2 .match(@re , str, str.bytesize, byte_index, pcre2 _match_options(options), match_data, PCRE2 .match_context)
269271
270272 if match_count < 0
0 commit comments