temporalio / temporalio/sdk-ruby
[Bug] Some common uses of ActiveModel fail in workflows due to sync construct use
Nobody has claimed this yet.
- Dominant language
- Ruby
- Stars
- 204
- Forks
- 42
- Avg merge
- 1d 30m
- Merged PRs (30d)
- 28
Description
Describe the bug
Many common uses of ActiveModel use ConcurrentMap under the hood which uses Thread::Mutex which is forbidden in workflows.
We believe this can be replicated simply by accessing an attribute that doesn't exist. We also believe this can be replicated using a model set like:
module MyCompany
module Messages
class Foo
include Message
attribute :some_field_1, :string
attribute :some_field_2, :string
attribute :some_field_3, ObjectType.for(Array), :default => []
attribute :some_field_4, :boolean, :default => false
attribute :some_field_5, :boolean, :default => false
end
end
module Message
extend ActiveSupport::Concern
include ActiveModel::Model
include ActiveModel::Attributes
include ActiveModel::Serializers::JSON
included do
def as_json(options = {})
super(options).merge(::JSON.create_id => self.class.name)
end
end
class_methods do
def json_create(data)
new(**data.except(JSON.create_id))
end
end
end
end
This can give stack traces like:
W, [2025-10-24T12:22:16.291089 #70269] WARN -- : Cannot access Thread::Mutex synchronize from inside a workflow, reason: disallowed. If this is known to be safe, the code can be run in a Temporalio::Workflow::Unsafe.illegal_call_tracing_disabled block. {:attempt=>1, :namespace=>"default", :run_id=>"019a1773-c113-74ff-924d-7c51d86cd682", :task_queue=>"deliverable-orchestration", :workflow_id=>"59824eb54dc4fa489c15c372", :workflow_type=>"OrchestrationWorkflowV2"} (Temporalio::Workflow::NondeterminismError)
/path/to/gems/temporalio-0.6.0-arm64-darwin/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb:112:in `block in initialize'
/path/to/gems/concurrent-ruby-1.3.4/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb:25:in `compute_if_absent'
/path/to/gems/activemodel-6.1.7.10/lib/active_model/attribute_methods.rb:400:in `attribute_method_matchers_matching'
/path/to/gems/activemodel-6.1.7.10/lib/active_model/attribute_methods.rb:506:in `matched_attribute_method'
/path/to/gems/activemodel-6.1.7.10/lib/active_model/attribute_methods.rb:494:in `respond_to?'
/path/to/gems/activemodel-6.1.7.10/lib/active_model/attribute_assignment.rb:48:in `_assign_attribute'
/path/to/gems/activemodel-6.1.7.10/lib/active_model/attribute_assignment.rb:42:in `block in _assign_attributes'
/path/to/gems/activemodel-6.1.7.10/lib/active_model/attribute_assignment.rb:41:in `each'
/path/to/gems/activemodel-6.1.7.10/lib/active_model/attribute_assignment.rb:41:in `_assign_attributes'
/path/to/gems/activemodel-6.1.7.10/lib/active_model/attribute_assignment.rb:34:in `assign_attributes'
/path/to/gems/activemodel-6.1.7.10/lib/active_model/model.rb:81:in `initialize'
/path/to/gems/activemodel-6.1.7.10/lib/active_model/attributes.rb:77:in `initialize'
/path/to/models/my_models.rb:99:in `new'
And for cases where it's an invalid attribute access via method_missing, it can give traces like:
/path/to/gems/temporalio-1.0.0-arm64-darwin/lib/temporalio/internal/worker/workflow_instance/illegal_call_tracer.rb:112:in `block in initialize'
/path/to/gems/concurrent-ruby-1.3.4/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb:25:in `compute_if_absent'
/path/to/gems/activemodel-6.1.7.3/lib/active_model/attribute_methods.rb:400:in `attribute_method_matchers_matching'
/path/to/gems/activemodel-6.1.7.3/lib/active_model/attribute_methods.rb:506:in `matched_attribute_method'
/path/to/gems/activemodel-6.1.7.3/lib/active_model/attribute_methods.rb:468:in `method_missing'
my_workflow_class.rb:52:in `block in execute'
my_workflow_class.rb:39:in `loop'
ConcurrentMap use is so pervasive in ActiveModel, it is unreasonable for us to ask users not to use anything relying on it. Also, it may be unreasonable to just alter our illegal trace detector to check backtrace for the active model because the use of mutexes can cause an issue like we hit with loggers (see https://temporal.io/blog/temporal-ruby-crash-proof-fibers#implicitly-used-sync-constructs).
The best solution may be a WorkflowSafeObject type of mixin that surrounds every call with Workflow::Unsafe::durable_scheduler_disabled.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the shown ActiveModel::Model and ActiveModel::Attributes example inside a workflow and inspect the attribute_methods.rb and illegal_call_tracer.rb frames in the trace. Determine which ActiveModel operations invoke ConcurrentMap or Thread::Mutex, then define and verify workflow-safe behavior with regression coverage; done means common ActiveModel use no longer triggers the workflow nondeterminism error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ruby
- Domain
- backend, distributed-systems
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100