backports 3.19.0 → 3.20.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: 7545e11eda01d2ea412b51973b69d70ede1544ad14d11511085f80d51f1e9924
4
- data.tar.gz: 9cfd27da7f62e6606b73afa3770e25eb7fd90d254eb95c242fe375e38a10f078
3
+ metadata.gz: b3d95602bc2a5f1d8835d424036e6d51671baa7c8d46f4482c61b6519044d493
4
+ data.tar.gz: '059fa80520c54c368ad38f6477bbb2b9debe734970b0e007ba04be2b37c0138f'
5
5
  SHA512:
6
- metadata.gz: 64f2afe9cf49d42da0122399c765cf8e9eda203145957fa3a296f169ac36c1f471cd08d3384d397b987c9cf368947e4f5db6fe0ba460d0fbad014ff0a40e35e8
7
- data.tar.gz: 0d52dd7e5b79dd2dd225ddff67a34bdd329141e4c0ce924369271ce9e4fde48e2462452940e18110d64b6d9c10f3fd8c4cd5d57a8944317a8124c0b37f22887a
6
+ metadata.gz: 0b4031dc752ebb19df160368c272e37b0bf9a939288ccccc9d5043567efed030d157d2da1668ec0787216ab05f730b3ff8d7c23b78a5e2400719cd5183232a69
7
+ data.tar.gz: c92bbe1f69c27f5ed6e18162e8fe96665a9828480bd2c10a13750f1b8190db53a0ef25b2009f2414cb3d193e22c0810b12a259ef84d079bac6f53dde9aafcd96
@@ -8,7 +8,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
8
8
 
9
9
  Note: [Next major version (X-mas 2021?)](https://github.com/marcandre/backports/issues/139) may drop support for Ruby < 2.2, please comment.
10
10
 
11
- ## [3.19.0](https://github.com/marcandre/backports/compare/v3.19.0...v3.18.2) - 2020-12-28
11
+ ## [3.20.0](https://github.com/marcandre/backports/compare/v3.19.0...v3.20.0) - 2020-12-30
12
+
13
+ ### Added
14
+
15
+ Ractor backport (all methods)
16
+
17
+ ## [3.19.0](https://github.com/marcandre/backports/compare/v3.18.2...v3.19.0) - 2020-12-28
12
18
 
13
19
  ### Added
14
20
 
@@ -35,7 +41,7 @@ Note: [Next major version (X-mas 2021?)](https://github.com/marcandre/backports/
35
41
 
36
42
  Require per ruby version now properly requiring 2.3.0 backports for `String` [#152]
37
43
 
38
- ## [3.18.1](https://github.com/marcandre/backports/compare/v3.18.1...v3.18.2) - 2020-66-22
44
+ ## [3.18.1](https://github.com/marcandre/backports/compare/v3.18.1...v3.18.2) - 2020-06-22
39
45
 
40
46
  ### Fixed
41
47
 
data/Gemfile CHANGED
@@ -11,6 +11,6 @@ end
11
11
 
12
12
  if RUBY_VERSION >= '2.4.0'
13
13
  group :development do
14
- gem 'rubocop', '~> 1.1.0'
14
+ gem 'rubocop', '~> 1.6.0'
15
15
  end
16
16
  end
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Backports Library [<img src="https://travis-ci.org/marcandre/backports.svg?branch=master">](https://travis-ci.org/marcandre/backports) [<img src="https://badge.fury.io/rb/backports.svg" alt="Gem Version" />](http://badge.fury.io/rb/backports) [![Tidelift](https://tidelift.com/badges/package/rubygems/backports)](https://tidelift.com/subscription/pkg/rubygems-backports?utm_source=rubygems-backports&utm_medium=referral&utm_campaign=readme)
2
2
 
3
- Yearning to use some of the new cool features in Ruby 2.7 while using 2.3.x?
3
+ Yearning to use write a gem using some new cool features in Ruby 3.0 while
4
+ still supporting Ruby 2.5.x?
4
5
  Have some legacy code in Ruby 1.8 but can't live without `flat_map`?
5
6
 
6
7
  This gem is for you!
@@ -16,13 +17,13 @@ for Ruby < 2.2.
16
17
 
17
18
  ### Explicitly (recommended)
18
19
 
19
- For example, if you want to use transform_values and transform_keys, even in
20
+ For example, if you want to use `transform_values` and `transform_keys`, even in
20
21
  Ruby implementations that don't include it:
21
22
 
22
23
  require 'backports/2.4.0/hash/transform_values'
23
24
  require 'backports/2.5.0/hash/transform_keys'
24
25
 
25
- This will enable Hash#transform_values and Hash#transform_keys, using the
26
+ This will enable `Hash#transform_values` and `Hash#transform_keys`, using the
26
27
  native versions if available or otherwise provide a pure Ruby version.
27
28
 
28
29
  ### By Module
@@ -32,19 +33,17 @@ Class:
32
33
 
33
34
  require 'backports/2.3.0/hash'
34
35
 
35
- This will make sure that Hash responds to dig, fetch_values, <, <=, >, >= and
36
- to_proc
36
+ This will make sure that Hash responds to `dig`, `fetch_values`, `to_proc` and comparisons.
37
37
 
38
38
  ### Up to a specific Ruby version (for quick coding)
39
39
 
40
40
  You can load all backports up to a specific version.
41
- For example, to bring any
42
- version of Ruby mostly up to Ruby 2.7.0's standards:
41
+ For example, to bring any version of Ruby mostly up to Ruby 3.0.0's standards:
43
42
 
44
- require 'backports/2.7.0'
43
+ require 'backports/3.0.0'
45
44
 
46
45
  This will bring in all the features of 1.8.7 and many features of Ruby 1.9.x
47
- all the way up to Ruby 2.7.0 (for all versions of Ruby)!
46
+ all the way up to Ruby 3.0.0 (for all versions of Ruby)!
48
47
 
49
48
  You may `require 'backports/latest'` as a
50
49
  shortcut to the latest Ruby version supported.
@@ -118,6 +117,12 @@ itself, JRuby and Rubinius.
118
117
  - `except`
119
118
  - `transform_keys`, `transform_keys!` (with hash argument)
120
119
 
120
+ #### Ractor
121
+ - All methods, with the caveats:
122
+ - uses Ruby's `Thread` internally
123
+ - will not raise some errors when `Ractor` would (in particular `Ractor::IsolationError`)
124
+ - supported in Ruby 2.0+ only
125
+
121
126
  #### Symbol
122
127
  - `name`
123
128
 
@@ -0,0 +1,5 @@
1
+ if RUBY_VERSION < '2'
2
+ warn 'Ractor not backported to Ruby 1.x'
3
+ else
4
+ require_relative '../ractor/ractor' unless defined?(Ractor.current)
5
+ end
@@ -0,0 +1,91 @@
1
+ require_relative '../2.4.0/hash/transform_values'
2
+ require_relative '../2.5.0/hash/transform_keys'
3
+
4
+ class Ractor
5
+ module Cloner
6
+ extend self
7
+
8
+ def deep_clone(obj)
9
+ return obj if Ractor.ractor_shareable_self?(obj, false) { false }
10
+
11
+ @processed = {}.compare_by_identity
12
+ @changed = nil
13
+ result = process(obj) do |r|
14
+ copy_contents(r)
15
+ end
16
+ return result if result
17
+
18
+ Ractor.ractor_mark_set_shareable(@processed)
19
+ obj
20
+ end
21
+
22
+ # Yields a deep copy.
23
+ # If no deep copy is needed, `obj` is returned and
24
+ # nothing is yielded
25
+ private def clone_deeper(obj)
26
+ return obj if Ractor.ractor_shareable_self?(obj, false) { false }
27
+
28
+ result = process(obj) do |r|
29
+ copy_contents(r)
30
+ end
31
+ return obj unless result
32
+
33
+ yield result if block_given?
34
+ result
35
+ end
36
+
37
+ # Yields if `obj` is a new structure
38
+ # Returns the deep copy, or `false` if no deep copy is needed
39
+ private def process(obj)
40
+ @processed.fetch(obj) do
41
+ # For recursive structures, assume that we'll need a duplicate.
42
+ # If that's not the case, we will have duplicated the whole structure
43
+ # for nothing...
44
+ @processed[obj] = result = obj.dup
45
+ changed = track_change { yield result }
46
+ return false if obj.frozen? && !changed
47
+
48
+ @changed = true
49
+ result.freeze if obj.frozen?
50
+
51
+ result
52
+ end
53
+ end
54
+
55
+ # returns if the block called `deep clone` and that the deep copy was needed
56
+ private def track_change
57
+ prev = @changed
58
+ @changed = false
59
+ yield
60
+ @changed
61
+ ensure
62
+ @changed = prev
63
+ end
64
+
65
+ # modifies in place `obj` by calling `deep clone` on its contents
66
+ private def copy_contents(obj)
67
+ case obj
68
+ when ::Hash
69
+ if obj.default
70
+ clone_deeper(obj.default) do |copy|
71
+ obj.default = copy
72
+ end
73
+ end
74
+ obj.transform_keys! { |key| clone_deeper(key) }
75
+ obj.transform_values! { |value| clone_deeper(value) }
76
+ when ::Array
77
+ obj.map! { |item| clone_deeper(item) }
78
+ when ::Struct
79
+ obj.each_pair do |key, item|
80
+ clone_deeper(item) { |copy| obj[key] = copy }
81
+ end
82
+ end
83
+ obj.instance_variables.each do |var|
84
+ clone_deeper(obj.instance_variable_get(var)) do |copy|
85
+ obj.instance_variable_set(var, copy)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ private_constant :Cloner
91
+ end
@@ -0,0 +1,16 @@
1
+ class Ractor
2
+ class ClosedError < ::StopIteration
3
+ end
4
+
5
+ class Error < ::StandardError
6
+ end
7
+
8
+ class RemoteError < Error
9
+ attr_reader :ractor
10
+
11
+ def initialize(message = nil)
12
+ @ractor = Ractor.current
13
+ super
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,62 @@
1
+ require_relative '../tools/filtered_queue'
2
+
3
+ class Ractor
4
+ # Standard ::Queue but raises if popping and closed
5
+ class BaseQueue < ::Backports::FilteredQueue
6
+ ClosedQueueError = ::Ractor::ClosedError
7
+
8
+ # yields message (if any)
9
+ def pop_non_blocking
10
+ yield pop(timeout: 0)
11
+ rescue TimeoutError
12
+ nil
13
+ end
14
+ end
15
+
16
+ class IncomingQueue < BaseQueue
17
+ TYPE = :incoming
18
+
19
+ protected def reenter
20
+ raise ::Ractor::Error, 'Can not reenter'
21
+ end
22
+ end
23
+
24
+ # * Wraps exception
25
+ # * Add `ack: ` to push (blocking)
26
+ class OutgoingQueue < BaseQueue
27
+ TYPE = :outgoing
28
+
29
+ WrappedException = ::Struct.new(:exception, :ractor)
30
+
31
+ def initialize
32
+ @ack_queue = ::Queue.new
33
+ super
34
+ end
35
+
36
+ def pop(timeout: nil, ack: true)
37
+ r = super(timeout: timeout)
38
+ @ack_queue << :done if ack
39
+ raise r.exception if WrappedException === r
40
+
41
+ r
42
+ end
43
+
44
+ def close(how = :hard)
45
+ super()
46
+ return if how == :soft
47
+
48
+ clear
49
+ @ack_queue.close
50
+ end
51
+
52
+ def push(obj, ack:)
53
+ super(obj)
54
+ if ack
55
+ r = @ack_queue.pop # block until popped
56
+ raise ClosedError, "The #{self.class::TYPE}-port is already closed" unless r == :done
57
+ end
58
+ self
59
+ end
60
+ end
61
+ private_constant :BaseQueue, :OutgoingQueue, :IncomingQueue
62
+ end
@@ -0,0 +1,238 @@
1
+ # Ruby 2.0+ backport of `Ractor` class
2
+ # Extra private methods and instance variables all start with `ractor_`
3
+ class Ractor
4
+ require_relative '../tools/arguments'
5
+
6
+ require_relative 'cloner'
7
+ require_relative 'errors'
8
+ require_relative 'queues'
9
+ require_relative 'sharing'
10
+
11
+ # Implementation notes
12
+ #
13
+ # Uses one `Thread` for each `Ractor`, as well as queues for communication
14
+ #
15
+ # The incoming queue is strict: contrary to standard queue, you can't pop from an empty closed queue.
16
+ # Since standard queues return `nil` is those conditions, we wrap/unwrap `nil` values and consider
17
+ # all `nil` values to be results of closed queues. `ClosedQueueError` are re-raised as `Ractor::ClosedError`
18
+ #
19
+ # The outgoing queue is strict and blocking. Same wrapping / raising as incoming,
20
+ # with an extra queue to acknowledge when a value has been read (or if the port is closed while waiting).
21
+ #
22
+ # The last result is a bit tricky as it needs to be pushed on the outgoing queue but can not be blocking.
23
+ # For this, we "soft close" the outgoing port.
24
+
25
+ def initialize(*args, &block)
26
+ @ractor_incoming_queue = IncomingQueue.new
27
+ @ractor_outgoing_queue = OutgoingQueue.new
28
+ raise ArgumentError, 'must be called with a block' unless block
29
+
30
+ kw = args.last
31
+ if kw.is_a?(Hash) && kw.size == 1 && kw.key?(:name)
32
+ args.pop
33
+ name = kw[:name]
34
+ end
35
+ @ractor_name = name && Backports.coerce_to_str(name)
36
+
37
+ if Ractor.main == nil # then initializing main Ractor
38
+ @ractor_thread = ::Thread.current
39
+ @ractor_origin = nil
40
+ @ractor_thread.thread_variable_set(:ractor, self)
41
+ else
42
+ @ractor_origin = caller(1, 1).first.split(':in `').first
43
+
44
+ args.map! { |a| Ractor.ractor_isolate(a, false) }
45
+ ractor_thread_start(args, block)
46
+ end
47
+ end
48
+
49
+ private def ractor_thread_start(args, block)
50
+ Thread.new do
51
+ @ractor_thread = Thread.current
52
+ @ractor_thread_group = ThreadGroup.new.add(@ractor_thread)
53
+ ::Thread.current.thread_variable_set(:ractor, self)
54
+ result = nil
55
+ begin
56
+ result = instance_exec(*args, &block)
57
+ rescue ::Exception => err # rubocop:disable Lint/RescueException
58
+ begin
59
+ raise RemoteError, "thrown by remote Ractor: #{err.message}"
60
+ rescue RemoteError => e # Hack to create exception with `cause`
61
+ result = OutgoingQueue::WrappedException.new(e)
62
+ end
63
+ ensure
64
+ ractor_thread_terminate(result)
65
+ end
66
+ end
67
+ end
68
+
69
+ private def ractor_thread_terminate(result)
70
+ begin
71
+ ractor_outgoing_queue.push(result, ack: false) unless ractor_outgoing_queue.closed?
72
+ rescue ClosedQueueError
73
+ return # ignore
74
+ end
75
+ ractor_incoming_queue.close
76
+ ractor_outgoing_queue.close(:soft)
77
+ ensure
78
+ # TODO: synchronize?
79
+ @ractor_thread_group.list.each do |thread|
80
+ thread.kill unless thread == Thread.current
81
+ end
82
+ end
83
+
84
+ def send(obj, move: false)
85
+ ractor_incoming_queue << Ractor.ractor_isolate(obj, move)
86
+ self
87
+ rescue ::ClosedQueueError
88
+ raise ClosedError, 'The incoming-port is already closed'
89
+ end
90
+ alias_method :<<, :send
91
+
92
+ def take
93
+ ractor_outgoing_queue.pop(ack: true)
94
+ end
95
+
96
+ def name
97
+ @ractor_name
98
+ end
99
+
100
+ RACTOR_STATE = {
101
+ 'sleep' => 'blocking',
102
+ 'run' => 'running',
103
+ 'aborting' => 'aborting',
104
+ false => 'terminated',
105
+ nil => 'terminated',
106
+ }.freeze
107
+ private_constant :RACTOR_STATE
108
+
109
+ def inspect
110
+ state = RACTOR_STATE[@ractor_thread ? @ractor_thread.status : 'run']
111
+ info = [
112
+ 'Ractor:#1',
113
+ name,
114
+ @ractor_origin,
115
+ state
116
+ ].compact.join(' ')
117
+
118
+ "#<#{info}>"
119
+ end
120
+
121
+ def close_incoming
122
+ r = ractor_incoming_queue.closed?
123
+ ractor_incoming_queue.close
124
+ r
125
+ end
126
+
127
+ def close_outgoing
128
+ r = ractor_outgoing_queue.closed?
129
+ ractor_outgoing_queue.close
130
+ r
131
+ end
132
+
133
+ private def receive
134
+ ractor_incoming_queue.pop
135
+ end
136
+
137
+ private def receive_if(&block)
138
+ raise ArgumentError, 'no block given' unless block
139
+ ractor_incoming_queue.pop(&block)
140
+ end
141
+
142
+ class << self
143
+ def yield(value, move: false)
144
+ value = ractor_isolate(value, move)
145
+ current.ractor_outgoing_queue.push(value, ack: true)
146
+ rescue ClosedQueueError
147
+ raise ClosedError, 'The outgoing-port is already closed'
148
+ end
149
+
150
+ def receive
151
+ current.__send__(:receive)
152
+ end
153
+ alias_method :recv, :receive
154
+
155
+ def receive_if(&block)
156
+ current.__send__(:receive_if, &block)
157
+ end
158
+
159
+ def select(*ractors, yield_value: not_given = true, move: false)
160
+ cur = Ractor.current
161
+ queues = ractors.map do |r|
162
+ r == cur ? r.ractor_incoming_queue : r.ractor_outgoing_queue
163
+ end
164
+ if !not_given
165
+ out = current.ractor_outgoing_queue
166
+ yield_value = ractor_isolate(yield_value, move)
167
+ elsif ractors.empty?
168
+ raise ArgumentError, 'specify at least one ractor or `yield_value`'
169
+ end
170
+
171
+ while true # rubocop:disable Style/InfiniteLoop
172
+ # Don't `loop`, in case of `ClosedError` (not that there should be any)
173
+ queues.each_with_index do |q, i|
174
+ q.pop_non_blocking do |val|
175
+ r = ractors[i]
176
+ return [r == cur ? :receive : r, val]
177
+ end
178
+ end
179
+
180
+ if out && out.num_waiting > 0
181
+ # Not quite atomic...
182
+ out.push(yield_value, ack: true)
183
+ return [:yield, nil]
184
+ end
185
+
186
+ sleep(0.001)
187
+ end
188
+ end
189
+
190
+ def make_shareable(obj)
191
+ return obj if ractor_check_shareability?(obj, true)
192
+
193
+ raise Ractor::Error, '#freeze does not freeze object correctly'
194
+ end
195
+
196
+ def shareable?(obj)
197
+ ractor_check_shareability?(obj, false)
198
+ end
199
+
200
+ def current
201
+ Thread.current.thread_variable_get(:ractor)
202
+ end
203
+
204
+ def count
205
+ ObjectSpace.each_object(Ractor).count(&:ractor_live?)
206
+ end
207
+
208
+ # @api private
209
+ def ractor_reset
210
+ ObjectSpace.each_object(Ractor).each do |r|
211
+ next if r == Ractor.current
212
+ next unless (th = r.ractor_thread)
213
+
214
+ th.kill
215
+ th.join
216
+ end
217
+ Ractor.current.ractor_incoming_queue.clear
218
+ end
219
+
220
+ attr_reader :main
221
+
222
+ private def ractor_init
223
+ @ractor_shareable = ::ObjectSpace::WeakMap.new
224
+ @main = Ractor.new { nil }
225
+ end
226
+ end
227
+
228
+ # @api private
229
+ def ractor_live?
230
+ !defined?(@ractor_thread) || # May happen if `count` is called from another thread before `initialize` has completed
231
+ @ractor_thread.status
232
+ end
233
+
234
+ # @api private
235
+ attr_reader :ractor_outgoing_queue, :ractor_incoming_queue, :ractor_thread
236
+
237
+ ractor_init
238
+ end
@@ -0,0 +1,93 @@
1
+ class Ractor
2
+ class << self
3
+ # @api private
4
+ def ractor_isolate(val, move = false)
5
+ return val if move
6
+
7
+ Cloner.deep_clone(val)
8
+ end
9
+
10
+ private def ractor_check_shareability?(obj, freeze_all)
11
+ ractor_shareable_self?(obj, freeze_all) do
12
+ visited = {}
13
+
14
+ return false unless ractor_shareable_parts?(obj, freeze_all, visited)
15
+
16
+ ractor_mark_set_shareable(visited)
17
+
18
+ true
19
+ end
20
+ end
21
+
22
+ # yield if shareability can't be determined without looking at its parts
23
+ def ractor_shareable_self?(obj, freeze_all)
24
+ return true if @ractor_shareable.key?(obj)
25
+ return true if ractor_shareable_by_nature?(obj, freeze_all)
26
+ if obj.frozen? || (freeze_all && obj.freeze)
27
+ yield
28
+ else
29
+ false
30
+ end
31
+ end
32
+
33
+ private def ractor_shareable_parts?(obj, freeze_all, visited)
34
+ return true if visited.key?(obj)
35
+ visited[obj] = true
36
+
37
+ ractor_traverse(obj) do |part|
38
+ return false unless ractor_shareable_self?(part, freeze_all) do
39
+ ractor_shareable_parts?(part, freeze_all, visited)
40
+ end
41
+ end
42
+
43
+ true
44
+ end
45
+
46
+ def ractor_mark_set_shareable(visited)
47
+ visited.each do |key|
48
+ @ractor_shareable[key] = Ractor
49
+ end
50
+ end
51
+
52
+ private def ractor_traverse(obj, &block)
53
+ case obj
54
+ when ::Hash
55
+ Hash obj.default
56
+ yield obj.default_proc
57
+ obj.each do |key, value|
58
+ yield key
59
+ yield value
60
+ end
61
+ when ::Range
62
+ yield obj.begin
63
+ yield obj.end
64
+ when ::Array, ::Struct
65
+ obj.each(&block)
66
+ when ::Complex
67
+ yield obj.real
68
+ yield obj.imaginary
69
+ when ::Rational
70
+ yield obj.numerator
71
+ yield obj.denominator
72
+ end
73
+ obj.instance_variables.each do |var|
74
+ yield obj.instance_variable_get(var)
75
+ end
76
+ end
77
+
78
+ private def ractor_shareable_by_nature?(obj, freeze_all)
79
+ case obj
80
+ when ::Module, ::Ractor
81
+ true
82
+ when ::Regexp, ::Range, ::Numeric
83
+ !freeze_all # Assume that these are literals that would have been frozen in 3.0
84
+ # unless we're making them shareable, in which case we might as well
85
+ # freeze them for real.
86
+ when ::Symbol, false, true, nil # Were only frozen in Ruby 2.3+
87
+ true
88
+ else
89
+ false
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,202 @@
1
+ module Backports
2
+ class FilteredQueue
3
+ require 'backports/2.3.0/queue/close'
4
+ CONSUME_ON_ESCAPE = true
5
+
6
+ class ClosedQueueError < ::ClosedQueueError
7
+ end
8
+ class TimeoutError < ::ThreadError
9
+ end
10
+
11
+ class Message
12
+ # Not using Struct as we want comparision by identity
13
+ attr_reader :value
14
+ attr_accessor :reserved
15
+
16
+ def initialize(value)
17
+ @value = value
18
+ @reserved = false
19
+ end
20
+ end
21
+ private_constant :Message
22
+
23
+ # Like ::Queue, but with
24
+ # - filtering
25
+ # - timeout
26
+ # - raises on closed queues
27
+
28
+ attr_reader :num_waiting
29
+
30
+ # Timeout processing based on https://spin.atomicobject.com/2017/06/28/queue-pop-with-timeout-fixed/
31
+ def initialize
32
+ @mutex = ::Mutex.new
33
+ @queue = []
34
+ @closed = false
35
+ @received = ::ConditionVariable.new
36
+ @num_waiting = 0
37
+ end
38
+
39
+ def close
40
+ @mutex.synchronize do
41
+ @closed = true
42
+ @received.broadcast
43
+ end
44
+ self
45
+ end
46
+
47
+ def closed?
48
+ @closed
49
+ end
50
+
51
+ def <<(x)
52
+ @mutex.synchronize do
53
+ ensure_open
54
+ @queue << Message.new(x)
55
+ @received.signal
56
+ end
57
+ self
58
+ end
59
+ alias_method :push, :<<
60
+
61
+ def clear
62
+ @mutex.synchronize do
63
+ @queue.clear
64
+ end
65
+ self
66
+ end
67
+
68
+ def pop(timeout: nil, &block)
69
+ msg = nil
70
+ exclude = [] if block # exclusion list of messages rejected by this call
71
+ timeout_time = timeout + Time.now.to_f if timeout
72
+ while true do
73
+ @mutex.synchronize do
74
+ reenter if reentrant?
75
+ msg = acquire!(timeout_time, exclude)
76
+ return consume!(msg).value unless block
77
+ end
78
+ return msg.value if filter?(msg, &block)
79
+ end
80
+ end
81
+
82
+ def empty?
83
+ avail = @mutex.synchronize do
84
+ available!
85
+ end
86
+
87
+ !avail
88
+ end
89
+
90
+ protected def timeout_value
91
+ raise self.class::TimeoutError, "timeout elapsed"
92
+ end
93
+
94
+ protected def closed_queue_value
95
+ ensure_open
96
+ end
97
+
98
+ # @return if outer message should be consumed or not
99
+ protected def reenter
100
+ true
101
+ end
102
+
103
+ ### private section
104
+ #
105
+ # bang methods require synchronization
106
+
107
+ # @returns:
108
+ # * true if message consumed (block result truthy or due to reentrant call)
109
+ # * false if rejected
110
+ private def filter?(msg)
111
+ consume = self.class::CONSUME_ON_ESCAPE
112
+ begin
113
+ reentered = consume_on_reentry(msg) do
114
+ consume = !!(yield msg.value)
115
+ end
116
+ reentered ? reenter : consume
117
+ ensure
118
+ commit(msg, consume) unless reentered
119
+ end
120
+ end
121
+
122
+ # @returns msg
123
+ private def consume!(msg)
124
+ @queue.delete(msg)
125
+ end
126
+
127
+ private def reject!(msg)
128
+ msg.reserved = false
129
+ @received.broadcast
130
+ end
131
+
132
+ private def commit(msg, consume)
133
+ @mutex.synchronize do
134
+ if consume
135
+ consume!(msg)
136
+ else
137
+ reject!(msg)
138
+ end
139
+ end
140
+ end
141
+
142
+ private def consume_on_reentry(msg)
143
+ q_map = current_filtered_queues
144
+ if (outer_msg = q_map[self])
145
+ commit(outer_msg, reenter)
146
+ end
147
+ q_map[self] = msg
148
+ begin
149
+ yield
150
+ ensure
151
+ reentered = !q_map.delete(self)
152
+ end
153
+ reentered
154
+ end
155
+
156
+ private def reentrant?
157
+ !!current_filtered_queues[self]
158
+ end
159
+
160
+ # @returns Hash { FilteredQueue => Message }
161
+ private def current_filtered_queues
162
+ t = Thread.current
163
+ t.thread_variable_get(:backports_currently_filtered_queues) or
164
+ t.thread_variable_set(:backports_currently_filtered_queues, {}.compare_by_identity)
165
+ end
166
+
167
+ # private methods assume @mutex synchonized
168
+ # adds to exclude list
169
+ private def acquire!(timeout_time, exclude = nil)
170
+ while true do
171
+ if (msg = available!(exclude))
172
+ msg.reserved = true
173
+ exclude << msg if exclude
174
+ return msg
175
+ end
176
+ return closed_queue_value if @closed
177
+ # wait for element or timeout
178
+ if timeout_time
179
+ remaining_time = timeout_time - ::Time.now.to_f
180
+ return timeout_value if remaining_time <= 0
181
+ end
182
+ begin
183
+ @num_waiting += 1
184
+ @received.wait(@mutex, remaining_time)
185
+ ensure
186
+ @num_waiting -= 1
187
+ end
188
+ end
189
+ end
190
+
191
+ private def available!(exclude = nil)
192
+ @queue.find do |msg|
193
+ next if exclude && exclude.include?(msg)
194
+ !msg.reserved
195
+ end
196
+ end
197
+
198
+ private def ensure_open
199
+ raise self.class::ClosedQueueError, 'queue closed' if @closed
200
+ end
201
+ end
202
+ end
@@ -1,3 +1,3 @@
1
1
  module Backports
2
- VERSION = "3.19.0" unless Backports.constants.include? :VERSION # the guard is against a redefinition warning that happens on Travis
2
+ VERSION = "3.20.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.19.0
4
+ version: 3.20.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-12-28 00:00:00.000000000 Z
11
+ date: 2020-12-30 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.
@@ -538,6 +538,7 @@ files:
538
538
  - lib/backports/3.0.0/hash.rb
539
539
  - lib/backports/3.0.0/hash/except.rb
540
540
  - lib/backports/3.0.0/hash/transform_keys.rb
541
+ - lib/backports/3.0.0/ractor.rb
541
542
  - lib/backports/3.0.0/symbol.rb
542
543
  - lib/backports/3.0.0/symbol/name.rb
543
544
  - lib/backports/3.0.rb
@@ -548,6 +549,11 @@ files:
548
549
  - lib/backports/force/string_length.rb
549
550
  - lib/backports/force/string_size.rb
550
551
  - lib/backports/latest.rb
552
+ - lib/backports/ractor/cloner.rb
553
+ - lib/backports/ractor/errors.rb
554
+ - lib/backports/ractor/queues.rb
555
+ - lib/backports/ractor/ractor.rb
556
+ - lib/backports/ractor/sharing.rb
551
557
  - lib/backports/rails.rb
552
558
  - lib/backports/rails/array.rb
553
559
  - lib/backports/rails/enumerable.rb
@@ -566,6 +572,7 @@ files:
566
572
  - lib/backports/tools/arguments.rb
567
573
  - lib/backports/tools/deprecation.rb
568
574
  - lib/backports/tools/extreme_object.rb
575
+ - lib/backports/tools/filtered_queue.rb
569
576
  - lib/backports/tools/float_integer_conversion.rb
570
577
  - lib/backports/tools/io.rb
571
578
  - lib/backports/tools/make_block_optional.rb