diff --git a/src/crystal/system/thread_local.cr b/src/crystal/system/thread_local.cr index fa651de00997..19333bbafd40 100644 --- a/src/crystal/system/thread_local.cr +++ b/src/crystal/system/thread_local.cr @@ -1,20 +1,20 @@ class Thread struct Local(T) # Reserves space for saving a `T` reference on each thread. - def initialize - {% unless T < Reference || T < Pointer || T.union_types.all? { |t| t == Nil || t < Reference } %} - {% raise "Can only create Thread::Local with reference types, nilable reference types, or pointer types, not {{T}}" %} - {% end %} - end + # def self.new + # {% unless T < Reference || T < Pointer || T.union_types.all? { |t| t == Nil || t < Reference } %} + # {% raise "Can only create Thread::Local with reference types, nilable reference types, or pointer types, not {{T}}" %} + # {% end %} + # end # Reserves space for saving a `T` reference on each thread and registers a # destructor that will be called when a thread terminates if the local value # has been set. - def initialize(&destructor : Proc(T, Nil)) - {% unless T < Reference || T < Pointer || T.union_types.all? { |t| t == Nil || t < Reference } %} - {% raise "Can only create Thread::Local with reference types, nilable reference types, or pointer types, not {{T}}" %} - {% end %} - end + # def self.new(&destructor : Proc(T, Nil)) + # {% unless T < Reference || T < Pointer || T.union_types.all? { |t| t == Nil || t < Reference } %} + # {% raise "Can only create Thread::Local with reference types, nilable reference types, or pointer types, not {{T}}" %} + # {% end %} + # end # Returns the current local value for the thread; if unset constructs one by # yielding and sets it as the current local value. diff --git a/src/crystal/system/unix/thread_local.cr b/src/crystal/system/unix/thread_local.cr index 92eeaa5a9437..b7041a55b5b2 100644 --- a/src/crystal/system/unix/thread_local.cr +++ b/src/crystal/system/unix/thread_local.cr @@ -2,22 +2,22 @@ require "c/pthread" class Thread struct Local(T) - @key = uninitialized LibC::PthreadKeyT - - def initialize - previous_def - @key = pthread_key_create(nil) + def self.new : self + err = LibC.pthread_key_create(out key, nil) + raise RuntimeError.from_os_error("pthread_key_create", Errno.new(err)) unless err == 0 + new(key) end - def initialize(&destructor : Proc(T, Nil)) - previous_def(&destructor) - @key = pthread_key_create(destructor.unsafe_as(Proc(Void*, Nil))) + def self.new(&destructor : Proc(T, Nil)) : self + err = LibC.pthread_key_create(out key, destructor.unsafe_as(Proc(Void*, Nil))) + raise RuntimeError.from_os_error("pthread_key_create", Errno.new(err)) unless err == 0 + new(key) end - private def pthread_key_create(destructor) - err = LibC.pthread_key_create(out key, destructor) - raise RuntimeError.from_os_error("pthread_key_create", Errno.new(err)) unless err == 0 - key + def initialize(@key : LibC::PthreadKeyT) + {% unless T < Reference || T < Pointer || T.union_types.all? { |t| t == Nil || t < Reference } %} + {% raise "Can only create Thread::Local with reference types, nilable reference types, or pointer types, not {{T}}" %} + {% end %} end def get? : T? diff --git a/src/crystal/system/wasi/thread_local.cr b/src/crystal/system/wasi/thread_local.cr index c03997493626..4de4edc3170c 100644 --- a/src/crystal/system/wasi/thread_local.cr +++ b/src/crystal/system/wasi/thread_local.cr @@ -2,12 +2,14 @@ class Thread struct Local(T) @value : T? - def initialize - previous_def + def self.new(&destructor : T ->) : self + new end - def initialize(&destructor : T ->) - previous_def(&destructor) + def initialize + {% unless T < Reference || T < Pointer || T.union_types.all? { |t| t == Nil || t < Reference } %} + {% raise "Can only create Thread::Local with reference types, nilable reference types, or pointer types, not {{T}}" %} + {% end %} end def get? : T? diff --git a/src/crystal/system/win32/thread_local.cr b/src/crystal/system/win32/thread_local.cr index e98e8a9f7267..aa417ae820ba 100644 --- a/src/crystal/system/win32/thread_local.cr +++ b/src/crystal/system/win32/thread_local.cr @@ -2,22 +2,22 @@ require "c/fibersapi" class Thread struct Local(T) - @key = uninitialized LibC::DWORD - - def initialize - previous_def - @key = fls_alloc(nil) + def self.new : self + key = LibC.FlsAlloc(nil) + raise RuntimeError.from_winerror("FlsAlloc: out of indexes") if key == LibC::FLS_OUT_OF_INDEXES + new(key) end - def initialize(&destructor : Proc(T, Nil)) - previous_def(&destructor) - @key = fls_alloc(destructor.unsafe_as(LibC::FLS_CALLBACK_FUNCTION)) + def self.new(&destructor : Proc(T, Nil)) + key = LibC.FlsAlloc(destructor.unsafe_as(LibC::FLS_CALLBACK_FUNCTION)) + raise RuntimeError.from_winerror("FlsAlloc: out of indexes") if key == LibC::FLS_OUT_OF_INDEXES + new(key) end - private def fls_alloc(destructor) - key = LibC.FlsAlloc(destructor) - raise RuntimeError.from_winerror("FlsAlloc: out of indexes") if key == LibC::FLS_OUT_OF_INDEXES - key + def initialize(@key : LibC::DWORD) + {% unless T < Reference || T < Pointer || T.union_types.all? { |t| t == Nil || t < Reference } %} + {% raise "Can only create Thread::Local with reference types, nilable reference types, or pointer types, not {{T}}" %} + {% end %} end def get? : T? diff --git a/src/regex/lib_pcre2.cr b/src/regex/lib_pcre2.cr index 6f45a4465219..f6234e30b3c1 100644 --- a/src/regex/lib_pcre2.cr +++ b/src/regex/lib_pcre2.cr @@ -264,10 +264,12 @@ lib LibPCRE2 fun jit_stack_create = pcre2_jit_stack_create_8(startsize : LibC::SizeT, maxsize : LibC::SizeT, gcontext : GeneralContext*) : JITStack* fun jit_stack_assign = pcre2_jit_stack_assign_8(mcontext : MatchContext*, callable_function : Void* -> JITStack*, callable_data : Void*) : Void + fun jit_stack_free = pcre2_jit_stack_free_8(jit_stack : JITStack*) : Void fun pattern_info = pcre2_pattern_info_8(code : Code*, what : UInt32, where : Void*) : Int fun match = pcre2_match_8(code : Code*, subject : UInt8*, length : LibC::SizeT, startoffset : LibC::SizeT, options : UInt32, match_data : MatchData*, mcontext : MatchContext*) : Int + fun match_data_create = pcre2_match_data_create_8(ovecsize : UInt32, gcontext : GeneralContext*) : MatchData* fun match_data_create_from_pattern = pcre2_match_data_create_from_pattern_8(code : Code*, gcontext : GeneralContext*) : MatchData* fun match_data_free = pcre2_match_data_free_8(match_data : MatchData*) : Void diff --git a/src/regex/pcre2.cr b/src/regex/pcre2.cr index b56a4ea68839..ca29dd38f46f 100644 --- a/src/regex/pcre2.cr +++ b/src/regex/pcre2.cr @@ -1,5 +1,5 @@ require "./lib_pcre2" -require "crystal/thread_local_value" +require "crystal/system/thread_local" # :nodoc: module Regex::PCRE2 @@ -207,12 +207,14 @@ module Regex::PCRE2 private def match_impl(str, byte_index, options) match_data = match_data(str, byte_index, options) || return - ovector_count = LibPCRE2.get_ovector_count(match_data) + # we reuse the same matchdata allocation, so we must "reimplement" the + # behavior of pcre2_match_data_create_from_pattern (get_ovector_count always + # returns 65535, aka the maximum): + ovector_count = capture_count_impl &+ 1 ovector = Slice.new(LibPCRE2.get_ovector_pointer(match_data), ovector_count &* 2) # We need to dup the ovector because `match_data` is re-used for subsequent - # matches (see `@match_data`). - # Dup brings the ovector data into the realm of the GC. + # matches. Dup brings the ovector data into the realm of the GC. ovector = ovector.dup ::Regex::MatchData.new(self, @re, str, byte_index, ovector.to_unsafe, ovector_count.to_i32 &- 1) @@ -228,43 +230,52 @@ module Regex::PCRE2 class_getter match_context : LibPCRE2::MatchContext* do match_context = LibPCRE2.match_context_create(nil) - LibPCRE2.jit_stack_assign(match_context, ->(_data) { Regex::PCRE2.jit_stack }, nil) + LibPCRE2.jit_stack_assign(match_context, ->(_data) { current_jit_stack }, nil) match_context end - # Returns a JIT stack that's shared in the current thread. + # JIT stack is unique per thread. # - # Only a single `match` function can run per thread at any given time, so there - # can't be any concurrent access to the JIT stack. - @@jit_stack = Crystal::ThreadLocalValue(LibPCRE2::JITStack*).new + # Only a single `match` function can run per thread at any given time, so + # there can't be any concurrent access to the JIT stack. + protected class_getter(jit_stack) do + Thread::Local(LibPCRE2::JITStack*).new do |jit_stack| + LibPCRE2.jit_stack_free(jit_stack) + end + end - def self.jit_stack - @@jit_stack.get do + protected def self.current_jit_stack + jit_stack.get do LibPCRE2.jit_stack_create(32_768, 1_048_576, nil) || raise "Error allocating JIT stack" end end - # Match data is shared per instance and thread. + # Match data is unique per thread. # - # Match data contains a buffer for backtracking when matching in interpreted mode (non-JIT). - # This buffer is heap-allocated and should be re-used for subsequent matches. - @match_data = Crystal::ThreadLocalValue(LibPCRE2::MatchData*).new + # Match data contains a buffer for backtracking when matching in interpreted + # mode (non-JIT). This buffer is heap-allocated and should be re-used for + # subsequent matches. + protected class_getter(match_data) do + Thread::Local(LibPCRE2::MatchData*).new do |match_data| + LibPCRE2.match_data_free(match_data) + end + end - private def match_data - @match_data.get do - LibPCRE2.match_data_create_from_pattern(@re, nil) + protected def self.current_match_data : LibPCRE2::MatchData* + match_data.get do + # the ovector size is clamped to 65535 pairs; we declare the maximum + # because we allocate the match data buffer once for the thread and need + # to adapt to any regular expression + LibPCRE2.match_data_create(65_535, nil) end end def finalize - @match_data.consume_each do |match_data| - LibPCRE2.match_data_free(match_data) - end LibPCRE2.code_free @re end private def match_data(str, byte_index, options) - match_data = self.match_data + match_data = Regex::PCRE2.current_match_data match_count = LibPCRE2.match(@re, str, str.bytesize, byte_index, pcre2_match_options(options), match_data, PCRE2.match_context) if match_count < 0