Skip to content

Commit 1474586

Browse files
Improved documentation.
1 parent 9564134 commit 1474586

File tree

1 file changed

+140
-1
lines changed

1 file changed

+140
-1
lines changed

guides/getting-started/readme.md

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Async::Safe.enable!
2525

2626
When a violation is detected, an `Async::Safe::ViolationError` will be raised immediately with details about the object, method, and execution contexts involved.
2727

28-
### Single-Owner Model (Opt-In)
28+
### Single-Owner Model
2929

3030
By default, all classes are assumed to be async-safe. To enable tracking for specific classes, mark them with `ASYNC_SAFE = false`:
3131

@@ -181,6 +181,145 @@ $ bundle exec sus
181181

182182
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.
183183

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+
184323
## How It Works
185324

186325
1. **Default Assumption**: All objects follow a single-owner model (not thread-safe).

0 commit comments

Comments
 (0)