Class: Temporalio::Internal::Worker::WorkflowInstance::IllegalCallTracer

Inherits:
Object
  • Object
show all
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

Constructor Details

#initialize(illegal_calls) ⇒ IllegalCallTracer

Illegal calls are Hash[String, :all | Hash[Symbol, Bool]]



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb', line 37

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.
    cls_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[cls_name]
    if vals == :all || vals&.[](tp.callee_id) # steep:ignore
      raise Workflow::NondeterminismError,
            "Cannot access #{cls_name} #{tp.callee_id} from inside a " \
            'workflow. 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



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb', line 11

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 | Hash[Symbol, bool]
    fixed_val = case val
                when Array
                  val.to_h do |sub_val|
                    unless sub_val.is_a?(Symbol)
                      raise TypeError,
                            'Invalid illegal call map, each value must be a Symbol'
                    end

                    [sub_val, true]
                  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



85
86
87
88
89
90
91
92
# File 'lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb', line 85

def disable(&block)
  previous_thread = @enabled_thread
  @tracepoint.disable do
    block.call
  ensure
    @enabled_thread = previous_thread
  end
end

#enable(&block) ⇒ Object



74
75
76
77
78
79
80
81
82
83
# File 'lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb', line 74

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