|
84 | 84 | # little non-robust to weird special cases like Main.eval being |
85 | 85 | # Base.MainInclude.eval.) |
86 | 86 |
|
| 87 | +# It appears we can't use a ReentrantLock here, as contention seems to lead to |
| 88 | +# deadlock. Perhaps because it triggers a task switch while compiling the |
| 89 | +# @generated function. |
| 90 | +_cache_lock = Threads.SpinLock() |
87 | 91 | _cachename = Symbol("#_RuntimeGeneratedFunctions_cache") |
88 | 92 | _tagname = Symbol("#_RuntimeGeneratedFunctions_ModTag") |
89 | 93 |
|
90 | 94 | function _cache_body(moduletag, id, body) |
91 | | - cache = getfield(parentmodule(moduletag), _cachename) |
92 | | - # Caching is tricky when `id` is the same for different AST instances: |
93 | | - # |
94 | | - # Tricky case #1: If a function body with the same `id` was cached |
95 | | - # previously, we need to use that older instance of the body AST as the |
96 | | - # canonical one rather than `body`. This ensures the lifetime of the |
97 | | - # body in the cache will always cover the lifetime of the parent |
98 | | - # `RuntimeGeneratedFunction`s when they share the same `id`. |
99 | | - # |
100 | | - # Tricky case #2: Unless we hold a separate reference to |
101 | | - # `cache[id].value`, the GC can collect it (causing it to become |
102 | | - # `nothing`). So root it in a local variable first. |
103 | | - # |
104 | | - cached_body = haskey(cache, id) ? cache[id].value : nothing |
105 | | - cached_body = cached_body !== nothing ? cached_body : body |
106 | | - # Use a WeakRef to allow `body` to be garbage collected. (After GC, the |
107 | | - # cache will still contain an empty entry with key `id`.) |
108 | | - cache[id] = WeakRef(cached_body) |
109 | | - return cached_body |
| 95 | + lock(_cache_lock) do |
| 96 | + cache = getfield(parentmodule(moduletag), _cachename) |
| 97 | + # Caching is tricky when `id` is the same for different AST instances: |
| 98 | + # |
| 99 | + # Tricky case #1: If a function body with the same `id` was cached |
| 100 | + # previously, we need to use that older instance of the body AST as the |
| 101 | + # canonical one rather than `body`. This ensures the lifetime of the |
| 102 | + # body in the cache will always cover the lifetime of the parent |
| 103 | + # `RuntimeGeneratedFunction`s when they share the same `id`. |
| 104 | + # |
| 105 | + # Tricky case #2: Unless we hold a separate reference to |
| 106 | + # `cache[id].value`, the GC can collect it (causing it to become |
| 107 | + # `nothing`). So root it in a local variable first. |
| 108 | + # |
| 109 | + cached_body = haskey(cache, id) ? cache[id].value : nothing |
| 110 | + cached_body = cached_body !== nothing ? cached_body : body |
| 111 | + # Use a WeakRef to allow `body` to be garbage collected. (After GC, the |
| 112 | + # cache will still contain an empty entry with key `id`.) |
| 113 | + cache[id] = WeakRef(cached_body) |
| 114 | + return cached_body |
| 115 | + end |
110 | 116 | end |
111 | 117 |
|
112 | 118 | function _lookup_body(moduletag, id) |
113 | | - getfield(parentmodule(moduletag), _cachename)[id].value |
| 119 | + lock(_cache_lock) do |
| 120 | + cache = getfield(parentmodule(moduletag), _cachename) |
| 121 | + cache[id].value |
| 122 | + end |
114 | 123 | end |
115 | 124 |
|
116 | 125 | function _ensure_cache_exists!(mod) |
117 | | - if !isdefined(mod, _cachename) |
118 | | - mod.eval(quote |
119 | | - const $_cachename = Dict() |
120 | | - struct $_tagname |
121 | | - end |
122 | | - end) |
| 126 | + lock(_cache_lock) do |
| 127 | + if !isdefined(mod, _cachename) |
| 128 | + mod.eval(quote |
| 129 | + const $_cachename = Dict() |
| 130 | + struct $_tagname |
| 131 | + end |
| 132 | + end) |
| 133 | + end |
123 | 134 | end |
124 | 135 | end |
125 | 136 |
|
|
0 commit comments