-
In Falcon, I want to read a unique value from every request header and then later add it to outbound http requests. With an existing code base, I would have to touch a lot of files in order to read and pass this value to classes which execute outbound requests. This is primarily for logging. Is there a safe and simple way to accomplish this without modifying lots of code and crossing values between fibers? Note: With Rails, I've used the RequestStore gem to accomplish thread-local variables per request (which sets Thread.current on a new rack request and clears when the rack request finishes). What's not clear to me is, in Falcon would a similar approach work if a request came in along with an asynchronous outbound http requests. Is this approach thread safe with Falcon? ADDITIONAL DETAILS: (optional)
At each incoming/outgoing request, I want log a unique request ID so I when searching quest logs, I can trace the request from end-to-end. |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 16 replies
-
That gem will not work with Falcon correctly. What we need is inheritable fiber locals. This is something I am considering at the moment. cc @eregon I completely agree that this should (1) be possible and (2) work in a logical way. The problem right now is there is no native interface for how things get inherited between threads, e.g. Thread.current[:inherited_x] = 1
parent = Thread.current
Thread.new do
# Some kind of implicit way to do this:
Thread.current[:inherited_x] = parent[:inherited_x]
end The same applies to Fibers. I believe we may need to introduce some standard way of doing this. A kind of inherited thread local or fiber local. Threads and/or fibers inherit the scope of the thread/fiber they are created in. This would be limited to specific locals. Right now, such functionality does not exist. HOWEVER. Because of the design of the fiber scheduler, I believe we can monkey patch this. class Scheduler
def fiber(...)
locals = Fiber.current.inheritable_locals
fiber = Fiber.new{Fiber.current.inheritable_locals = locals; ...}
end
end I am currently experimenting with such a design in Ruby 3. The UX is unclear to me (how to define inheritable locals), but I believe we can at least experiment with it. One idea I've been exploring is having explicit attributes, i.e. class Fiber
attr :my_local
end
# Perhaps we could introduce the following:
class Fiber
inheritable_attr :my_local
end For your use case, you'd end up with something like Fiber.current.my_local = 10
Fiber.new do
# Fiber.current.my_local is 10 here, inherited at point of calling `new`.
end By this design, we have a generic way for people to define inheritable attributes which are scoped to the task that creates them, and all children (i.e. web requests, and so on) that are performed by them. |
Beta Was this translation helpful? Give feedback.
-
Here is a working proof of concept.. a total hack too haha: require 'fiber'
module Inheritable
def initialize(**options)
super(**options)
self.inherit_attributes_from(Fiber.current)
end
def self.prepended(klass)
klass.extend(Singleton)
klass.instance_variable_set(:@inheritable_attributes, Hash.new)
end
module Singleton
def inheritable(key, default: nil)
@inheritable_attributes[:"@#{key}"] = default
end
def inheritable_attributes
@inheritable_attributes
end
end
def inherit_attributes_from(fiber)
self.class.inheritable_attributes.each do |name, default|
value = fiber.instance_variable_get(name) || default
self.instance_variable_set(name, value)
end
end
end
Fiber.prepend(Inheritable)
class Fiber
attr_accessor :x
inheritable :x
end
Fiber.current.x = 10
f = Fiber.new do
puts Fiber.current.x
end
f.transfer @eregon what do you think? |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
@ioquatix - the Inheritable example worked great. For my version (Ruby 2.6) I had to modify the initialize. module Inheritable
def initialize
super
self.inherit_attributes_from(Fiber.current)
end
#...
end Everything else from your suggested example appears to be working great. I even did some testing using siege stress tester and logging multiple request IDs to see if I could find any leakage of requests into other requests and after doing 10 seconds of 194 requests per second with a nested async fibers (which included random delays), it seems to work. Thanks for the help. If considering this as a permanent solution to the library, I personally think using |
Beta Was this translation helpful? Give feedback.
-
I see this is already answered, but I was looking through here and I thought I'd comment about how Twitter solves this problem. Basically, they have this concept of Locals which is essentially a thread local key-value store that can be loaded and saved. Additionally, keys can be In practice, these are used to solve the problem you propose. They sort of "follow" the chain of continuations always ensuring that, for instance, all code handling the same request is run with the same Anyway, food for thought! |
Beta Was this translation helpful? Give feedback.
Here is a working proof of concept.. a total hack too haha: