Class: Temporalio::Internal::Worker::WorkflowInstance::IllegalCallTracer
- Inherits:
-
Object
- Object
- Temporalio::Internal::Worker::WorkflowInstance::IllegalCallTracer
- Defined in:
- lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb
Overview
Class that installs TracePoint to disallow illegal calls.
Class Method Summary collapse
Instance Method Summary collapse
- #disable(&block) ⇒ Object
- #enable(&block) ⇒ Object
-
#initialize(illegal_calls) ⇒ IllegalCallTracer
constructor
Illegal calls are Hash[String, :all | Hash[Symbol, Bool]].
Constructor Details
#initialize(illegal_calls) ⇒ IllegalCallTracer
Illegal calls are Hash[String, :all | Hash[Symbol, Bool]]
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb', line 54 def initialize(illegal_calls) @tracepoint = TracePoint.new(:call, :c_call) do |tp| # Manual check for proper thread since we have seen issues in Ruby 3.2 where it leaks next unless Thread.current == @enabled_thread cls = tp.defined_class next unless cls.is_a?(Module) # Extract the class name from the defined class. This is more difficult than it seems because you have to # resolve the attached object of the singleton class. But in older Ruby (at least <= 3.1), the singleton # class of things like `Date` does not have `attached_object` so you have to fall back in these rare cases # to parsing the string output. Reaching the string parsing component is rare, so this should not have # significant performance impact. class_name = if cls.singleton_class? if cls.respond_to?(:attached_object) cls = cls.attached_object # steep:ignore next unless cls.is_a?(Module) cls.name.to_s else cls.to_s.delete_prefix('#<Class:').delete_suffix('>') end else cls.name.to_s end # Check if the call is considered illegal vals = illegal_calls[class_name] invalid_suffix = case vals when :all '' when Temporalio::Worker::IllegalWorkflowCallValidator disable do vals.block.call(Temporalio::Worker::IllegalWorkflowCallValidator::CallInfo.new( class_name:, method_name: tp.callee_id, trace_point: tp )) nil rescue Exception => e # rubocop:disable Lint/RescueException ", reason: #{e}" end else per_method = vals&.[](tp.callee_id) case per_method when true '' when Temporalio::Worker::IllegalWorkflowCallValidator disable do per_method.block.call(Temporalio::Worker::IllegalWorkflowCallValidator::CallInfo.new( class_name:, method_name: tp.callee_id, trace_point: tp )) nil rescue Exception => e # rubocop:disable Lint/RescueException ", reason: #{e}" end end end if invalid_suffix raise Workflow::NondeterminismError, "Cannot access #{class_name} #{tp.callee_id} from inside a " \ "workflow#{invalid_suffix}. If this is known to be safe, the code can be run in " \ 'a Temporalio::Workflow::Unsafe.illegal_call_tracing_disabled block.' end end end |
Class Method Details
.frozen_validated_illegal_calls(illegal_calls) ⇒ Object
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb', line 12 def self.frozen_validated_illegal_calls(illegal_calls) illegal_calls.to_h do |key, val| raise TypeError, 'Invalid illegal call map, top-level key must be a String' unless key.is_a?(String) # @type var fixed_val: :all | Worker::IllegalWorkflowCallValidator | Hash[Symbol, TrueClass | Worker::IllegalWorkflowCallValidator] # rubocop:disable Layout/LineLength fixed_val = case val when Temporalio::Worker::IllegalWorkflowCallValidator if sub_val.method_name raise ArgumentError, 'Top level IllegalWorkflowCallValidator instances cannot have method name' end val when Array val.to_h do |sub_val| case sub_val when Symbol [sub_val, true] when Temporalio::Worker::IllegalWorkflowCallValidator unless sub_val.method_name raise ArgumentError, 'IllegalWorkflowCallValidator instances in array for ' \ "#{key} must have a method name" end [sub_val.method_name, sub_val] else raise TypeError, 'Invalid illegal call array entry for ' \ "#{key}, each value must be a Symbol or an IllegalWorkflowCallValidator" end end.freeze when :all :all else raise TypeError, 'Invalid illegal call map, top-level value must be an Array or :all' end [key.frozen? ? key : key.dup.freeze, fixed_val] end.freeze end |
Instance Method Details
#disable(&block) ⇒ Object
131 132 133 134 135 136 137 138 |
# File 'lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb', line 131 def disable(&block) previous_thread = @enabled_thread @tracepoint.disable do block.call ensure @enabled_thread = previous_thread end end |
#enable(&block) ⇒ Object
120 121 122 123 124 125 126 127 128 129 |
# File 'lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb', line 120 def enable(&block) # We've seen leaking issues in Ruby 3.2 where the TracePoint inadvertently remains enabled even for threads # that it was not started on. So we will check the thread ourselves. @enabled_thread = Thread.current @tracepoint.enable do block.call ensure @enabled_thread = nil end end |