backports 3.18.2 → 3.21.0

Sign up to get free protection for your applications and to get access to all the features.
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.