Fiber-local state?

Trying to come up with a way to store state specific to a fiber that will get mopped up when the fiber completes. One thing I tried was to add ivars to Fiber, but that may have performance implications since that makes every fiber take up additional heap memory, including fibers that would not store anything in them, and more bytes per allocation requires more time (this surprised me when I discovered it). In the grand scheme of things, it might not have a huge impact on either allocation/GC time or memory used, but I haven’t benchmarked in a real application yet.

Another idea was to maintain fiber-local state in a separate data structure and monkeypatch Fiber#finalize to remove itself from it. The hard part about using this as a pattern is that other libraries may have to do it, too, so we would have to call previous_def to invoke a previously defined monkeypatch for it.

In order to support this pattern, though, Fiber would need to define a no-op finalize method. This could also have performance implications since the GC would then register a finalizer for every fiber, regardless of whether it needs it. I don’t know how much performance that requires.

Are there other ways to do fiber-local state that don’t have the potential to slow down an application?

I don’t think that adding a couple of ivar in the Fiber is a bad thing, but yeah, the penalty exists.
I think Fibers could be subclassed. I don’t recall anything in the runtime that forbids it.
I’m in favor of adding protected methods to reduce the monkey-patching needed.

Yet two other routes could be:

a) mimic Crystal::ThreadLocalValue for fibers, to have an external hash from fiber => value. you would still need a monkey-patch to release the entries when a fiber finishes.

b) (ab)use the fiber stack to store some data. There is memory available there… I guess it can be used. It would be like reserving part of the stack for additional fiber runtime memory. If so, either that data need to be added as gc root at before_collect or the stackbottom/top need to cover them to avoid collection. Also the stack are stored in a pool and are reused.

Could we implement point 1? I think the solution is really nice, and it would be nice if it were supported by the standard library.

That would probably be an excellent use case for nurseries that manage the lifecycle of fibers => https://github.com/crystal-lang/crystal/issues/6468

Apart from that, adding a custom ivar to Fiber shouldn’t have much impact. Your benchmark surely measures something, but it’s not influenced by allocation size. Replacing all classes with structs yields pretty similar results (I’ve left a comment on the gist).

1 Like

To be clear, this is needed because the hash is external, not part of the Fiber type? Otherwise the value would be GC’d automatically?

I’m also interested in this feature. For my DI shard I’m doing this at the moment:

# :nodoc:
class Fiber
  property container : ADI::ServiceContainer { ADI::ServiceContainer.new }
end

This works quite well, as i can then just have a class method that is just Fiber.current.container. However I imagine this has some perf overhead, so deff interested in something more official to handle this.

Exactly

1 Like