|
6 | 6 | require "set" |
7 | 7 | require "weakref" |
8 | 8 |
|
| 9 | +# Fiber-local variable to track when we're in a transfer operation: |
| 10 | +Fiber.attr_accessor :async_safe_transfer |
| 11 | + |
9 | 12 | module Async |
10 | 13 | module Safe |
11 | 14 | # Raised when an object is accessed from a different fiber than the one that owns it. |
@@ -95,18 +98,23 @@ def transfer(*objects) |
95 | 98 | current = Fiber.current |
96 | 99 | visited = Set.new |
97 | 100 |
|
98 | | - # Traverse object graph (outside mutex to avoid deadlock): |
99 | | - objects.each do |object| |
100 | | - traverse_objects(object, visited) |
101 | | - end |
102 | | - |
103 | | - # Transfer all visited objects (convert to array to avoid triggering TracePoint in sync block): |
104 | | - objects_to_transfer = visited.to_a |
| 101 | + # Disable tracking during traversal to avoid deadlock: |
| 102 | + current.async_safe_transfer = true |
105 | 103 |
|
106 | | - @mutex.synchronize do |
107 | | - objects_to_transfer.each do |object| |
108 | | - @owners[object] = current if @owners.key?(object) |
| 104 | + begin |
| 105 | + # Traverse object graph: |
| 106 | + objects.each do |object| |
| 107 | + traverse_objects(object, visited) |
109 | 108 | end |
| 109 | + |
| 110 | + # Transfer all visited objects: |
| 111 | + @mutex.synchronize do |
| 112 | + visited.each do |object| |
| 113 | + @owners[object] = current if @owners.key?(object) |
| 114 | + end |
| 115 | + end |
| 116 | + ensure |
| 117 | + current.async_safe_transfer = false |
110 | 118 | end |
111 | 119 | end |
112 | 120 |
|
@@ -138,6 +146,9 @@ def transfer(*objects) |
138 | 146 | # |
139 | 147 | # @parameter trace_point [TracePoint] The trace point containing access information. |
140 | 148 | def check_access(trace_point) |
| 149 | + # Skip if we're in a transfer operation: |
| 150 | + return if Fiber.current.async_safe_transfer |
| 151 | + |
141 | 152 | object = trace_point.self |
142 | 153 |
|
143 | 154 | # Skip tracking class/module methods: |
|
0 commit comments