atomic-ruby 0.6.5 → 0.7.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: 456e4fcacc95b4ceaf41c86933754eb112532ec91f33aad3672f85abc49c92ca
4
- data.tar.gz: 53e2e40cd821bc065218c61aa7be2a100add7e6071d51d409e36f87d3c520da3
3
+ metadata.gz: 673339dcf17f8da8f0f34326cb31a5a1514b820b4c31229f0022290f50c0a7fe
4
+ data.tar.gz: 289ebbf398e1f2a9bc2b58623680525c02b57056789a589c329f51977b46c250
5
5
  SHA512:
6
- metadata.gz: 534e5ceb68455cfd921c5c2b4312e8cf36179bb324c047dfafbade88b3ecac46a91c4388039437dabd01a2262d60e3da628d2269d6f857547a3175c2783d2ab3
7
- data.tar.gz: e12909f23008b2d50df62aa8d5488ba72d632d230a0f507362a9574882d1447a5c60cc6547a34dc52106b4cc42cf41af3d00fa65cfa43df69bdc9b37fa1e3048
6
+ metadata.gz: 5d495876e6706f589bbe91dcea4ad2ba71ab525b32ad897cdd6762de2a3451de91874c7446d8a439323e316d6b13f9748f19f304871a435916deb40e2f9239f6
7
+ data.tar.gz: be35ad1d840b4ea84eecf942087571a7f78b44bb04b3365f18491fed64fc2b5d3fb2182a614a05ad66b55e723c39680d7aa19810aa57bcd24a0166971bd1761c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.7.0] - 2025-10-20
4
+
5
+ - Improve thread safety, performance, and error handling across atomic classes
6
+
7
+ ## [0.6.6] - 2025-10-16
8
+
9
+ - Fix individual file requires
10
+
3
11
  ## [0.6.5] - 2025-10-16
4
12
 
5
13
  - Move shortcut aliases for `AtomicRuby` namespaced classes to respective files
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "atomic_ruby/atomic_ruby"
4
+
3
5
  module AtomicRuby
4
6
  class Atom
5
7
  def initialize(value)
@@ -4,14 +4,12 @@ require_relative "atom"
4
4
 
5
5
  module AtomicRuby
6
6
  class AtomicBoolean
7
- class InvalidBooleanError < StandardError; end
8
-
9
- def initialize(value)
10
- unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
11
- raise InvalidBooleanError, "expected boolean to be a `TrueClass` or `FalseClass`, got #{value.class}"
7
+ def initialize(boolean)
8
+ unless boolean.is_a?(TrueClass) || boolean.is_a?(FalseClass)
9
+ raise ArgumentError, "boolean must be a TrueClass or FalseClass, got #{boolean.class}"
12
10
  end
13
11
 
14
- @boolean = Atom.new(value)
12
+ @boolean = Atom.new(boolean)
15
13
 
16
14
  Ractor.make_shareable(self)
17
15
  end
@@ -4,12 +4,12 @@ require_relative "atom"
4
4
 
5
5
  module AtomicRuby
6
6
  class AtomicCountDownLatch
7
- class InvalidCountError < StandardError; end
8
- class AlreadyCountedDownError < StandardError; end
7
+ class Error < StandardError; end
8
+ class AlreadyCountedDownError < Error; end
9
9
 
10
10
  def initialize(count)
11
- unless count.is_a?(Integer)
12
- raise InvalidCountError, "expected count to be an `Integer`, got #{count.class}"
11
+ unless count.is_a?(Integer) && count > 0
12
+ raise ArgumentError, "count must be a positive Integer, got #{count.class}"
13
13
  end
14
14
 
15
15
  @count = Atom.new(count)
@@ -22,11 +22,18 @@ module AtomicRuby
22
22
  end
23
23
 
24
24
  def count_down
25
- unless @count.value > 0
26
- raise AlreadyCountedDownError, "count has already reached zero"
25
+ already_counted_down = false
26
+ new_count = @count.swap do |current_count|
27
+ if current_count == 0
28
+ already_counted_down = true
29
+ current_count
30
+ else
31
+ current_count - 1
32
+ end
27
33
  end
28
-
29
- @count.swap { |current_value| current_value - 1 }
34
+ raise AlreadyCountedDownError, "already counted down to zero" if already_counted_down
35
+
36
+ new_count
30
37
  end
31
38
 
32
39
  def wait
@@ -1,35 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "atom"
4
- require_relative "atomic_boolean"
5
4
 
6
5
  module AtomicRuby
7
6
  class AtomicThreadPool
8
- class UnsupportedWorkTypeError < StandardError; end
9
- class InvalidWorkQueueingError < StandardError; end
7
+ class Error < StandardError; end
8
+
9
+ class EnqueuedWorkAfterShutdownError < Error
10
+ def message = "cannot queue work after shutdown"
11
+ end
10
12
 
11
13
  def initialize(size:, name: nil)
14
+ raise ArgumentError, "size must be a positive Integer" unless size.is_a?(Integer) && size > 0
15
+ raise ArgumentError, "name must be a String" unless name.nil? || name.is_a?(String)
16
+
12
17
  @size = size
13
18
  @name = name
14
- @queue = Atom.new([])
15
- @threads = []
19
+
20
+ @state = Atom.new(queue: [], shutdown: false)
16
21
  @started_threads = Atom.new(0)
17
- @stopping = AtomicBoolean.new(false)
22
+ @threads = []
18
23
 
19
24
  start
20
25
  end
21
26
 
22
27
  def <<(work)
23
- unless work.is_a?(Proc) || work == :stop
24
- raise UnsupportedWorkTypeError, "expected work to be a `Proc`, got #{work.class}"
25
- end
26
-
27
- if @stopping.true?
28
- raise InvalidWorkQueueingError, "cannot queue work during or after pool shutdown"
28
+ state = @state.swap do |current_state|
29
+ if current_state[:shutdown]
30
+ current_state
31
+ else
32
+ current_state.merge(queue: [*current_state[:queue], work])
33
+ end
29
34
  end
30
-
31
- @queue.swap { |current_queue| current_queue += [work] }
32
- true
35
+ raise EnqueuedWorkAfterShutdownError if state[:shutdown]
33
36
  end
34
37
 
35
38
  def length
@@ -37,20 +40,31 @@ module AtomicRuby
37
40
  end
38
41
 
39
42
  def queue_length
40
- @queue.value.length
43
+ @state.value[:queue].length
41
44
  end
42
45
 
43
46
  def shutdown
44
- self << :stop
47
+ already_shutdown = false
48
+ @state.swap do |current_state|
49
+ if current_state[:shutdown]
50
+ already_shutdown = true
51
+ current_state
52
+ else
53
+ current_state.merge(shutdown: true)
54
+ end
55
+ end
56
+ return if already_shutdown
57
+
58
+ Thread.pass until @state.value[:queue].empty?
59
+
45
60
  @threads.each(&:join)
46
- true
47
61
  end
48
62
 
49
63
  private
50
64
 
51
65
  def start
52
- @threads = @size.times.map do |num|
53
- Thread.new(num) do |idx|
66
+ @size.times do |num|
67
+ @threads << Thread.new(num) do |idx|
54
68
  thread_name = String.new("AtomicThreadPool thread #{idx}")
55
69
  thread_name << " for #{@name}" if @name
56
70
  Thread.current.name = thread_name
@@ -59,9 +73,23 @@ module AtomicRuby
59
73
 
60
74
  loop do
61
75
  work = nil
62
- @queue.swap { |current_queue| work = current_queue.last; current_queue[0..-2] }
63
- case work
64
- when Proc
76
+ should_shutdown = false
77
+
78
+ @state.swap do |current_state|
79
+ if current_state[:shutdown] && current_state[:queue].empty?
80
+ should_shutdown = true
81
+ current_state
82
+ elsif current_state[:queue].empty?
83
+ current_state
84
+ else
85
+ work = current_state[:queue].first
86
+ current_state.merge(queue: current_state[:queue].drop(1))
87
+ end
88
+ end
89
+
90
+ if should_shutdown
91
+ break
92
+ elsif work
65
93
  begin
66
94
  work.call
67
95
  rescue => err
@@ -69,18 +97,13 @@ module AtomicRuby
69
97
  puts "#{err.class}: #{err.message}"
70
98
  puts err.backtrace.join("\n")
71
99
  end
72
- when :stop
73
- @stopping.make_true
74
- when NilClass
75
- if @stopping.true?
76
- break
77
- else
78
- Thread.pass
79
- end
100
+ else
101
+ Thread.pass
80
102
  end
81
103
  end
82
104
  end
83
105
  end
106
+ @threads.freeze
84
107
 
85
108
  Thread.pass until @started_threads.value == @size
86
109
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AtomicRuby
4
- VERSION = "0.6.5"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/atomic-ruby.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "atomic_ruby/atomic_ruby"
4
-
5
3
  require_relative "atomic-ruby/version"
6
4
  require_relative "atomic-ruby/atom"
7
5
  require_relative "atomic-ruby/atomic_boolean"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: atomic-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.5
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Young
@@ -50,7 +50,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
50
50
  - !ruby/object:Gem::Version
51
51
  version: '0'
52
52
  requirements: []
53
- rubygems_version: 3.6.9
53
+ rubygems_version: 3.7.2
54
54
  specification_version: 4
55
55
  summary: Atomic primitives for Ruby
56
56
  test_files: []