backports 3.19.0 → 3.20.0

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