async-safe 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/context/getting-started.md +32 -0
- data/lib/async/safe/builtins.rb +28 -0
- data/lib/async/safe/class.rb +25 -2
- data/lib/async/safe/monitor.rb +43 -5
- data/lib/async/safe/version.rb +1 -1
- data/readme.md +7 -0
- data/releases.md +7 -0
- data.tar.gz.sig +0 -0
- metadata +1 -1
- metadata.gz.sig +1 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7859608fbc62080a81bb9f20ce2c9ffe8f299f76432d1ec9f4a3d4aa47291240
|
4
|
+
data.tar.gz: e9a4f441e4ccdbfe0f086d6df486f22c7aa9504d07f42128689f54fd1befad4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12d90b93da4da2d525d6bf9e14acf235f0a2c50bc8301cd265b88f333a9d8c9af55ed0a7d69cb992239af715f5cd680650de0d2b111fb474c3a487f1007acfdb
|
7
|
+
data.tar.gz: 84d82aa42d46b54494ac5f4c8b025c80f41b2c8ccb7e1412d89a5d1073c662f9b86c7a84dce7c782c73e97164fc8a2df0a5d064bb4dac83a9e0de3d7a85cc264
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/context/getting-started.md
CHANGED
@@ -163,6 +163,38 @@ Fiber.schedule do
|
|
163
163
|
end
|
164
164
|
~~~
|
165
165
|
|
166
|
+
### Deep Transfer with Traversal
|
167
|
+
|
168
|
+
By default, `transfer` only transfers the object itself (shallow). For collections like `Array`, `Hash`, and `Set`, the gem automatically traverses and transfers contained objects:
|
169
|
+
|
170
|
+
~~~ ruby
|
171
|
+
bodies = [Body.new, Body.new]
|
172
|
+
|
173
|
+
Async::Safe.transfer(bodies) # Transfers array AND all bodies inside
|
174
|
+
~~~
|
175
|
+
|
176
|
+
Custom classes can define traversal behavior using `async_safe_traverse`:
|
177
|
+
|
178
|
+
~~~ ruby
|
179
|
+
class Request
|
180
|
+
async_safe!(false)
|
181
|
+
attr_accessor :body, :headers
|
182
|
+
|
183
|
+
def self.async_safe_traverse(instance, &block)
|
184
|
+
yield instance.body
|
185
|
+
yield instance.headers
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
request = Request.new
|
190
|
+
request.body = Body.new
|
191
|
+
request.headers = Headers.new
|
192
|
+
|
193
|
+
Async::Safe.transfer(request) # Transfers request, body, AND headers.
|
194
|
+
~~~
|
195
|
+
|
196
|
+
**Note:** Shareable objects (`async_safe? -> true`) are never traversed or transferred, as they can be safely shared across fibers.
|
197
|
+
|
166
198
|
## Integration with Tests
|
167
199
|
|
168
200
|
Add to your test helper (e.g., `config/sus.rb` or `spec/spec_helper.rb`):
|
data/lib/async/safe/builtins.rb
CHANGED
@@ -8,6 +8,34 @@
|
|
8
8
|
# Note: Immutable values (nil, true, false, integers, symbols, etc.) are already
|
9
9
|
# handled by the frozen? check in the monitor and don't need to be listed here.
|
10
10
|
|
11
|
+
# Arrays contain references to other objects that may need transfer:
|
12
|
+
class Array
|
13
|
+
ASYNC_SAFE = false
|
14
|
+
|
15
|
+
def self.async_safe_traverse(instance, &block)
|
16
|
+
instance.each(&block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Hashes contain keys and values that may need transfer:
|
21
|
+
class Hash
|
22
|
+
ASYNC_SAFE = false
|
23
|
+
|
24
|
+
def self.async_safe_traverse(instance, &block)
|
25
|
+
instance.each_key(&block)
|
26
|
+
instance.each_value(&block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Sets contain elements that may need transfer:
|
31
|
+
class Set
|
32
|
+
ASYNC_SAFE = false
|
33
|
+
|
34
|
+
def self.async_safe_traverse(instance, &block)
|
35
|
+
instance.each(&block)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
11
39
|
module Async
|
12
40
|
module Safe
|
13
41
|
# Automatically transfers ownership of objects when they are removed from a Thread::Queue.
|
data/lib/async/safe/class.rb
CHANGED
@@ -45,9 +45,32 @@ class Class
|
|
45
45
|
|
46
46
|
# Mark the class as async-safe or not.
|
47
47
|
#
|
48
|
-
# @parameter value [Boolean
|
49
|
-
# @returns [Boolean
|
48
|
+
# @parameter value [Boolean | Hash | Array] Configuration for async-safety.
|
49
|
+
# @returns [Boolean | Hash | Array] The configured value.
|
50
50
|
def async_safe!(value = true)
|
51
51
|
self.const_set(:ASYNC_SAFE, value)
|
52
52
|
end
|
53
|
+
|
54
|
+
# Define how to traverse this object's children during ownership transfer.
|
55
|
+
#
|
56
|
+
# This method is called by `Async::Safe.transfer` to recursively transfer
|
57
|
+
# ownership of contained objects. By default, only the object itself is transferred.
|
58
|
+
# Define this method to enable deep transfer for collection-like classes.
|
59
|
+
#
|
60
|
+
# @parameter instance [Object] The instance to traverse.
|
61
|
+
# @parameter block [Proc] Block to call for each child object that should be transferred.
|
62
|
+
#
|
63
|
+
# ~~~ ruby
|
64
|
+
# class MyContainer
|
65
|
+
# async_safe!(false)
|
66
|
+
# attr_reader :children
|
67
|
+
#
|
68
|
+
# def self.async_safe_traverse(instance, &block)
|
69
|
+
# instance.children.each(&block)
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
# ~~~
|
73
|
+
def async_safe_traverse(instance, &block)
|
74
|
+
# Default: no traversal (shallow transfer only)
|
75
|
+
end
|
53
76
|
end
|
data/lib/async/safe/monitor.rb
CHANGED
@@ -87,17 +87,55 @@ module Async
|
|
87
87
|
|
88
88
|
# Explicitly transfer ownership of objects to the current fiber.
|
89
89
|
#
|
90
|
+
# Also recursively transfers ownership of any tracked instance variables and
|
91
|
+
# objects contained in collections (Array, Hash, Set).
|
92
|
+
#
|
90
93
|
# @parameter objects [Array(Object)] The objects to transfer.
|
91
94
|
def transfer(*objects)
|
95
|
+
current = Fiber.current
|
96
|
+
visited = Set.new
|
97
|
+
|
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
|
105
|
+
|
92
106
|
@mutex.synchronize do
|
93
|
-
|
94
|
-
|
95
|
-
objects.each do |object|
|
96
|
-
@owners[object] = current
|
107
|
+
objects_to_transfer.each do |object|
|
108
|
+
@owners[object] = current if @owners.key?(object)
|
97
109
|
end
|
98
110
|
end
|
99
111
|
end
|
100
112
|
|
113
|
+
private
|
114
|
+
|
115
|
+
# Traverse the object graph and collect all reachable objects.
|
116
|
+
#
|
117
|
+
# @parameter object [Object] The object to traverse.
|
118
|
+
# @parameter visited [Set] Set of visited objects (object references, not IDs).
|
119
|
+
def traverse_objects(object, visited)
|
120
|
+
# Avoid circular references:
|
121
|
+
return if visited.include?(object)
|
122
|
+
|
123
|
+
# Skip objects that don't need traversing:
|
124
|
+
return if object.frozen? or object.is_a?(Module)
|
125
|
+
|
126
|
+
# Skip async-safe (shareable) objects - they're not owned:
|
127
|
+
klass = object.class
|
128
|
+
return if klass.async_safe?(nil)
|
129
|
+
|
130
|
+
# Mark as visited:
|
131
|
+
visited << object
|
132
|
+
|
133
|
+
# Recurse through custom traversal:
|
134
|
+
klass.async_safe_traverse(object) do |element|
|
135
|
+
traverse_objects(element, visited)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
101
139
|
# Check if the current access is allowed or constitutes a violation.
|
102
140
|
#
|
103
141
|
# @parameter trace_point [TracePoint] The trace point containing access information.
|
@@ -105,7 +143,7 @@ module Async
|
|
105
143
|
object = trace_point.self
|
106
144
|
|
107
145
|
# Skip tracking class/module methods:
|
108
|
-
return if object.is_a?(
|
146
|
+
return if object.is_a?(Module)
|
109
147
|
|
110
148
|
# Skip frozen objects:
|
111
149
|
return if object.frozen?
|
data/lib/async/safe/version.rb
CHANGED
data/readme.md
CHANGED
@@ -22,6 +22,10 @@ Please see the [project documentation](https://socketry.github.io/async-safe/) f
|
|
22
22
|
|
23
23
|
Please see the [project releases](https://socketry.github.io/async-safe/releases/index) for all releases.
|
24
24
|
|
25
|
+
### v0.4.0
|
26
|
+
|
27
|
+
- Improved `Async::Safe.transfer` to recursively transfer ownership of tracked instance variables.
|
28
|
+
|
25
29
|
### v0.3.2
|
26
30
|
|
27
31
|
- Better error message.
|
@@ -32,6 +36,9 @@ Please see the [project releases](https://socketry.github.io/async-safe/releases
|
|
32
36
|
- Added flexible `ASYNC_SAFE` constant support: boolean, hash, or array configurations.
|
33
37
|
- Added `Class#async_safe!` method for marking classes.
|
34
38
|
- Added `Class#async_safe?(method)` method for querying safety.
|
39
|
+
- Added `Class.async_safe_traverse` for custom deep transfer traversal (opt-in).
|
40
|
+
- Improved `Async::Safe.transfer` to use shallow transfer by default with opt-in deep traversal.
|
41
|
+
- Mark built-in collections (`Array`, `Hash`, `Set`) as single-owner with deep traversal support.
|
35
42
|
- Removed logger feature: always raises `ViolationError` exceptions.
|
36
43
|
- Removed `Async::Safe::Concurrent` module: use `async_safe!` instead.
|
37
44
|
|
data/releases.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Releases
|
2
2
|
|
3
|
+
## v0.4.0
|
4
|
+
|
5
|
+
- Improved `Async::Safe.transfer` to recursively transfer ownership of tracked instance variables.
|
6
|
+
|
3
7
|
## v0.3.2
|
4
8
|
|
5
9
|
- Better error message.
|
@@ -10,6 +14,9 @@
|
|
10
14
|
- Added flexible `ASYNC_SAFE` constant support: boolean, hash, or array configurations.
|
11
15
|
- Added `Class#async_safe!` method for marking classes.
|
12
16
|
- Added `Class#async_safe?(method)` method for querying safety.
|
17
|
+
- Added `Class.async_safe_traverse` for custom deep transfer traversal (opt-in).
|
18
|
+
- Improved `Async::Safe.transfer` to use shallow transfer by default with opt-in deep traversal.
|
19
|
+
- Mark built-in collections (`Array`, `Hash`, `Set`) as single-owner with deep traversal support.
|
13
20
|
- Removed logger feature: always raises `ViolationError` exceptions.
|
14
21
|
- Removed `Async::Safe::Concurrent` module: use `async_safe!` instead.
|
15
22
|
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
metadata.gz.sig
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
|
2
|
-
��cB4�V$h�0}�>cd��U@j�D�^Li�a@-�����!��J�!�{
|
1
|
+
'�i��HX���Kl4�fŷ�9�O�^��w|��P _�]��@_�AFW �URHwvn�9
|