backports 3.18.2 → 3.21.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -2
  3. data/Gemfile +2 -2
  4. data/README.md +35 -14
  5. data/lib/backports/2.0.0.rb +1 -1
  6. data/lib/backports/2.1.0.rb +1 -1
  7. data/lib/backports/2.2.0.rb +1 -1
  8. data/lib/backports/2.2.0/string/unicode_normalize.rb +3 -3
  9. data/lib/backports/2.3.0.rb +1 -1
  10. data/lib/backports/2.3.0/queue/close.rb +48 -0
  11. data/lib/backports/2.4.0.rb +1 -1
  12. data/lib/backports/2.4.0/bignum.rb +3 -0
  13. data/lib/backports/2.4.0/bignum/dup.rb +5 -0
  14. data/lib/backports/2.4.0/string/unpack1.rb +7 -0
  15. data/lib/backports/2.5.0.rb +1 -1
  16. data/lib/backports/2.5.0/hash/transform_keys.rb +10 -3
  17. data/lib/backports/2.5.0/string/undump.rb +2 -2
  18. data/lib/backports/2.5.rb +1 -1
  19. data/lib/backports/2.6.0.rb +2 -2
  20. data/lib/backports/2.6.0/enumerable/chain.rb +2 -0
  21. data/lib/backports/2.6.rb +1 -1
  22. data/lib/backports/2.7.0.rb +2 -2
  23. data/lib/backports/3.0.0.rb +3 -0
  24. data/lib/backports/3.0.0/env.rb +3 -0
  25. data/lib/backports/3.0.0/env/except.rb +10 -0
  26. data/lib/backports/3.0.0/hash.rb +3 -0
  27. data/lib/backports/3.0.0/hash/except.rb +10 -0
  28. data/lib/backports/3.0.0/hash/transform_keys.rb +48 -0
  29. data/lib/backports/3.0.0/ractor.rb +19 -0
  30. data/lib/backports/3.0.0/symbol.rb +3 -0
  31. data/lib/backports/3.0.0/symbol/name.rb +11 -0
  32. data/lib/backports/3.0.rb +1 -0
  33. data/lib/backports/ractor/cloner.rb +103 -0
  34. data/lib/backports/ractor/errors.rb +20 -0
  35. data/lib/backports/ractor/filtered_queue.rb +205 -0
  36. data/lib/backports/ractor/queues.rb +66 -0
  37. data/lib/backports/ractor/ractor.rb +272 -0
  38. data/lib/backports/ractor/sharing.rb +97 -0
  39. data/lib/backports/version.rb +1 -1
  40. metadata +23 -3
@@ -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
@@ -0,0 +1,97 @@
1
+ # shareable_constant_value: literal
2
+
3
+ module Backports
4
+ class Ractor
5
+ class << self
6
+ # @api private
7
+ def ractor_isolate(val, move = false)
8
+ return val if move
9
+
10
+ Cloner.deep_clone(val)
11
+ end
12
+
13
+ private def ractor_check_shareability?(obj, freeze_all)
14
+ ractor_shareable_self?(obj, freeze_all) do
15
+ visited = {}
16
+
17
+ return false unless ractor_shareable_parts?(obj, freeze_all, visited)
18
+
19
+ ractor_mark_set_shareable(visited)
20
+
21
+ true
22
+ end
23
+ end
24
+
25
+ # yield if shareability can't be determined without looking at its parts
26
+ def ractor_shareable_self?(obj, freeze_all)
27
+ return true if @ractor_shareable.key?(obj)
28
+ return true if ractor_shareable_by_nature?(obj, freeze_all)
29
+ if obj.frozen? || (freeze_all && obj.freeze)
30
+ yield
31
+ else
32
+ false
33
+ end
34
+ end
35
+
36
+ private def ractor_shareable_parts?(obj, freeze_all, visited)
37
+ return true if visited.key?(obj)
38
+ visited[obj] = true
39
+
40
+ ractor_traverse(obj) do |part|
41
+ return false unless ractor_shareable_self?(part, freeze_all) do
42
+ ractor_shareable_parts?(part, freeze_all, visited)
43
+ end
44
+ end
45
+
46
+ true
47
+ end
48
+
49
+ def ractor_mark_set_shareable(visited)
50
+ visited.each do |key|
51
+ @ractor_shareable[key] = Ractor
52
+ end
53
+ end
54
+
55
+ private def ractor_traverse(obj, &block)
56
+ case obj
57
+ when ::Hash
58
+ Hash obj.default
59
+ yield obj.default_proc
60
+ obj.each do |key, value|
61
+ yield key
62
+ yield value
63
+ end
64
+ when ::Range
65
+ yield obj.begin
66
+ yield obj.end
67
+ when ::Array, ::Struct
68
+ obj.each(&block)
69
+ when ::Complex
70
+ yield obj.real
71
+ yield obj.imaginary
72
+ when ::Rational
73
+ yield obj.numerator
74
+ yield obj.denominator
75
+ end
76
+ obj.instance_variables.each do |var|
77
+ yield obj.instance_variable_get(var)
78
+ end
79
+ end
80
+
81
+ private def ractor_shareable_by_nature?(obj, freeze_all)
82
+ case obj
83
+ when ::Module, Ractor
84
+ true
85
+ when ::Regexp, ::Range, ::Numeric
86
+ !freeze_all # Assume that these are literals that would have been frozen in 3.0
87
+ # unless we're making them shareable, in which case we might as well
88
+ # freeze them for real.
89
+ when ::Symbol, false, true, nil # Were only frozen in Ruby 2.3+
90
+ true
91
+ else
92
+ false
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,3 +1,3 @@
1
1
  module Backports
2
- VERSION = "3.18.2" unless Backports.constants.include? :VERSION # the guard is against a redefinition warning that happens on Travis
2
+ VERSION = "3.21.0" unless Backports.constants.include? :VERSION # the guard is against a redefinition warning that happens on Travis
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backports
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.18.2
4
+ version: 3.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc-André Lafortune
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-26 00:00:00.000000000 Z
11
+ date: 2021-03-31 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Essential backports that enable many of the nice features of Ruby for
14
14
  earlier versions.
@@ -425,6 +425,7 @@ files:
425
425
  - lib/backports/2.3.0/numeric.rb
426
426
  - lib/backports/2.3.0/numeric/negative.rb
427
427
  - lib/backports/2.3.0/numeric/positive.rb
428
+ - lib/backports/2.3.0/queue/close.rb
428
429
  - lib/backports/2.3.0/string.rb
429
430
  - lib/backports/2.3.0/string/uminus.rb
430
431
  - lib/backports/2.3.0/string/uplus.rb
@@ -432,6 +433,8 @@ files:
432
433
  - lib/backports/2.3.0/struct/dig.rb
433
434
  - lib/backports/2.3.rb
434
435
  - lib/backports/2.4.0.rb
436
+ - lib/backports/2.4.0/bignum.rb
437
+ - lib/backports/2.4.0/bignum/dup.rb
435
438
  - lib/backports/2.4.0/comparable.rb
436
439
  - lib/backports/2.4.0/comparable/clamp.rb
437
440
  - lib/backports/2.4.0/enumerable.rb
@@ -452,6 +455,7 @@ files:
452
455
  - lib/backports/2.4.0/regexp/match.rb
453
456
  - lib/backports/2.4.0/string.rb
454
457
  - lib/backports/2.4.0/string/match.rb
458
+ - lib/backports/2.4.0/string/unpack1.rb
455
459
  - lib/backports/2.4.0/true_class.rb
456
460
  - lib/backports/2.4.0/true_class/dup.rb
457
461
  - lib/backports/2.4.rb
@@ -529,6 +533,16 @@ files:
529
533
  - lib/backports/2.7.0/time/ceil.rb
530
534
  - lib/backports/2.7.0/time/floor.rb
531
535
  - lib/backports/2.7.rb
536
+ - lib/backports/3.0.0.rb
537
+ - lib/backports/3.0.0/env.rb
538
+ - lib/backports/3.0.0/env/except.rb
539
+ - lib/backports/3.0.0/hash.rb
540
+ - lib/backports/3.0.0/hash/except.rb
541
+ - lib/backports/3.0.0/hash/transform_keys.rb
542
+ - lib/backports/3.0.0/ractor.rb
543
+ - lib/backports/3.0.0/symbol.rb
544
+ - lib/backports/3.0.0/symbol/name.rb
545
+ - lib/backports/3.0.rb
532
546
  - lib/backports/basic_object.rb
533
547
  - lib/backports/force/array_map.rb
534
548
  - lib/backports/force/enumerable_map.rb
@@ -536,6 +550,12 @@ files:
536
550
  - lib/backports/force/string_length.rb
537
551
  - lib/backports/force/string_size.rb
538
552
  - lib/backports/latest.rb
553
+ - lib/backports/ractor/cloner.rb
554
+ - lib/backports/ractor/errors.rb
555
+ - lib/backports/ractor/filtered_queue.rb
556
+ - lib/backports/ractor/queues.rb
557
+ - lib/backports/ractor/ractor.rb
558
+ - lib/backports/ractor/sharing.rb
539
559
  - lib/backports/rails.rb
540
560
  - lib/backports/rails/array.rb
541
561
  - lib/backports/rails/enumerable.rb
@@ -585,7 +605,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
585
605
  - !ruby/object:Gem::Version
586
606
  version: '0'
587
607
  requirements: []
588
- rubygems_version: 3.1.2
608
+ rubygems_version: 3.2.3
589
609
  signing_key:
590
610
  specification_version: 4
591
611
  summary: Backports of Ruby features for older Ruby.