async-safe 0.3.0 → 0.3.1
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 +140 -1
- data/lib/async/safe/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +1 -1
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a165b5a9b594d4eec52f9a7c80ee3cc020db7925ac7630ddc342a552a3a20112
|
4
|
+
data.tar.gz: c9cedd555a12b366f00bf4fa43906501bd6596a8abd3db2531b457d3083bd093
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5816b73d736aa8570fbdf3eed10ba2424e0dc138ac823bd37577cda99b42ac5c120556ac34b60cb645d08206a0214bd5b9a36bd8b25fae829791c522c2586ccf
|
7
|
+
data.tar.gz: d5c2d51e17041f290616eb45a4be80acd5e33b6a6b4e59a801462a3d7bfaccb453633c8e921a2d615136ac784b20fa92436fa73cf664ba4bcf6eaa807ab6ec7e
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/context/getting-started.md
CHANGED
@@ -25,7 +25,7 @@ Async::Safe.enable!
|
|
25
25
|
|
26
26
|
When a violation is detected, an `Async::Safe::ViolationError` will be raised immediately with details about the object, method, and execution contexts involved.
|
27
27
|
|
28
|
-
### Single-Owner Model
|
28
|
+
### Single-Owner Model
|
29
29
|
|
30
30
|
By default, all classes are assumed to be async-safe. To enable tracking for specific classes, mark them with `ASYNC_SAFE = false`:
|
31
31
|
|
@@ -181,6 +181,145 @@ $ bundle exec sus
|
|
181
181
|
|
182
182
|
Any thread safety violations will cause your tests to fail immediately with a clear error message showing which object was accessed incorrectly and from which fibers.
|
183
183
|
|
184
|
+
## Determining Async Safety
|
185
|
+
|
186
|
+
When deciding whether to mark a class or method with `ASYNC_SAFE = false`, consider these factors:
|
187
|
+
|
188
|
+
### Async-Safe Patterns
|
189
|
+
|
190
|
+
**Immutable objects:**
|
191
|
+
~~~ ruby
|
192
|
+
class ImmutableUser
|
193
|
+
def initialize(name, email)
|
194
|
+
@name = name.freeze
|
195
|
+
@email = email.freeze
|
196
|
+
freeze # Entire object is frozen
|
197
|
+
end
|
198
|
+
|
199
|
+
attr_reader :name, :email
|
200
|
+
end
|
201
|
+
~~~
|
202
|
+
|
203
|
+
**Pure functions (no state modification):**
|
204
|
+
~~~ ruby
|
205
|
+
class Calculator
|
206
|
+
def add(a, b)
|
207
|
+
a + b # No instance state, pure computation
|
208
|
+
end
|
209
|
+
end
|
210
|
+
~~~
|
211
|
+
|
212
|
+
**Thread-safe synchronization:**
|
213
|
+
~~~ ruby
|
214
|
+
class SafeQueue
|
215
|
+
ASYNC_SAFE = true # Explicitly marked
|
216
|
+
|
217
|
+
def initialize
|
218
|
+
@queue = Thread::Queue.new # Thread-safe internally
|
219
|
+
end
|
220
|
+
|
221
|
+
def push(item)
|
222
|
+
@queue.push(item) # Delegates to thread-safe queue
|
223
|
+
end
|
224
|
+
end
|
225
|
+
~~~
|
226
|
+
|
227
|
+
### Unsafe (Single-Owner) Patterns
|
228
|
+
|
229
|
+
**Mutable instance state:**
|
230
|
+
~~~ ruby
|
231
|
+
class Counter
|
232
|
+
ASYNC_SAFE = false # Enable tracking
|
233
|
+
|
234
|
+
def initialize
|
235
|
+
@count = 0
|
236
|
+
end
|
237
|
+
|
238
|
+
def increment
|
239
|
+
@count += 1 # Reads and writes @count (race condition!)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
~~~
|
243
|
+
|
244
|
+
**Stateful iteration:**
|
245
|
+
~~~ ruby
|
246
|
+
class Reader
|
247
|
+
ASYNC_SAFE = false # Enable tracking
|
248
|
+
|
249
|
+
def initialize(data)
|
250
|
+
@data = data
|
251
|
+
@index = 0
|
252
|
+
end
|
253
|
+
|
254
|
+
def read
|
255
|
+
value = @data[@index]
|
256
|
+
@index += 1 # Mutates state
|
257
|
+
value
|
258
|
+
end
|
259
|
+
end
|
260
|
+
~~~
|
261
|
+
|
262
|
+
**Lazy initialization:**
|
263
|
+
~~~ ruby
|
264
|
+
class DataLoader
|
265
|
+
ASYNC_SAFE = false # Enable tracking
|
266
|
+
|
267
|
+
def data
|
268
|
+
@data ||= load_data # Not atomic! (race condition)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
~~~
|
272
|
+
|
273
|
+
### Mixed Safety
|
274
|
+
|
275
|
+
Use hash or array configuration for classes with both safe and unsafe methods:
|
276
|
+
|
277
|
+
~~~ ruby
|
278
|
+
class MixedClass
|
279
|
+
ASYNC_SAFE = {
|
280
|
+
read_config: true, # Safe: only reads frozen data
|
281
|
+
update_state: false # Unsafe: modifies mutable state
|
282
|
+
}.freeze
|
283
|
+
|
284
|
+
def initialize
|
285
|
+
@config = {setting: "value"}.freeze
|
286
|
+
@state = {count: 0}
|
287
|
+
end
|
288
|
+
|
289
|
+
def read_config
|
290
|
+
@config[:setting] # Safe: frozen hash
|
291
|
+
end
|
292
|
+
|
293
|
+
def update_state
|
294
|
+
@state[:count] += 1 # Unsafe: mutates state
|
295
|
+
end
|
296
|
+
end
|
297
|
+
~~~
|
298
|
+
|
299
|
+
### Quick Checklist
|
300
|
+
|
301
|
+
Mark a method as unsafe (`ASYNC_SAFE = false`) if it:
|
302
|
+
- ❌ Modifies instance variables.
|
303
|
+
- ❌ Uses `||=` for lazy initialization.
|
304
|
+
- ❌ Iterates with mutable state (like `@index`).
|
305
|
+
- ❌ Reads then writes shared state.
|
306
|
+
- ❌ Accesses mutable collections without synchronization.
|
307
|
+
|
308
|
+
A method is likely safe if it:
|
309
|
+
- ✅ Only reads from frozen/immutable data.
|
310
|
+
- ✅ Has no instance state.
|
311
|
+
- ✅ Uses only local variables.
|
312
|
+
- ✅ Delegates to thread-safe primitives `Thread::Queue`, `Mutex`, etc.
|
313
|
+
- ✅ The object itself is frozen.
|
314
|
+
|
315
|
+
### When in Doubt
|
316
|
+
|
317
|
+
If you're unsure whether a class is thread-safe:
|
318
|
+
1. **Mark it as unsafe** (`ASYNC_SAFE = false`) - let the monitoring catch any issues.
|
319
|
+
2. **Run your tests** with monitoring enabled.
|
320
|
+
3. **If no violations occur** after thorough testing, it's likely safe.
|
321
|
+
4. **Review the code** for the patterns above.
|
322
|
+
|
184
323
|
## How It Works
|
185
324
|
|
186
325
|
1. **Default Assumption**: All objects follow a single-owner model (not thread-safe).
|
data/lib/async/safe/version.rb
CHANGED
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
metadata.gz.sig
CHANGED
Binary file
|