Skip to content

Commit 47e0fb2

Browse files
authored
Add thread_local macro (#16173)
This is a simple abstraction over the `ThreadLocal` annotation (for quick access) that also injects a reference into `Thread.current` to keep the value visible to the GC (that can't scan `ThreadLocal` values). There is no support for destructors. When needed just use a class with a finalizer: when the Thread is collected the thread local value will also be collected, which will run the finalizer.
1 parent 1eeacb2 commit 47e0fb2

File tree

1 file changed

+54
-0
lines changed

1 file changed

+54
-0
lines changed

src/object.cr

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,4 +707,58 @@ class Object
707707
ptr.as(Pointer(typeof(crystal_instance_type_id))).value = crystal_instance_type_id
708708
ptr
709709
end
710+
711+
# :nodoc:
712+
#
713+
# Generates a class getter with a lazy initializer for a thread local
714+
# variable.
715+
#
716+
# Unlike the `ThreadLocal` annotation, the value is guaranteed to always be
717+
# reachable by the GC that won't collect it until it's really unreachable. We
718+
# achieve that by using the `ThreadLocal` annocation (unreachable by GC) to
719+
# speedup direct accesses while still keeping a direct reference on the
720+
# current `Thread` object whose lifetime outlives the actual thread.
721+
#
722+
# For example:
723+
#
724+
# ```
725+
# class Foo
726+
# thread_local(instance : Foo) { Foo.new }
727+
# end
728+
#
729+
# # thread 1:
730+
# Foo.instance # => <Foo:0x76a066968000>
731+
# Foo.instance # => <Foo:0x76a066968000>
732+
#
733+
# # thread 2:
734+
# Foo.instance # => <Foo:0x76a06116ae30>
735+
# ```
736+
macro thread_local(decl, &constructor)
737+
{% raise "The thread_local macro expects a TypeDeclaration" unless decl.is_a?(TypeDeclaration) %}
738+
{% name = decl.var.id %}
739+
{% tls_name = "__tls_#{@type.name.gsub(/:/, "_")}__#{name}".id %}
740+
{% emulated_tls = flag?(:android) || flag?(:openbsd) || (flag?(:win32) && flag?(:gnu)) %}
741+
742+
{% unless emulated_tls %}
743+
@[ThreadLocal]
744+
@@{{name}} : {{decl.type}} | Nil
745+
{% end %}
746+
747+
def self.{{name}} : {{decl.type}}
748+
{% if emulated_tls %}
749+
Thread.current.{{tls_name}} ||= {{yield}}
750+
{% else %}
751+
if (value = @@{{name}}).nil?
752+
Thread.current.{{tls_name}} = @@{{name}} = {{yield}}
753+
else
754+
value
755+
end
756+
{% end %}
757+
end
758+
759+
class ::Thread
760+
# :nodoc:
761+
property {{tls_name}} : {{decl.type}} | Nil
762+
end
763+
end
710764
end

0 commit comments

Comments
 (0)