Module: PyCallThread
- Defined in:
- lib/pycall_thread.rb
Overview
Provides a way to run PyCall code from multiple threads, see PyCallThread.init and PyCallThread.run for more information.
Constant Summary collapse
- VALID_UNSAFE_RETURN_VALUES =
%i[allow error warn].freeze
Class Method Summary collapse
- .init(unsafe_return: :error, &require_pycall_block) ⇒ Object
- .pycall_thread_loop ⇒ Object
- .python_object?(obj) ⇒ Boolean
- .require_pycall(&require_pycall_block) ⇒ Object
-
.run(&block) ⇒ Object
Runs &block on the PyCall thread, and returns the result.
- .run_result(result) ⇒ Object
- .stop_pycall_thread ⇒ Object
Class Method Details
.init(unsafe_return: :error, &require_pycall_block) ⇒ Object
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# File 'lib/pycall_thread.rb', line 8 def self.init(unsafe_return: :error, &require_pycall_block) raise ArgumentError, "Invalid value for unsafe_return: #{unsafe_return}. Must be one of: #{VALID_UNSAFE_RETURN_VALUES.join(", ")}" unless VALID_UNSAFE_RETURN_VALUES.include?(unsafe_return) @unsafe_return = unsafe_return # Start the thread we will use to run code invoked with PyCallThread.run @py_thread = Thread.new { pycall_thread_loop } @initialized = true # If we've been passed a require_pycall_block, use that to require 'pycall' # instead of doing it directly. require_pycall(&require_pycall_block) at_exit { stop_pycall_thread } end |
.pycall_thread_loop ⇒ Object
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/pycall_thread.rb', line 76 def self.pycall_thread_loop Thread.current.name = "pycall" loop do block = @queue.pop break if block == :stop block.call rescue StandardError => e puts "pycall_thread_loop(): exception in pycall_thread_loop #{e}" puts e.backtrace.join("\n") end # If PyCall.finalize is not present, the main proces will hang at exit # See: https://github.com/mrkn/pycall.rb/pull/187 PyCall.finalize if PyCall.respond_to?(:finalize) end |
.python_object?(obj) ⇒ Boolean
94 95 96 97 98 99 100 101 102 103 |
# File 'lib/pycall_thread.rb', line 94 def self.python_object?(obj) [ PyCall::IterableWrapper, PyCall::PyObjectWrapper, PyCall::PyModuleWrapper, PyCall::PyObjectWrapper, PyCall::PyTypeObjectWrapper, PyCall::PyPtr ].any? { |kind| obj.is_a?(kind) } end |
.require_pycall(&require_pycall_block) ⇒ Object
24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/pycall_thread.rb', line 24 def self.require_pycall(&require_pycall_block) # Only safe to use PyCallThread if PyCall hasn't already been loaded raise "PyCall::LibPython already exists: PyCall can't have been initialized already" if defined?(PyCall::LibPython) run do # require 'pycall' or run a user-defined block that should do the same if require_pycall_block require_pycall_block.call else require "pycall" end nil end end |
.run(&block) ⇒ Object
Runs &block on the PyCall thread, and returns the result
40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/pycall_thread.rb', line 40 def self.run(&block) init unless @initialized result_queue = Queue.new @queue << lambda { begin result_queue << { retval: block.call } rescue StandardError => e result_queue << { exception: e } end } run_result(result_queue.pop) end |
.run_result(result) ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/pycall_thread.rb', line 55 def self.run_result(result) if result[:exception] raise result[:exception] elsif python_object?(result[:retval]) msg = "Trying to return a python object from a PyCallThread.run block is potentially not thread-safe. Please convert #{result.inspect} to a basic Ruby type (like string, array, number, boolean etc) before returning." case @unsafe_return when :error raise msg when :warn warn "Warning: #{msg}" end end result[:retval] end |
.stop_pycall_thread ⇒ Object
71 72 73 74 |
# File 'lib/pycall_thread.rb', line 71 def self.stop_pycall_thread @queue << :stop @py_thread.join end |