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