Skip to content

Commit c859d58

Browse files
committed
Add YARD documentation and inline RBS type signatures
1 parent 19f5749 commit c859d58

6 files changed

Lines changed: 474 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## [Unreleased]
22

3+
- Add YARD documentation and inline RBS type signatures
4+
35
## [0.9.0] - 2025-11-05
46

57
- Switch `AtomicThreadPool` back to atomics now that Ractor safety is lazy

Gemfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ gem "rake-compiler"
1111

1212
gem "minitest"
1313

14+
gem "rbs-inline"
15+
16+
gem "yard"
17+
1418
gem "debug"
1519

1620
gem "benchmark"

lib/atomic-ruby/atom.rb

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,94 @@
1+
# rbs_inline: enabled
12
# frozen_string_literal: true
23

34
require "atomic_ruby/atomic_ruby"
45

56
module AtomicRuby
7+
# Provides atomic reference semantics using Compare-And-Swap (CAS) operations.
8+
#
9+
# An Atom allows for lock-free, thread-safe updates to a single reference value.
10+
# The core operation is {#swap}, which atomically updates the value based on
11+
# the current value, retrying if another thread modifies it concurrently.
12+
#
13+
# @example Basic usage
14+
# atom = Atom.new(0)
15+
# atom.swap { |current_value| current_value + 1 }
16+
# puts atom.value #=> 1
17+
#
18+
# @example Thread-safe counter
19+
# counter = Atom.new(0)
20+
# threads = 10.times.map do
21+
# Thread.new { 100.times { counter.swap { |current_count| current_count + 1 } } }
22+
# end
23+
# threads.each(&:join)
24+
# puts counter.value #=> 1000
25+
#
26+
# @example Non-mutating array operations
27+
# atom = Atom.new([])
28+
# atom.swap { |current_array| current_array + [1] }
29+
# atom.swap { |current_array| current_array + [2] }
30+
# puts atom.value #=> [1, 2]
31+
#
32+
# @note This class is Ractor-safe in Ruby 4.0+ when compiled with ractor support.
33+
# Values that cross ractor boundaries are automatically made shareable.
634
class Atom
35+
# Creates a new atomic reference with the given initial value.
36+
#
37+
# @param value [untyped] The initial value to store atomically
38+
#
39+
# @example
40+
# atom = Atom.new(42)
41+
# atom = Atom.new([1, 2, 3])
42+
# atom = Atom.new({ key: "value" })
43+
#
44+
# @rbs (untyped value) -> void
745
def initialize(value)
846
_initialize(value)
947
end
1048

49+
# Returns the current value stored in the atom.
50+
#
51+
# This operation is atomic and thread-safe. The returned value reflects
52+
# the state at the time of the call, but may change immediately after
53+
# in concurrent environments.
54+
#
55+
# @return [untyped] The current atomic value
56+
#
57+
# @example
58+
# atom = Atom.new("hello")
59+
# puts atom.value #=> "hello"
60+
#
61+
# @rbs () -> untyped
1162
def value
1263
_value
1364
end
1465

66+
# Atomically updates the value using a compare-and-swap operation.
67+
#
68+
# The block receives the current value and must return the new value.
69+
# If another thread modifies the atom between reading the current value
70+
# and attempting to update it, the operation retries with the new current value.
71+
#
72+
# @yieldparam current_value [untyped] The current atomic value
73+
# @yieldreturn [untyped] The new value to store atomically
74+
# @return [untyped] The new value that was successfully stored
75+
#
76+
# @example Increment a counter
77+
# atom = Atom.new(0)
78+
# new_value = atom.swap { |current_value| current_value + 1 }
79+
# puts new_value #=> 1
80+
#
81+
# @example Append to array (non-mutating)
82+
# atom = Atom.new([1, 2])
83+
# atom.swap { |current_array| current_array + [3] }
84+
# puts atom.value #=> [1, 2, 3]
85+
#
86+
# @example Conditional update
87+
# atom = Atom.new(10)
88+
# atom.swap { |current_value| current_value > 5 ? current_value * 2 : current_value }
89+
# puts atom.value #=> 20
90+
#
91+
# @rbs () { (untyped) -> untyped } -> untyped
1592
def swap(&block)
1693
_swap do |old_value|
1794
make_shareable_if_needed(block.call(old_value))
@@ -20,6 +97,15 @@ def swap(&block)
2097

2198
private
2299

100+
# Makes a value shareable when crossing ractor boundaries.
101+
#
102+
# This method ensures ractor safety by automatically making values
103+
# shareable when they need to cross ractor boundaries in Ruby 4.0+.
104+
#
105+
# @param value [untyped] The value to potentially make shareable
106+
# @return [untyped] The original value or shareable version
107+
#
108+
# @rbs (untyped value) -> untyped
23109
def make_shareable_if_needed(value)
24110
if RACTOR_SAFE &&
25111
(_initialized_ractor.nil? || Ractor.current != _initialized_ractor)

lib/atomic-ruby/atomic_boolean.rb

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,50 @@
1+
# rbs_inline: enabled
12
# frozen_string_literal: true
23

34
require_relative "atom"
45

56
module AtomicRuby
7+
# Provides atomic boolean semantics with thread-safe toggle operations.
8+
#
9+
# AtomicBoolean wraps a boolean value in an atomic reference, providing
10+
# lock-free operations for common boolean manipulations like toggling,
11+
# setting to true/false, and checking the current state.
12+
#
13+
# @example Basic usage
14+
# boolean = AtomicBoolean.new(false)
15+
# puts boolean.false? #=> true
16+
# boolean.toggle
17+
# puts boolean.true? #=> true
18+
#
19+
# @example Thread-safe toggle
20+
# boolean = AtomicBoolean.new(false)
21+
# threads = 10.times.map do
22+
# Thread.new { 100.times { boolean.toggle } }
23+
# end
24+
# threads.each(&:join)
25+
# # Final state depends on whether total toggles is even or odd
26+
#
27+
# @example Atomic flag setting
28+
# flag = AtomicBoolean.new(false)
29+
# flag.make_true
30+
# puts flag.value #=> true
31+
#
32+
# @note This class is Ractor-safe in Ruby 4.0+ when compiled with ractor support.
633
class AtomicBoolean
34+
# Creates a new atomic boolean with the given initial value.
35+
#
36+
# @param boolean [true, false] The initial boolean value
37+
# @raise [ArgumentError] if the value is not a boolean (TrueClass or FalseClass)
38+
#
39+
# @example
40+
# boolean = AtomicBoolean.new(true)
41+
# boolean = AtomicBoolean.new(false)
42+
#
43+
# @example Invalid usage
44+
# AtomicBoolean.new(nil) #=> raises ArgumentError
45+
# AtomicBoolean.new("true") #=> raises ArgumentError
46+
#
47+
# @rbs (bool boolean) -> void
748
def initialize(boolean)
849
unless boolean.is_a?(TrueClass) || boolean.is_a?(FalseClass)
950
raise ArgumentError, "boolean must be a TrueClass or FalseClass"
@@ -14,26 +55,102 @@ def initialize(boolean)
1455
Ractor.make_shareable(self) if RACTOR_SAFE
1556
end
1657

58+
# Returns the current boolean value stored in the atom.
59+
#
60+
# This operation is atomic and thread-safe. The returned value reflects
61+
# the state at the time of the call, but may change immediately after
62+
# in concurrent environments.
63+
#
64+
# @return [true, false] The current atomic boolean value
65+
#
66+
# @example
67+
# boolean = AtomicBoolean.new(true)
68+
# puts boolean.value #=> true
69+
#
70+
# @rbs () -> bool
1771
def value
1872
@boolean.value
1973
end
2074

75+
# Tests if the current value is true.
76+
#
77+
# @return [true, false] true if the atomic value is true, false otherwise
78+
#
79+
# @example
80+
# boolean = AtomicBoolean.new(true)
81+
# puts boolean.true? #=> true
82+
# puts boolean.false? #=> false
83+
#
84+
# @rbs () -> bool
2185
def true?
2286
value == true
2387
end
2488

89+
# Tests if the current value is false.
90+
#
91+
# @return [true, false] true if the atomic value is false, false otherwise
92+
#
93+
# @example
94+
# boolean = AtomicBoolean.new(false)
95+
# puts boolean.false? #=> true
96+
# puts boolean.true? #=> false
97+
#
98+
# @rbs () -> bool
2599
def false?
26100
value == false
27101
end
28102

103+
# Atomically sets the value to true.
104+
#
105+
# This operation uses compare-and-swap to ensure atomicity,
106+
# making it safe for concurrent access.
107+
#
108+
# @return [true] Always returns true (the new value)
109+
#
110+
# @example
111+
# boolean = AtomicBoolean.new(false)
112+
# boolean.make_true
113+
# puts boolean.value #=> true
114+
#
115+
# @rbs () -> true
29116
def make_true
30117
@boolean.swap { true }
31118
end
32119

120+
# Atomically sets the value to false.
121+
#
122+
# This operation uses compare-and-swap to ensure atomicity,
123+
# making it safe for concurrent access.
124+
#
125+
# @return [false] Always returns false (the new value)
126+
#
127+
# @example
128+
# boolean = AtomicBoolean.new(true)
129+
# boolean.make_false
130+
# puts boolean.value #=> false
131+
#
132+
# @rbs () -> false
33133
def make_false
34134
@boolean.swap { false }
35135
end
36136

137+
# Atomically toggles the boolean value.
138+
#
139+
# Changes true to false and false to true using a compare-and-swap
140+
# operation, making it safe for concurrent access from multiple threads.
141+
#
142+
# @return [true, false] The new boolean value after toggling
143+
#
144+
# @example
145+
# boolean = AtomicBoolean.new(false)
146+
# boolean.toggle #=> true
147+
# boolean.toggle #=> false
148+
#
149+
# @example Thread-safe toggling
150+
# boolean = AtomicBoolean.new(false)
151+
# 10.times.map { Thread.new { boolean.toggle } }.each(&:join)
152+
#
153+
# @rbs () -> bool
37154
def toggle
38155
@boolean.swap { |current_value| !current_value }
39156
end

0 commit comments

Comments
 (0)