DEV: Make Ruby services thread-safe

A previous refactor of the `Service::Base::Step` class introduced a
non thread-safe behavior. `#call` mutates instance variables at runtime,
and since a step instance is the same for any given service class, this
can sometimes lead to `context` being the wrong one for the running
service.

This patch makes use of `Concurrent::ThreadLocalVar` to fix the issue.
This commit is contained in:
Loïc Guitaut
2025-02-07 17:04:11 +01:00
committed by Loïc Guitaut
parent 87f8845940
commit a4d34d60e3
2 changed files with 39 additions and 7 deletions

View File

@ -129,16 +129,16 @@ module Service
# @!visibility private
class Step
attr_reader :name, :method_name, :class_name, :instance, :context
attr_reader :name, :method_name, :class_name
def initialize(name, method_name = name, class_name: nil)
@name = name
@method_name = method_name
@class_name = class_name
@name, @method_name, @class_name = name, method_name, class_name
@instance = Concurrent::ThreadLocalVar.new
@context = Concurrent::ThreadLocalVar.new
end
def call(instance, context)
@instance, @context = instance, context
@instance.value, @context.value = instance, context
context[result_key] = Context.build
with_runtime { run_step }
end
@ -147,6 +147,10 @@ module Service
"result.#{type}.#{name}"
end
def instance = @instance.value
def context = @context.value
private
def run_step
@ -252,6 +256,7 @@ module Service
attr_reader :steps
def initialize(&block)
super("")
@steps = []
instance_exec(&block)
end
@ -268,8 +273,8 @@ module Service
attr_reader :steps, :keys
def initialize(*keys, &block)
super(keys.join(":"))
@keys = keys
@name = keys.join(":")
@steps = []
instance_exec(&block)
end
@ -308,7 +313,7 @@ module Service
attr_reader :steps, :exceptions
def initialize(exceptions, &block)
@name = "default"
super("default")
@steps = []
@exceptions = exceptions.presence || [StandardError]
instance_exec(&block)