lib/pitchfork.rb in pitchfork-0.3.0 vs lib/pitchfork.rb in pitchfork-0.4.0

- old
+ new

@@ -119,24 +119,45 @@ raise end end def self.clean_fork(&block) - # We fork from a thread to start with a clean stack. - # If we didn't the base stack would grow after each refork - # putting an effective limit on the number of generations. - parent_thread = Thread.current - Thread.new do - current_thread = Thread.current - # We copy over any thread state it might have - parent_thread.keys.each do |key| - current_thread[key] = parent_thread[key] + if pid = Process.fork + return pid + end + + begin + # Pitchfork recursively refork the worker processes. + # Because of this we need to unwind the stack before resuming execution + # in the child, otherwise on each generation the available stack space would + # get smaller and smaller until it's basically 0. + # + # The very first version of this method used to call fork from a new + # thread, however this can cause issues with some native gems that rely on + # pthread_atfork(3) or pthread_mutex_lock(3), as the new main thread would + # now be different. + # + # A second version used to fork from a new fiber, but fibers have a much smaller + # stack space (https://bugs.ruby-lang.org/issues/3187), so it would break large applications. + # + # The latest version now use `throw` to unwind the stack after the fork, it however + # restrict it to be called only inside `handle_clean_fork`. + if Thread.current[:pitchfork_handle_clean_fork] + throw self, block + else + while block + block = catch(self) do + Thread.current[:pitchfork_handle_clean_fork] = true + block.call + nil + end + end end - parent_thread.thread_variables.each do |variable| - current_thread.thread_variable_set(variable, parent_thread.thread_variable_get(variable)) - end - Process.fork(&block) - end.value + rescue + abort + else + exit + end end def self.fork_sibling(&block) if REFORKING_AVAILABLE # We double fork so that the new worker is re-attached back