backports 3.18.0 → 3.23.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +435 -292
  3. data/Gemfile +3 -16
  4. data/README.md +306 -156
  5. data/backports.gemspec +1 -1
  6. data/lib/backports/2.0.0.rb +1 -1
  7. data/lib/backports/2.1.0/module/singleton_class.rb +8 -0
  8. data/lib/backports/2.1.0.rb +1 -1
  9. data/lib/backports/2.2.0/string/unicode_normalize.rb +3 -3
  10. data/lib/backports/2.2.0.rb +1 -1
  11. data/lib/backports/2.3.0/queue/close.rb +48 -0
  12. data/lib/backports/2.3.0/string.rb +3 -0
  13. data/lib/backports/2.3.0/struct/dig.rb +2 -0
  14. data/lib/backports/2.3.0.rb +1 -1
  15. data/lib/backports/2.4.0/bignum/dup.rb +5 -0
  16. data/lib/backports/2.4.0/bignum.rb +3 -0
  17. data/lib/backports/2.4.0/string/unpack1.rb +7 -0
  18. data/lib/backports/2.4.0.rb +1 -1
  19. data/lib/backports/2.5.0/dir/children.rb +4 -0
  20. data/lib/backports/2.5.0/dir/each_child.rb +7 -0
  21. data/lib/backports/2.5.0/hash/transform_keys.rb +10 -3
  22. data/lib/backports/2.5.0/string/undump.rb +2 -2
  23. data/lib/backports/2.5.0.rb +1 -1
  24. data/lib/backports/2.5.rb +1 -1
  25. data/lib/backports/2.6.0/enumerable/chain.rb +2 -0
  26. data/lib/backports/2.6.0.rb +2 -2
  27. data/lib/backports/2.6.rb +1 -1
  28. data/lib/backports/2.7.0/complex/{comparision.rb → comparison.rb} +0 -0
  29. data/lib/backports/2.7.0/enumerable/tally.rb +4 -3
  30. data/lib/backports/2.7.0/symbol/end_with.rb +9 -0
  31. data/lib/backports/2.7.0/symbol.rb +3 -0
  32. data/lib/backports/2.7.0.rb +2 -2
  33. data/lib/backports/3.0.0/env/except.rb +10 -0
  34. data/lib/backports/3.0.0/env.rb +3 -0
  35. data/lib/backports/3.0.0/hash/except.rb +10 -0
  36. data/lib/backports/3.0.0/hash/transform_keys.rb +48 -0
  37. data/lib/backports/3.0.0/hash.rb +3 -0
  38. data/lib/backports/3.0.0/ractor.rb +19 -0
  39. data/lib/backports/3.0.0/symbol/name.rb +21 -0
  40. data/lib/backports/3.0.0/symbol.rb +3 -0
  41. data/lib/backports/3.0.0.rb +3 -0
  42. data/lib/backports/3.0.rb +1 -0
  43. data/lib/backports/3.1.0/array/intersect.rb +16 -0
  44. data/lib/backports/3.1.0/array.rb +3 -0
  45. data/lib/backports/3.1.0/class/descendants.rb +11 -0
  46. data/lib/backports/3.1.0/class/subclasses.rb +11 -0
  47. data/lib/backports/3.1.0/class.rb +3 -0
  48. data/lib/backports/3.1.0/enumerable/compact.rb +5 -0
  49. data/lib/backports/3.1.0/enumerable/tally.rb +18 -0
  50. data/lib/backports/3.1.0/enumerable.rb +3 -0
  51. data/lib/backports/3.1.0/file/dirname.rb +16 -0
  52. data/lib/backports/3.1.0/file.rb +3 -0
  53. data/lib/backports/3.1.0/integer/try_convert.rb +7 -0
  54. data/lib/backports/3.1.0/integer.rb +3 -0
  55. data/lib/backports/3.1.0/match_data/match.rb +5 -0
  56. data/lib/backports/3.1.0/match_data/match_length.rb +6 -0
  57. data/lib/backports/3.1.0/match_data.rb +3 -0
  58. data/lib/backports/3.1.0/struct/keyword_init.rb +5 -0
  59. data/lib/backports/3.1.0/struct.rb +3 -0
  60. data/lib/backports/3.1.0.rb +3 -0
  61. data/lib/backports/3.1.rb +1 -0
  62. data/lib/backports/latest.rb +1 -1
  63. data/lib/backports/ractor/cloner.rb +103 -0
  64. data/lib/backports/ractor/errors.rb +20 -0
  65. data/lib/backports/ractor/filtered_queue.rb +205 -0
  66. data/lib/backports/ractor/queues.rb +66 -0
  67. data/lib/backports/ractor/ractor.rb +272 -0
  68. data/lib/backports/ractor/sharing.rb +97 -0
  69. data/lib/backports/version.rb +1 -1
  70. metadata +51 -8
@@ -0,0 +1,103 @@
1
+ # shareable_constant_value: literal
2
+
3
+ using ::RubyNext if defined?(::RubyNext)
4
+
5
+ module Backports
6
+ class Ractor
7
+ class Cloner
8
+ class << self
9
+ def deep_clone(obj)
10
+ return obj if Ractor.ractor_shareable_self?(obj, false) { false }
11
+
12
+ new.deep_clone(obj)
13
+ end
14
+
15
+ private :new
16
+ end
17
+
18
+ def initialize
19
+ @processed = {}.compare_by_identity
20
+ @changed = nil
21
+ end
22
+
23
+ def deep_clone(obj)
24
+ result = process(obj) do |r|
25
+ copy_contents(r)
26
+ end
27
+ return result if result
28
+
29
+ Ractor.ractor_mark_set_shareable(@processed)
30
+ obj
31
+ end
32
+
33
+ # Yields a deep copy.
34
+ # If no deep copy is needed, `obj` is returned and
35
+ # nothing is yielded
36
+ private def clone_deeper(obj)
37
+ return obj if Ractor.ractor_shareable_self?(obj, false) { false }
38
+
39
+ result = process(obj) do |r|
40
+ copy_contents(r)
41
+ end
42
+ return obj unless result
43
+
44
+ yield result if block_given?
45
+ result
46
+ end
47
+
48
+ # Yields if `obj` is a new structure
49
+ # Returns the deep copy, or `false` if no deep copy is needed
50
+ private def process(obj)
51
+ @processed.fetch(obj) do
52
+ # For recursive structures, assume that we'll need a duplicate.
53
+ # If that's not the case, we will have duplicated the whole structure
54
+ # for nothing...
55
+ @processed[obj] = result = obj.dup
56
+ changed = track_change { yield result }
57
+ return false if obj.frozen? && !changed
58
+
59
+ @changed = true
60
+ result.freeze if obj.frozen?
61
+
62
+ result
63
+ end
64
+ end
65
+
66
+ # returns if the block called `deep clone` and that the deep copy was needed
67
+ private def track_change
68
+ prev = @changed
69
+ @changed = false
70
+ yield
71
+ @changed
72
+ ensure
73
+ @changed = prev
74
+ end
75
+
76
+ # modifies in place `obj` by calling `deep clone` on its contents
77
+ private def copy_contents(obj)
78
+ case obj
79
+ when ::Hash
80
+ if obj.default
81
+ clone_deeper(obj.default) do |copy|
82
+ obj.default = copy
83
+ end
84
+ end
85
+ obj.transform_keys! { |key| clone_deeper(key) }
86
+ obj.transform_values! { |value| clone_deeper(value) }
87
+ when ::Array
88
+ obj.map! { |item| clone_deeper(item) }
89
+ when ::Struct
90
+ obj.each_pair do |key, item|
91
+ clone_deeper(item) { |copy| obj[key] = copy }
92
+ end
93
+ end
94
+ obj.instance_variables.each do |var|
95
+ clone_deeper(obj.instance_variable_get(var)) do |copy|
96
+ obj.instance_variable_set(var, copy)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ private_constant :Cloner
102
+ end
103
+ end
@@ -0,0 +1,20 @@
1
+ # shareable_constant_value: literal
2
+
3
+ module Backports
4
+ class Ractor
5
+ class ClosedError < ::StopIteration
6
+ end
7
+
8
+ class Error < ::StandardError
9
+ end
10
+
11
+ class RemoteError < Error
12
+ attr_reader :ractor
13
+
14
+ def initialize(message = nil)
15
+ @ractor = Ractor.current
16
+ super
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,205 @@
1
+ # shareable_constant_value: literal
2
+
3
+ module Backports
4
+ # Like ::Queue, but with
5
+ # - filtering
6
+ # - timeout
7
+ # - raises on closed queues
8
+ #
9
+ # Independent from other Ractor related backports.
10
+ class FilteredQueue
11
+ CONSUME_ON_ESCAPE = true
12
+
13
+ class ClosedQueueError < ::ClosedQueueError
14
+ end
15
+
16
+ class TimeoutError < ::ThreadError
17
+ end
18
+
19
+ class Message
20
+ # Not using Struct as we want comparision by identity
21
+ attr_reader :value
22
+ attr_accessor :reserved
23
+
24
+ def initialize(value)
25
+ @value = value
26
+ @reserved = false
27
+ end
28
+ end
29
+ private_constant :Message
30
+
31
+ attr_reader :num_waiting
32
+
33
+ # Timeout processing based on https://spin.atomicobject.com/2017/06/28/queue-pop-with-timeout-fixed/
34
+ def initialize
35
+ @mutex = ::Mutex.new
36
+ @queue = []
37
+ @closed = false
38
+ @received = ::ConditionVariable.new
39
+ @num_waiting = 0
40
+ end
41
+
42
+ def close
43
+ @mutex.synchronize do
44
+ @closed = true
45
+ @received.broadcast
46
+ end
47
+ self
48
+ end
49
+
50
+ def closed?
51
+ @closed
52
+ end
53
+
54
+ def <<(x)
55
+ @mutex.synchronize do
56
+ ensure_open
57
+ @queue << Message.new(x)
58
+ @received.signal
59
+ end
60
+ self
61
+ end
62
+ alias_method :push, :<<
63
+
64
+ def clear
65
+ @mutex.synchronize do
66
+ @queue.clear
67
+ end
68
+ self
69
+ end
70
+
71
+ def pop(timeout: nil, &block)
72
+ msg = nil
73
+ exclude = [] if block # exclusion list of messages rejected by this call
74
+ timeout_time = timeout + Time.now.to_f if timeout
75
+ while true do # rubocop:disable Style/InfiniteLoop, Style/WhileUntilDo
76
+ @mutex.synchronize do
77
+ reenter if reentrant?
78
+ msg = acquire!(timeout_time, exclude)
79
+ return consume!(msg).value unless block
80
+ end
81
+ return msg.value if filter?(msg, &block)
82
+ end
83
+ end
84
+
85
+ def empty?
86
+ avail = @mutex.synchronize do
87
+ available!
88
+ end
89
+
90
+ !avail
91
+ end
92
+
93
+ protected def timeout_value
94
+ raise self.class::TimeoutError, "timeout elapsed"
95
+ end
96
+
97
+ protected def closed_queue_value
98
+ ensure_open
99
+ end
100
+
101
+ # @return if outer message should be consumed or not
102
+ protected def reenter
103
+ true
104
+ end
105
+
106
+ ### private section
107
+ #
108
+ # bang methods require synchronization
109
+
110
+ # @returns:
111
+ # * true if message consumed (block result truthy or due to reentrant call)
112
+ # * false if rejected
113
+ private def filter?(msg)
114
+ consume = self.class::CONSUME_ON_ESCAPE
115
+ begin
116
+ reentered = consume_on_reentry(msg) do
117
+ consume = !!(yield msg.value)
118
+ end
119
+ reentered ? reenter : consume
120
+ ensure
121
+ commit(msg, consume) unless reentered
122
+ end
123
+ end
124
+
125
+ # @returns msg
126
+ private def consume!(msg)
127
+ @queue.delete(msg)
128
+ end
129
+
130
+ private def reject!(msg)
131
+ msg.reserved = false
132
+ @received.broadcast
133
+ end
134
+
135
+ private def commit(msg, consume)
136
+ @mutex.synchronize do
137
+ if consume
138
+ consume!(msg)
139
+ else
140
+ reject!(msg)
141
+ end
142
+ end
143
+ end
144
+
145
+ private def consume_on_reentry(msg)
146
+ q_map = current_filtered_queues
147
+ if (outer_msg = q_map[self])
148
+ commit(outer_msg, reenter)
149
+ end
150
+ q_map[self] = msg
151
+ begin
152
+ yield
153
+ ensure
154
+ reentered = !q_map.delete(self)
155
+ end
156
+ reentered
157
+ end
158
+
159
+ private def reentrant?
160
+ !!current_filtered_queues[self]
161
+ end
162
+
163
+ # @returns Hash { FilteredQueue => Message }
164
+ private def current_filtered_queues
165
+ t = Thread.current
166
+ t.thread_variable_get(:backports_currently_filtered_queues) or
167
+ t.thread_variable_set(:backports_currently_filtered_queues, {}.compare_by_identity)
168
+ end
169
+
170
+ # private methods assume @mutex synchonized
171
+ # adds to exclude list
172
+ private def acquire!(timeout_time, exclude = nil)
173
+ while true do # rubocop:disable Style/InfiniteLoop, Style/WhileUntilDo
174
+ if (msg = available!(exclude))
175
+ msg.reserved = true
176
+ exclude << msg if exclude
177
+ return msg
178
+ end
179
+ return closed_queue_value if @closed
180
+ # wait for element or timeout
181
+ if timeout_time
182
+ remaining_time = timeout_time - ::Time.now.to_f
183
+ return timeout_value if remaining_time <= 0
184
+ end
185
+ begin
186
+ @num_waiting += 1
187
+ @received.wait(@mutex, remaining_time)
188
+ ensure
189
+ @num_waiting -= 1
190
+ end
191
+ end
192
+ end
193
+
194
+ private def available!(exclude = nil)
195
+ @queue.find do |msg|
196
+ next if exclude && exclude.include?(msg)
197
+ !msg.reserved
198
+ end
199
+ end
200
+
201
+ private def ensure_open
202
+ raise self.class::ClosedQueueError, 'queue closed' if @closed
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,66 @@
1
+ # shareable_constant_value: literal
2
+
3
+ require_relative 'filtered_queue'
4
+
5
+ module Backports
6
+ class Ractor
7
+ # Standard ::Queue but raises if popping and closed
8
+ class BaseQueue < FilteredQueue
9
+ ClosedQueueError = Ractor::ClosedError
10
+
11
+ # yields message (if any)
12
+ def pop_non_blocking
13
+ yield pop(timeout: 0)
14
+ rescue TimeoutError
15
+ nil
16
+ end
17
+ end
18
+
19
+ class IncomingQueue < BaseQueue
20
+ TYPE = :incoming
21
+
22
+ protected def reenter
23
+ raise Ractor::Error, 'Can not reenter'
24
+ end
25
+ end
26
+
27
+ # * Wraps exception
28
+ # * Add `ack: ` to push (blocking)
29
+ class OutgoingQueue < BaseQueue
30
+ TYPE = :outgoing
31
+
32
+ WrappedException = ::Struct.new(:exception, :ractor)
33
+
34
+ def initialize
35
+ @ack_queue = ::Queue.new
36
+ super
37
+ end
38
+
39
+ def pop(timeout: nil, ack: true)
40
+ r = super(timeout: timeout)
41
+ @ack_queue << :done if ack
42
+ raise r.exception if WrappedException === r
43
+
44
+ r
45
+ end
46
+
47
+ def close(how = :hard)
48
+ super()
49
+ return if how == :soft
50
+
51
+ clear
52
+ @ack_queue.close
53
+ end
54
+
55
+ def push(obj, ack:)
56
+ super(obj)
57
+ if ack
58
+ r = @ack_queue.pop # block until popped
59
+ raise ClosedError, "The #{self.class::TYPE}-port is already closed" unless r == :done
60
+ end
61
+ self
62
+ end
63
+ end
64
+ private_constant :BaseQueue, :OutgoingQueue, :IncomingQueue
65
+ end
66
+ end
@@ -0,0 +1,272 @@
1
+ # shareable_constant_value: literal
2
+
3
+ # Ruby 2.0+ backport of `Ractor` class
4
+ # Extra private methods and instance variables all start with `ractor_`
5
+ module Backports
6
+ class Ractor
7
+ require_relative '../tools/arguments'
8
+
9
+ require_relative 'cloner'
10
+ require_relative 'errors'
11
+ require_relative 'queues'
12
+ require_relative 'sharing'
13
+
14
+ RactorThreadGroups = ::ObjectSpace::WeakMap.new # ThreadGroup => Ractor
15
+ private_constant :RactorThreadGroups
16
+ # Implementation notes
17
+ #
18
+ # Uses one `Thread` for each `Ractor`, as well as queues for communication
19
+ #
20
+ # The incoming queue is strict: contrary to standard queue, you can't pop from an empty closed queue.
21
+ # Since standard queues return `nil` is those conditions, we wrap/unwrap `nil` values and consider
22
+ # all `nil` values to be results of closed queues. `ClosedQueueError` are re-raised as `Ractor::ClosedError`
23
+ #
24
+ # The outgoing queue is strict and blocking. Same wrapping / raising as incoming,
25
+ # with an extra queue to acknowledge when a value has been read (or if the port is closed while waiting).
26
+ #
27
+ # The last result is a bit tricky as it needs to be pushed on the outgoing queue but can not be blocking.
28
+ # For this, we "soft close" the outgoing port.
29
+
30
+ def initialize(*args, &block)
31
+ @ractor_incoming_queue = IncomingQueue.new
32
+ @ractor_outgoing_queue = OutgoingQueue.new
33
+ raise ::ArgumentError, 'must be called with a block' unless block
34
+
35
+ kw = args.last
36
+ if kw.is_a?(::Hash) && kw.size == 1 && kw.key?(:name)
37
+ args.pop
38
+ name = kw[:name]
39
+ end
40
+ @ractor_name = name && Backports.coerce_to_str(name)
41
+
42
+ @id = Ractor.ractor_next_id
43
+ if Ractor.main == nil # then initializing main Ractor
44
+ @ractor_thread = ::Thread.current
45
+ @ractor_origin = nil
46
+ @ractor_thread.thread_variable_set(:backports_ractor, self)
47
+ else
48
+ @ractor_origin = caller(1, 1).first.split(':in `').first
49
+
50
+ args.map! { |a| Ractor.ractor_isolate(a, false) }
51
+ ractor_thread_start(args, block)
52
+ end
53
+ end
54
+
55
+ private def ractor_thread_start(args, block)
56
+ ::Thread.new do
57
+ @ractor_thread = ::Thread.current
58
+ @ractor_thread_group = ::ThreadGroup.new
59
+ RactorThreadGroups[@ractor_thread_group] = self
60
+ @ractor_thread_group.add(@ractor_thread)
61
+ ::Thread.current.thread_variable_set(:backports_ractor, self)
62
+ result = nil
63
+ begin
64
+ result = instance_exec(*args, &block)
65
+ rescue ::Exception => err # rubocop:disable Lint/RescueException
66
+ begin
67
+ raise RemoteError, "thrown by remote Ractor: #{err.message}"
68
+ rescue RemoteError => e # Hack to create exception with `cause`
69
+ result = OutgoingQueue::WrappedException.new(e)
70
+ end
71
+ ensure
72
+ ractor_thread_terminate(result)
73
+ end
74
+ end
75
+ end
76
+
77
+ private def ractor_thread_terminate(result)
78
+ begin
79
+ ractor_outgoing_queue.push(result, ack: false) unless ractor_outgoing_queue.closed?
80
+ rescue ::ClosedQueueError
81
+ return # ignore
82
+ end
83
+ ractor_incoming_queue.close
84
+ ractor_outgoing_queue.close(:soft)
85
+ ensure
86
+ # TODO: synchronize?
87
+ @ractor_thread_group.list.each do |thread|
88
+ thread.kill unless thread == Thread.current
89
+ end
90
+ end
91
+
92
+ def send(obj, move: false)
93
+ ractor_incoming_queue << Ractor.ractor_isolate(obj, move)
94
+ self
95
+ rescue ::ClosedQueueError
96
+ raise ClosedError, 'The incoming-port is already closed'
97
+ end
98
+ alias_method :<<, :send
99
+
100
+ def take
101
+ ractor_outgoing_queue.pop(ack: true)
102
+ end
103
+
104
+ def name
105
+ @ractor_name
106
+ end
107
+
108
+ RACTOR_STATE = {
109
+ 'sleep' => 'blocking',
110
+ 'run' => 'running',
111
+ 'aborting' => 'aborting',
112
+ false => 'terminated',
113
+ nil => 'terminated',
114
+ }.freeze
115
+ private_constant :RACTOR_STATE
116
+
117
+ def inspect
118
+ state = RACTOR_STATE[@ractor_thread ? @ractor_thread.status : 'run']
119
+ info = [
120
+ "Ractor:##{@id}",
121
+ name,
122
+ @ractor_origin,
123
+ state,
124
+ ].compact.join(' ')
125
+
126
+ "#<#{info}>"
127
+ end
128
+
129
+ def close_incoming
130
+ r = ractor_incoming_queue.closed?
131
+ ractor_incoming_queue.close
132
+ r
133
+ end
134
+
135
+ def close_outgoing
136
+ r = ractor_outgoing_queue.closed?
137
+ ractor_outgoing_queue.close
138
+ r
139
+ end
140
+
141
+ private def receive
142
+ ractor_incoming_queue.pop
143
+ end
144
+
145
+ private def receive_if(&block)
146
+ raise ::ArgumentError, 'no block given' unless block
147
+ ractor_incoming_queue.pop(&block)
148
+ end
149
+
150
+ def [](key)
151
+ Ractor.current.ractor_locals[key]
152
+ end
153
+
154
+ def []=(key, value)
155
+ Ractor.current.ractor_locals[key] = value
156
+ end
157
+
158
+ # @api private
159
+ def ractor_locals
160
+ @ractor_locals ||= {}.compare_by_identity
161
+ end
162
+
163
+ class << self
164
+ def yield(value, move: false)
165
+ value = ractor_isolate(value, move)
166
+ current.ractor_outgoing_queue.push(value, ack: true)
167
+ rescue ::ClosedQueueError
168
+ raise ClosedError, 'The outgoing-port is already closed'
169
+ end
170
+
171
+ def receive
172
+ current.__send__(:receive)
173
+ end
174
+ alias_method :recv, :receive
175
+
176
+ def receive_if(&block)
177
+ current.__send__(:receive_if, &block)
178
+ end
179
+
180
+ def select(*ractors, yield_value: not_given = true, move: false)
181
+ cur = Ractor.current
182
+ queues = ractors.map do |r|
183
+ r == cur ? r.ractor_incoming_queue : r.ractor_outgoing_queue
184
+ end
185
+ if !not_given
186
+ out = current.ractor_outgoing_queue
187
+ yield_value = ractor_isolate(yield_value, move)
188
+ elsif ractors.empty?
189
+ raise ::ArgumentError, 'specify at least one ractor or `yield_value`'
190
+ end
191
+
192
+ while true # rubocop:disable Style/InfiniteLoop
193
+ # Don't `loop`, in case of `ClosedError` (not that there should be any)
194
+ queues.each_with_index do |q, i|
195
+ q.pop_non_blocking do |val|
196
+ r = ractors[i]
197
+ return [r == cur ? :receive : r, val]
198
+ end
199
+ end
200
+
201
+ if out && out.num_waiting > 0
202
+ # Not quite atomic...
203
+ out.push(yield_value, ack: true)
204
+ return [:yield, nil]
205
+ end
206
+
207
+ sleep(0.001)
208
+ end
209
+ end
210
+
211
+ def make_shareable(obj)
212
+ return obj if ractor_check_shareability?(obj, true)
213
+
214
+ raise Ractor::Error, '#freeze does not freeze object correctly'
215
+ end
216
+
217
+ def shareable?(obj)
218
+ ractor_check_shareability?(obj, false)
219
+ end
220
+
221
+ def current
222
+ ::Thread.current.thread_variable_get(:backports_ractor) ||
223
+ ::Thread.current.thread_variable_set(:backports_ractor, ractor_find_current)
224
+ end
225
+
226
+ def count
227
+ ::ObjectSpace.each_object(Ractor).count(&:ractor_live?)
228
+ end
229
+
230
+ # @api private
231
+ def ractor_reset
232
+ ::ObjectSpace.each_object(Ractor).each do |r|
233
+ next if r == Ractor.current
234
+ next unless (th = r.ractor_thread)
235
+
236
+ th.kill
237
+ th.join
238
+ end
239
+ Ractor.current.ractor_incoming_queue.clear
240
+ end
241
+
242
+ # @api private
243
+ def ractor_next_id
244
+ @id ||= 0
245
+ @id += 1
246
+ end
247
+
248
+ attr_reader :main
249
+
250
+ private def ractor_init
251
+ @ractor_shareable = ::ObjectSpace::WeakMap.new
252
+ @main = Ractor.new { nil }
253
+ RactorThreadGroups[::ThreadGroup::Default] = @main
254
+ end
255
+
256
+ private def ractor_find_current
257
+ RactorThreadGroups[Thread.current.group]
258
+ end
259
+ end
260
+
261
+ # @api private
262
+ def ractor_live?
263
+ !defined?(@ractor_thread) || # May happen if `count` is called from another thread before `initialize` has completed
264
+ @ractor_thread.status
265
+ end
266
+
267
+ # @api private
268
+ attr_reader :ractor_outgoing_queue, :ractor_incoming_queue, :ractor_thread
269
+
270
+ ractor_init
271
+ end
272
+ end