async-safe 0.1.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e20fdea7f6f63f3a47aa8be85492fa2725e6b3ea2c0284de63dd6fe45af6f49c
4
- data.tar.gz: 6556e3ed8f46cfe8157983229d4584a6b01eb0e8f2b36f83fe3ec5f541211830
3
+ metadata.gz: 8d77ebea20acb12bfc6f9e7848a809a17d993df98604ec9b457433ddf211437d
4
+ data.tar.gz: b7bdcdf3d68df5a5f385a252926319b6ffb2154b3bb431507d63fb3fe64f255e
5
5
  SHA512:
6
- metadata.gz: 6a02637fc476e5e38a6b1952394ad99ca3fbe13b106b6d7ac0abbafc7f285167f3a0ef9302a28ac0e12f29a70edce7158ea2690b88d309e99a5d1f073da58580
7
- data.tar.gz: 24fbc11d3be799f80649e1a487edc6f1b4040fbb0e4688d590f2caafceb76b761c298569a3b2384939844753eec8b788ff0bf70d8f820b3fbe4c9d9403183542
6
+ metadata.gz: 9cc0287a91eb60e17f4a6047574a88bf813dbe39ddf345738d3aacadd8a171ed3540e2e7f1004c987c45a5abd3afdfe83407cec276f08ede0a612a76fd0509da
7
+ data.tar.gz: ada0c1d9c123abacc5688a787229a0c578d2b3c59b7dbd7fe2c944e990a9eb2ffa3de4842e43b22b3eb0521683a8c1f7ccfd5221be35df9f6d36ee16e3e33914
checksums.yaml.gz.sig CHANGED
Binary file
@@ -25,12 +25,14 @@ 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 (Opt-In)
29
29
 
30
- By default, all objects are assumed to follow a **single-owner model** - they should only be accessed from one fiber/thread at a time:
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
 
32
32
  ~~~ ruby
33
33
  class MyBody
34
+ ASYNC_SAFE = false # Enable tracking for this class
35
+
34
36
  def initialize(chunks)
35
37
  @chunks = chunks
36
38
  @index = 0
@@ -51,7 +53,7 @@ Fiber.schedule do
51
53
  end
52
54
  ~~~
53
55
 
54
- ## Marking Async-Safe Classes
56
+ ### Marking Async-Safe Classes
55
57
 
56
58
  Mark entire classes as safe for concurrent access:
57
59
 
@@ -90,15 +92,29 @@ class MyQueue
90
92
  end
91
93
  ~~~
92
94
 
93
- ## Marking Async-Safe Methods
95
+ Or use a hash for per-method configuration:
96
+
97
+ ~~~ ruby
98
+ class MixedClass
99
+ ASYNC_SAFE = {
100
+ read: true, # This method is async-safe
101
+ write: false # This method is NOT async-safe
102
+ }.freeze
103
+
104
+ # ... implementation
105
+ end
106
+ ~~~
107
+
108
+ ### Marking Methods with Hash
94
109
 
95
- Mark specific methods as async-safe:
110
+ Use a hash to specify which methods are async-safe:
96
111
 
97
112
  ~~~ ruby
98
113
  class MixedSafety
99
- include Async::Safe
100
-
101
- async_safe :safe_read
114
+ ASYNC_SAFE = {
115
+ safe_read: true, # This method is async-safe
116
+ increment: false # This method is NOT async-safe
117
+ }.freeze
102
118
 
103
119
  def initialize(data)
104
120
  @data = data
@@ -110,7 +126,7 @@ class MixedSafety
110
126
  end
111
127
 
112
128
  def increment
113
- @count += 1 # Not async-safe
129
+ @count += 1 # Not async-safe - will be tracked
114
130
  end
115
131
  end
116
132
 
@@ -122,7 +138,18 @@ Fiber.schedule do
122
138
  end
123
139
  ~~~
124
140
 
125
- ## Transferring Ownership
141
+ Or use an array to list async-safe methods:
142
+
143
+ ~~~ ruby
144
+ class MyClass
145
+ ASYNC_SAFE = [:read, :inspect].freeze
146
+
147
+ # read and inspect are async-safe
148
+ # all other methods will be tracked
149
+ end
150
+ ~~~
151
+
152
+ ### Transferring Ownership
126
153
 
127
154
  Explicitly transfer ownership between fibers:
128
155
 
@@ -8,15 +8,50 @@
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
- # Thread synchronization primitives:
12
- Thread::ASYNC_SAFE = true
13
- Thread::Queue::ASYNC_SAFE = true
14
- Thread::SizedQueue::ASYNC_SAFE = true
15
- Thread::Mutex::ASYNC_SAFE = true
16
- Thread::ConditionVariable::ASYNC_SAFE = true
11
+ module Async
12
+ module Safe
13
+ # Automatically transfers ownership of objects when they are removed from a Thread::Queue.
14
+ #
15
+ # When included in Thread::Queue or Thread::SizedQueue, this module wraps pop/deq/shift
16
+ # methods to automatically transfer ownership of the dequeued object to the fiber that
17
+ # dequeues it.
18
+ module TransferableThreadQueue
19
+ # Pop an object from the queue and transfer ownership to the current fiber.
20
+ #
21
+ # @parameter arguments [Array] Arguments passed to the original pop method.
22
+ # @returns [Object] The dequeued object with transferred ownership.
23
+ def pop(...)
24
+ object = super(...)
25
+ Async::Safe.transfer(object)
26
+ object
27
+ end
28
+
29
+ # Dequeue an object from the queue and transfer ownership to the current fiber.
30
+ #
31
+ # Alias for {#pop}.
32
+ #
33
+ # @parameter arguments [Array] Arguments passed to the original deq method.
34
+ # @returns [Object] The dequeued object with transferred ownership.
35
+ def deq(...)
36
+ object = super(...)
37
+ Async::Safe.transfer(object)
38
+ object
39
+ end
40
+
41
+ # Shift an object from the queue and transfer ownership to the current fiber.
42
+ #
43
+ # Alias for {#pop}.
44
+ #
45
+ # @parameter arguments [Array] Arguments passed to the original shift method.
46
+ # @returns [Object] The dequeued object with transferred ownership.
47
+ def shift(...)
48
+ object = super(...)
49
+ Async::Safe.transfer(object)
50
+ object
51
+ end
52
+ end
53
+ end
54
+ end
17
55
 
18
- # Fibers are async-safe:
19
- Fiber::ASYNC_SAFE = true
20
-
21
- # ObjectSpace::WeakMap is async-safe:
22
- ObjectSpace::WeakMap::ASYNC_SAFE = true
56
+ Thread::Queue.prepend(Async::Safe::TransferableThreadQueue)
57
+ Thread::SizedQueue.prepend(Async::Safe::TransferableThreadQueue)
@@ -7,15 +7,40 @@
7
7
  class Class
8
8
  # Check if this class or a specific method is async-safe.
9
9
  #
10
+ # The `ASYNC_SAFE` constant can be:
11
+ # - `true` - entire class is async-safe.
12
+ # - `false` - entire class is NOT async-safe (single-owner).
13
+ # - `{method_name: true/false}` - per-method configuration.
14
+ # - `[method_name1, method_name2]` - per-method configuration.
15
+ #
10
16
  # @parameter method [Symbol | Nil] The method name to check, or nil to check if the entire class is async-safe.
11
- # @returns [Boolean] Whether the class or method is async-safe.
17
+ # @returns [Boolean] Whether the class or method is async-safe. Defaults to true if not specified.
12
18
  def async_safe?(method = nil)
13
- # Check if entire class is marked async-safe via constant:
14
- if const_defined?(:ASYNC_SAFE, false) && const_get(:ASYNC_SAFE)
15
- return true
19
+ if const_defined?(:ASYNC_SAFE)
20
+ async_safe = const_get(:ASYNC_SAFE)
21
+
22
+ case async_safe
23
+ when Hash
24
+ if method
25
+ async_safe = async_safe.fetch(method, false)
26
+ else
27
+ # In general, some methods may not be safe:
28
+ async_safe = false
29
+ end
30
+ when Array
31
+ if method
32
+ async_safe = async_safe.include?(method)
33
+ else
34
+ # In general, some methods may not be safe:
35
+ async_safe = false
36
+ end
37
+ end
38
+
39
+ return async_safe
16
40
  end
17
41
 
18
- false
42
+ # Default to true:
43
+ return true
19
44
  end
20
45
 
21
46
  # Mark the class as async-safe or not.
@@ -5,7 +5,7 @@
5
5
 
6
6
  module Async
7
7
  module Safe
8
- VERSION = "0.1.0"
8
+ VERSION = "0.3.0"
9
9
  end
10
10
  end
11
11
 
data/lib/async/safe.rb CHANGED
@@ -12,47 +12,12 @@ require_relative "safe/builtins"
12
12
  module Async
13
13
  # Provides runtime thread safety monitoring for concurrent Ruby code.
14
14
  #
15
- # By default, all objects follow a **single-owner model** - they should only be accessed
16
- # from one fiber/thread at a time. Objects or methods can be explicitly marked as
17
- # async-safe to allow concurrent access.
15
+ # By default, all classes are assumed to be async-safe. Classes that follow a
16
+ # **single-owner model** should be explicitly marked with `ASYNC_SAFE = false` to
17
+ # enable tracking and violation detection.
18
18
  #
19
19
  # Enable monitoring in your test suite to catch concurrency bugs early.
20
20
  module Safe
21
- # Include this module to mark specific methods as async-safe
22
- def self.included(base)
23
- base.extend(ClassMethods)
24
- end
25
-
26
- # Class methods for marking async-safe methods
27
- module ClassMethods
28
- # Mark one or more methods as async-safe.
29
- #
30
- # @parameter method_names [Array(Symbol)] The methods to mark as async-safe.
31
- def async_safe(*method_names)
32
- @async_safe_methods ||= Set.new
33
- @async_safe_methods.merge(method_names)
34
- end
35
-
36
- # Check if a method is async-safe.
37
- #
38
- # Overrides the default implementation from `Class` to also check method-level safety.
39
- #
40
- # @parameter method [Symbol | Nil] The method name to check, or nil to check if the entire class is async-safe.
41
- # @returns [Boolean] Whether the method or class is async-safe.
42
- def async_safe?(method = nil)
43
- # Check if entire class is marked async-safe:
44
- return true if super
45
-
46
- # Check if specific method is marked async-safe:
47
- if method
48
- return @async_safe_methods&.include?(method)
49
- end
50
-
51
- # Default to false if no method is specified and the class is not async safe:
52
- return false
53
- end
54
- end
55
-
56
21
  class << self
57
22
  # @attribute [Monitor] The global monitoring instance.
58
23
  attr_reader :monitor
data/readme.md CHANGED
@@ -22,6 +22,21 @@ 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.3.0
26
+
27
+ - Inverted default model: classes are async-safe by default, use `ASYNC_SAFE = false` to enable tracking.
28
+ - Added flexible `ASYNC_SAFE` constant support: boolean, hash, or array configurations.
29
+ - Added `Class#async_safe!` method for marking classes.
30
+ - Added `Class#async_safe?(method)` method for querying safety.
31
+ - Removed logger feature: always raises `ViolationError` exceptions.
32
+ - Removed `Async::Safe::Concurrent` module: use `async_safe!` instead.
33
+ - Removed `reset!` method: use `disable!` + `enable!` instead.
34
+
35
+ ### v0.2.0
36
+
37
+ - `Thread::Queue` transfers ownership of objects popped from it.
38
+ - Add support for `logger:` option in `Async::Safe.enable!` which logs violations instead of raising errors.
39
+
25
40
  ### v0.1.0
26
41
 
27
42
  - Implement TracePoint-based ownership tracking.
data/releases.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Releases
2
2
 
3
+ ## v0.3.0
4
+
5
+ - Inverted default model: classes are async-safe by default, use `ASYNC_SAFE = false` to enable tracking.
6
+ - Added flexible `ASYNC_SAFE` constant support: boolean, hash, or array configurations.
7
+ - Added `Class#async_safe!` method for marking classes.
8
+ - Added `Class#async_safe?(method)` method for querying safety.
9
+ - Removed logger feature: always raises `ViolationError` exceptions.
10
+ - Removed `Async::Safe::Concurrent` module: use `async_safe!` instead.
11
+ - Removed `reset!` method: use `disable!` + `enable!` instead.
12
+
13
+ ## v0.2.0
14
+
15
+ - `Thread::Queue` transfers ownership of objects popped from it.
16
+ - Add support for `logger:` option in `Async::Safe.enable!` which logs violations instead of raising errors.
17
+
3
18
  ## v0.1.0
4
19
 
5
20
  - Implement TracePoint-based ownership tracking.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-safe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
metadata.gz.sig CHANGED
Binary file