async-safe 0.1.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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data/context/getting-started.md +176 -0
- data/context/index.yaml +12 -0
- data/lib/async/safe/builtins.rb +22 -0
- data/lib/async/safe/class.rb +28 -0
- data/lib/async/safe/monitor.rb +147 -0
- data/lib/async/safe/version.rb +11 -0
- data/lib/async/safe.rb +96 -0
- data/license.md +21 -0
- data/readme.md +55 -0
- data/releases.md +10 -0
- data.tar.gz.sig +0 -0
- metadata +78 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e20fdea7f6f63f3a47aa8be85492fa2725e6b3ea2c0284de63dd6fe45af6f49c
|
4
|
+
data.tar.gz: 6556e3ed8f46cfe8157983229d4584a6b01eb0e8f2b36f83fe3ec5f541211830
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6a02637fc476e5e38a6b1952394ad99ca3fbe13b106b6d7ac0abbafc7f285167f3a0ef9302a28ac0e12f29a70edce7158ea2690b88d309e99a5d1f073da58580
|
7
|
+
data.tar.gz: 24fbc11d3be799f80649e1a487edc6f1b4040fbb0e4688d590f2caafceb76b761c298569a3b2384939844753eec8b788ff0bf70d8f820b3fbe4c9d9403183542
|
checksums.yaml.gz.sig
ADDED
Binary file
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# Getting Started
|
2
|
+
|
3
|
+
This guide explains how to use `async-safe` to detect thread safety violations in your Ruby code.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add the gem to your project:
|
8
|
+
|
9
|
+
~~~ bash
|
10
|
+
$ bundle add async-safe
|
11
|
+
~~~
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
Enable monitoring in your test suite or development environment:
|
16
|
+
|
17
|
+
~~~ ruby
|
18
|
+
require 'async/safe'
|
19
|
+
|
20
|
+
# Enable monitoring
|
21
|
+
Async::Safe.enable!
|
22
|
+
|
23
|
+
# Your concurrent code here...
|
24
|
+
~~~
|
25
|
+
|
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
|
+
|
28
|
+
## Single-Owner Model
|
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:
|
31
|
+
|
32
|
+
~~~ ruby
|
33
|
+
class MyBody
|
34
|
+
def initialize(chunks)
|
35
|
+
@chunks = chunks
|
36
|
+
@index = 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def read
|
40
|
+
chunk = @chunks[@index]
|
41
|
+
@index += 1
|
42
|
+
chunk
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
body = MyBody.new(["a", "b", "c"])
|
47
|
+
body.read # OK - accessed from main fiber
|
48
|
+
|
49
|
+
Fiber.schedule do
|
50
|
+
body.read # 💥 Raises Async::Safe::ViolationError!
|
51
|
+
end
|
52
|
+
~~~
|
53
|
+
|
54
|
+
## Marking Async-Safe Classes
|
55
|
+
|
56
|
+
Mark entire classes as safe for concurrent access:
|
57
|
+
|
58
|
+
~~~ ruby
|
59
|
+
class MyQueue
|
60
|
+
async_safe!
|
61
|
+
|
62
|
+
def initialize
|
63
|
+
@queue = Thread::Queue.new
|
64
|
+
end
|
65
|
+
|
66
|
+
def push(item)
|
67
|
+
@queue.push(item)
|
68
|
+
end
|
69
|
+
|
70
|
+
def pop
|
71
|
+
@queue.pop
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
queue = MyQueue.new
|
76
|
+
queue.push("item")
|
77
|
+
|
78
|
+
Fiber.schedule do
|
79
|
+
queue.push("another") # ✅ OK - class is marked async-safe
|
80
|
+
end
|
81
|
+
~~~
|
82
|
+
|
83
|
+
Alternatively, you can manually set the constant:
|
84
|
+
|
85
|
+
~~~ ruby
|
86
|
+
class MyQueue
|
87
|
+
ASYNC_SAFE = true
|
88
|
+
|
89
|
+
# ... implementation
|
90
|
+
end
|
91
|
+
~~~
|
92
|
+
|
93
|
+
## Marking Async-Safe Methods
|
94
|
+
|
95
|
+
Mark specific methods as async-safe:
|
96
|
+
|
97
|
+
~~~ ruby
|
98
|
+
class MixedSafety
|
99
|
+
include Async::Safe
|
100
|
+
|
101
|
+
async_safe :safe_read
|
102
|
+
|
103
|
+
def initialize(data)
|
104
|
+
@data = data
|
105
|
+
@count = 0
|
106
|
+
end
|
107
|
+
|
108
|
+
def safe_read
|
109
|
+
@data # Async-safe method
|
110
|
+
end
|
111
|
+
|
112
|
+
def increment
|
113
|
+
@count += 1 # Not async-safe
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
obj = MixedSafety.new("data")
|
118
|
+
|
119
|
+
Fiber.schedule do
|
120
|
+
obj.safe_read # ✅ OK - method is marked async-safe
|
121
|
+
obj.increment # 💥 Raises Async::Safe::ViolationError!
|
122
|
+
end
|
123
|
+
~~~
|
124
|
+
|
125
|
+
## Transferring Ownership
|
126
|
+
|
127
|
+
Explicitly transfer ownership between fibers:
|
128
|
+
|
129
|
+
~~~ ruby
|
130
|
+
request = create_request
|
131
|
+
process_in_main_fiber(request)
|
132
|
+
|
133
|
+
Fiber.schedule do
|
134
|
+
Async::Safe.transfer(request) # Transfer ownership
|
135
|
+
process_in_worker_fiber(request) # ✅ OK now
|
136
|
+
end
|
137
|
+
~~~
|
138
|
+
|
139
|
+
## Integration with Tests
|
140
|
+
|
141
|
+
Add to your test helper (e.g., `config/sus.rb` or `spec/spec_helper.rb`):
|
142
|
+
|
143
|
+
~~~ ruby
|
144
|
+
require 'async/safe'
|
145
|
+
|
146
|
+
Async::Safe.enable!
|
147
|
+
~~~
|
148
|
+
|
149
|
+
Then run your tests normally:
|
150
|
+
|
151
|
+
~~~ bash
|
152
|
+
$ bundle exec sus
|
153
|
+
~~~
|
154
|
+
|
155
|
+
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.
|
156
|
+
|
157
|
+
## How It Works
|
158
|
+
|
159
|
+
1. **Default Assumption**: All objects follow a single-owner model (not thread-safe).
|
160
|
+
2. **TracePoint Monitoring**: Tracks which fiber/thread first accesses each object.
|
161
|
+
3. **Violation Detection**: Raises an exception when a different fiber/thread accesses the same object.
|
162
|
+
4. **Explicit Safety**: Objects/methods can be marked as thread-safe to allow concurrent access.
|
163
|
+
5. **Zero Overhead**: Monitoring is only active when explicitly enabled.
|
164
|
+
|
165
|
+
## Use Cases
|
166
|
+
|
167
|
+
- **Detecting concurrency bugs** in development and testing.
|
168
|
+
- **Validating thread safety assumptions** in async/fiber-based code.
|
169
|
+
- **Finding race conditions** before they cause production issues.
|
170
|
+
- **Educational tool** for learning about thread safety in Ruby.
|
171
|
+
|
172
|
+
## Performance
|
173
|
+
|
174
|
+
- **Zero overhead when disabled** - TracePoint is not activated.
|
175
|
+
- **Minimal overhead when enabled** - suitable for development/test environments.
|
176
|
+
- **Not recommended for production** - use only in development/testing.
|
data/context/index.yaml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# Automatically generated context index for Utopia::Project guides.
|
2
|
+
# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
|
3
|
+
---
|
4
|
+
description: Runtime thread safety monitoring for concurrent Ruby code.
|
5
|
+
metadata:
|
6
|
+
documentation_uri: https://socketry.github.io/async-safe/
|
7
|
+
source_code_uri: https://github.com/socketry/async-safe
|
8
|
+
files:
|
9
|
+
- path: getting-started.md
|
10
|
+
title: Getting Started
|
11
|
+
description: This guide explains how to use `async-safe` to detect thread safety
|
12
|
+
violations in your Ruby code.
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
# Mark Ruby's built-in thread-safe classes as async-safe
|
7
|
+
#
|
8
|
+
# Note: Immutable values (nil, true, false, integers, symbols, etc.) are already
|
9
|
+
# handled by the frozen? check in the monitor and don't need to be listed here.
|
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
|
17
|
+
|
18
|
+
# Fibers are async-safe:
|
19
|
+
Fiber::ASYNC_SAFE = true
|
20
|
+
|
21
|
+
# ObjectSpace::WeakMap is async-safe:
|
22
|
+
ObjectSpace::WeakMap::ASYNC_SAFE = true
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
# Extend Class with a default async_safe? implementation
|
7
|
+
class Class
|
8
|
+
# Check if this class or a specific method is async-safe.
|
9
|
+
#
|
10
|
+
# @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.
|
12
|
+
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
|
16
|
+
end
|
17
|
+
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
# Mark the class as async-safe or not.
|
22
|
+
#
|
23
|
+
# @parameter value [Boolean] Whether the class is async-safe.
|
24
|
+
# @returns [Boolean] Whether the class is async-safe.
|
25
|
+
def async_safe!(value = true)
|
26
|
+
self.const_set(:ASYNC_SAFE, value)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
require "set"
|
7
|
+
require "weakref"
|
8
|
+
|
9
|
+
module Async
|
10
|
+
module Safe
|
11
|
+
# Raised when an object is accessed from a different fiber than the one that owns it.
|
12
|
+
class ViolationError < StandardError
|
13
|
+
# Initialize a new violation error.
|
14
|
+
#
|
15
|
+
# @parameter message [String | Nil] Optional custom message.
|
16
|
+
# @parameter target [Object] The object that was accessed.
|
17
|
+
# @parameter method [Symbol] The method that was called.
|
18
|
+
# @parameter owner [Fiber] The fiber that owns the object.
|
19
|
+
# @parameter current [Fiber] The fiber that attempted to access the object.
|
20
|
+
def initialize(message = nil, target:, method:, owner:, current:)
|
21
|
+
@target = target
|
22
|
+
@method = method
|
23
|
+
@owner = owner
|
24
|
+
@current = current
|
25
|
+
|
26
|
+
super(message || build_message)
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :object_class, :method, :owner, :current
|
30
|
+
|
31
|
+
# Convert the violation error to a JSON-serializable hash.
|
32
|
+
#
|
33
|
+
# @returns [Hash] A hash representation of the violation.
|
34
|
+
def as_json
|
35
|
+
{
|
36
|
+
object_class: @object_class,
|
37
|
+
method: @method,
|
38
|
+
owner: {
|
39
|
+
name: @owner.inspect,
|
40
|
+
backtrace: @owner.backtrace,
|
41
|
+
},
|
42
|
+
current: {
|
43
|
+
name: @current.inspect,
|
44
|
+
backtrace: @current.backtrace,
|
45
|
+
},
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
private def build_message
|
50
|
+
"Thread safety violation detected! #{@target.inspect}##{@method} was accessed from #{@current.inspect} by #{@owner.inspect}."
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# The core monitoring implementation using TracePoint.
|
55
|
+
#
|
56
|
+
# This class tracks object ownership across fibers, detecting when an object
|
57
|
+
# is accessed from a different fiber than the one that originally created or
|
58
|
+
# accessed it.
|
59
|
+
#
|
60
|
+
# The monitor uses a TracePoint on `:call` events to track all method calls,
|
61
|
+
# and maintains a registry of which fiber "owns" each object. Uses weak references
|
62
|
+
# to avoid preventing garbage collection of tracked objects.
|
63
|
+
class Monitor
|
64
|
+
ASYNC_SAFE = true
|
65
|
+
|
66
|
+
# Initialize a new monitor instance.
|
67
|
+
def initialize
|
68
|
+
@owners = ObjectSpace::WeakMap.new
|
69
|
+
@mutex = Thread::Mutex.new
|
70
|
+
@trace_point = nil
|
71
|
+
end
|
72
|
+
|
73
|
+
attr :owners
|
74
|
+
|
75
|
+
# Enable the monitor by activating the TracePoint.
|
76
|
+
def enable!
|
77
|
+
@trace_point ||= TracePoint.trace(:call, &method(:check_access))
|
78
|
+
end
|
79
|
+
|
80
|
+
# Disable the monitor by deactivating the TracePoint.
|
81
|
+
def disable!
|
82
|
+
if trace_point = @trace_point
|
83
|
+
@trace_point = nil
|
84
|
+
trace_point.disable
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Explicitly transfer ownership of objects to the current fiber.
|
89
|
+
#
|
90
|
+
# @parameter objects [Array(Object)] The objects to transfer.
|
91
|
+
def transfer(*objects)
|
92
|
+
@mutex.synchronize do
|
93
|
+
current = Fiber.current
|
94
|
+
|
95
|
+
objects.each do |object|
|
96
|
+
@owners[object] = current
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Check if the current access is allowed or constitutes a violation.
|
102
|
+
#
|
103
|
+
# @parameter trace_point [TracePoint] The trace point containing access information.
|
104
|
+
def check_access(trace_point)
|
105
|
+
object = trace_point.self
|
106
|
+
|
107
|
+
# Skip tracking class/module methods:
|
108
|
+
return if object.is_a?(Class) || object.is_a?(Module)
|
109
|
+
|
110
|
+
# Skip frozen objects:
|
111
|
+
return if object.frozen?
|
112
|
+
|
113
|
+
method = trace_point.method_id
|
114
|
+
klass = trace_point.defined_class
|
115
|
+
|
116
|
+
# Check the object's actual class:
|
117
|
+
klass = object.class
|
118
|
+
|
119
|
+
# Check if the class or method is marked as async-safe:
|
120
|
+
if klass.async_safe?(method)
|
121
|
+
return
|
122
|
+
end
|
123
|
+
|
124
|
+
# Track ownership:
|
125
|
+
current = Fiber.current
|
126
|
+
|
127
|
+
@mutex.synchronize do
|
128
|
+
if owner = @owners[object]
|
129
|
+
# Violation if accessed from different fiber:
|
130
|
+
if owner != current
|
131
|
+
raise ViolationError.new(
|
132
|
+
target: object,
|
133
|
+
method: method,
|
134
|
+
owner: owner,
|
135
|
+
current: current,
|
136
|
+
)
|
137
|
+
end
|
138
|
+
else
|
139
|
+
# First access - record owner:
|
140
|
+
@owners[object] = current
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
data/lib/async/safe.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative "safe/version"
|
7
|
+
require_relative "safe/class"
|
8
|
+
require_relative "safe/monitor"
|
9
|
+
require_relative "safe/builtins"
|
10
|
+
|
11
|
+
# @namespace
|
12
|
+
module Async
|
13
|
+
# Provides runtime thread safety monitoring for concurrent Ruby code.
|
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.
|
18
|
+
#
|
19
|
+
# Enable monitoring in your test suite to catch concurrency bugs early.
|
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
|
+
class << self
|
57
|
+
# @attribute [Monitor] The global monitoring instance.
|
58
|
+
attr_reader :monitor
|
59
|
+
|
60
|
+
# Enable thread safety monitoring.
|
61
|
+
#
|
62
|
+
# This activates a TracePoint that tracks object access across fibers and threads.
|
63
|
+
# There is no performance overhead when monitoring is disabled.
|
64
|
+
def enable!
|
65
|
+
@monitor ||= Monitor.new
|
66
|
+
@monitor.enable!
|
67
|
+
end
|
68
|
+
|
69
|
+
# Disable thread safety monitoring.
|
70
|
+
def disable!
|
71
|
+
@monitor&.disable!
|
72
|
+
@monitor = nil
|
73
|
+
end
|
74
|
+
|
75
|
+
# Explicitly transfer ownership of objects to the current fiber.
|
76
|
+
#
|
77
|
+
# This allows an object to be safely passed between fibers.
|
78
|
+
#
|
79
|
+
# @parameter objects [Array(Object)] The objects to transfer ownership of.
|
80
|
+
#
|
81
|
+
# ~~~ ruby
|
82
|
+
# request = Request.new(...)
|
83
|
+
#
|
84
|
+
# Fiber.schedule do
|
85
|
+
# # Transfer ownership of the request to this fiber:
|
86
|
+
# Async::Safe.transfer(request)
|
87
|
+
# process(request)
|
88
|
+
# end
|
89
|
+
# ~~~
|
90
|
+
def transfer(*objects)
|
91
|
+
@monitor&.transfer(*objects)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
data/license.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# MIT License
|
2
|
+
|
3
|
+
Copyright, 2025, by Samuel Williams.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/readme.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# Async::Safe
|
2
|
+
|
3
|
+
Runtime thread safety monitoring for concurrent Ruby code.
|
4
|
+
|
5
|
+
This gem provides a TracePoint-based ownership tracking system that detects when objects are accessed from multiple fibers or threads without proper synchronization. It helps catch concurrency bugs during development and testing with zero overhead in production.
|
6
|
+
|
7
|
+
[](https://github.com/socketry/async-safe/actions?workflow=Test)
|
8
|
+
|
9
|
+
## Motivation
|
10
|
+
|
11
|
+
Ruby's fiber-based concurrency (via `async`) requires careful attention to object ownership. This gem helps you catch violations of the single-owner model in your test suite, preventing concurrency bugs from reaching production.
|
12
|
+
|
13
|
+
Enable it in your tests to get immediate feedback when objects are incorrectly shared across fibers.
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
Please see the [project documentation](https://socketry.github.io/async-safe/) for more details.
|
18
|
+
|
19
|
+
- [Getting Started](https://socketry.github.io/async-safe/guides/getting-started/index) - This guide explains how to use `async-safe` to detect thread safety violations in your Ruby code.
|
20
|
+
|
21
|
+
## Releases
|
22
|
+
|
23
|
+
Please see the [project releases](https://socketry.github.io/async-safe/releases/index) for all releases.
|
24
|
+
|
25
|
+
### v0.1.0
|
26
|
+
|
27
|
+
- Implement TracePoint-based ownership tracking.
|
28
|
+
- Add `Async::Safe::Concurrent` module for marking thread-safe classes.
|
29
|
+
- Add `thread_safe` class method for marking thread-safe methods.
|
30
|
+
- Add `Async::Safe.transfer` for explicit ownership transfer
|
31
|
+
- Add violation detection and reporting
|
32
|
+
- Zero overhead when monitoring is disabled
|
33
|
+
|
34
|
+
## See Also
|
35
|
+
|
36
|
+
- [async](https://github.com/socketry/async) - Composable asynchronous I/O for Ruby.
|
37
|
+
- [Thread Safety Guide](https://github.com/socketry/async/blob/main/.context/async/thread-safety.md) - Best practices for concurrent Ruby code.
|
38
|
+
|
39
|
+
## Contributing
|
40
|
+
|
41
|
+
We welcome contributions to this project.
|
42
|
+
|
43
|
+
1. Fork it.
|
44
|
+
2. Create your feature branch (`git checkout -b my-new-feature`).
|
45
|
+
3. Commit your changes (`git commit -am 'Add some feature'`).
|
46
|
+
4. Push to the branch (`git push origin my-new-feature`).
|
47
|
+
5. Create new Pull Request.
|
48
|
+
|
49
|
+
### Developer Certificate of Origin
|
50
|
+
|
51
|
+
In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
|
52
|
+
|
53
|
+
### Community Guidelines
|
54
|
+
|
55
|
+
This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
|
data/releases.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Releases
|
2
|
+
|
3
|
+
## v0.1.0
|
4
|
+
|
5
|
+
- Implement TracePoint-based ownership tracking.
|
6
|
+
- Add `Async::Safe::Concurrent` module for marking thread-safe classes.
|
7
|
+
- Add `thread_safe` class method for marking thread-safe methods.
|
8
|
+
- Add `Async::Safe.transfer` for explicit ownership transfer
|
9
|
+
- Add violation detection and reporting
|
10
|
+
- Zero overhead when monitoring is disabled
|
data.tar.gz.sig
ADDED
Binary file
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: async-safe
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Samuel Williams
|
8
|
+
bindir: bin
|
9
|
+
cert_chain:
|
10
|
+
- |
|
11
|
+
-----BEGIN CERTIFICATE-----
|
12
|
+
MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
|
13
|
+
ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
|
14
|
+
CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
|
15
|
+
MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
|
16
|
+
MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
|
17
|
+
bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
|
18
|
+
igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
|
19
|
+
9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
|
20
|
+
sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
|
21
|
+
e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
|
22
|
+
XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
|
23
|
+
RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
|
24
|
+
tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
|
25
|
+
zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
|
26
|
+
xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
|
27
|
+
BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
|
28
|
+
aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
|
29
|
+
aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
|
30
|
+
cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl
|
31
|
+
xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/
|
32
|
+
c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp
|
33
|
+
8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws
|
34
|
+
JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
|
35
|
+
eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
|
36
|
+
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
37
|
+
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
38
|
+
-----END CERTIFICATE-----
|
39
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
40
|
+
dependencies: []
|
41
|
+
executables: []
|
42
|
+
extensions: []
|
43
|
+
extra_rdoc_files: []
|
44
|
+
files:
|
45
|
+
- context/getting-started.md
|
46
|
+
- context/index.yaml
|
47
|
+
- lib/async/safe.rb
|
48
|
+
- lib/async/safe/builtins.rb
|
49
|
+
- lib/async/safe/class.rb
|
50
|
+
- lib/async/safe/monitor.rb
|
51
|
+
- lib/async/safe/version.rb
|
52
|
+
- license.md
|
53
|
+
- readme.md
|
54
|
+
- releases.md
|
55
|
+
homepage: https://github.com/socketry/async-safe
|
56
|
+
licenses:
|
57
|
+
- MIT
|
58
|
+
metadata:
|
59
|
+
documentation_uri: https://socketry.github.io/async-safe/
|
60
|
+
source_code_uri: https://github.com/socketry/async-safe
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.2'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubygems_version: 3.6.9
|
76
|
+
specification_version: 4
|
77
|
+
summary: Runtime thread safety monitoring for concurrent Ruby code.
|
78
|
+
test_files: []
|
metadata.gz.sig
ADDED
Binary file
|