backports 3.20.1 → 3.20.2
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 +4 -4
- data/Gemfile +1 -1
- data/README.md +4 -4
- data/lib/backports/2.5.0/integer/sqrt.rb +1 -1
- data/lib/backports/3.0.0/ractor.rb +15 -1
- data/lib/backports/ractor/cloner.rb +73 -70
- data/lib/backports/ractor/errors.rb +14 -10
- data/lib/backports/{tools → ractor}/filtered_queue.rb +11 -8
- data/lib/backports/ractor/queues.rb +50 -46
- data/lib/backports/ractor/ractor.rb +224 -213
- data/lib/backports/ractor/sharing.rb +75 -71
- data/lib/backports/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e5c6feae3c15c70a78dda33abcb10c737393eb6898d1756c8f3da4e5f604b25
|
4
|
+
data.tar.gz: 3339a5a0ae48803b9853627c096790f555a3a8f852af7b66c4331f683fcaac55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d843af1e7bd02fe062f600df7b130c37b2ce41bf178831049bbcd57fadbb9cac1c73d9bf4feebca71dcb39998492442083822d60c1691e1e67f195e21005ea97
|
7
|
+
data.tar.gz: f37e52397480aeb259cf469093980cdef01500e845b94fbe90d67864f37323d2dcdbd59fa308ada90cc7c1820a02b20c07c4a647e986840f0c9222c1b4313a35
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
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) [](https://tidelift.com/subscription/pkg/rubygems-backports?utm_source=rubygems-backports&utm_medium=referral&utm_campaign=readme)
|
2
2
|
|
3
|
-
Yearning to
|
3
|
+
Yearning to write a gem using some new cool features in Ruby 3.0 while
|
4
4
|
still supporting Ruby 2.5.x?
|
5
5
|
Have some legacy code in Ruby 1.8 but can't live without `flat_map`?
|
6
6
|
|
@@ -135,7 +135,7 @@ itself, JRuby and Rubinius.
|
|
135
135
|
- `clamp` (with range)
|
136
136
|
|
137
137
|
#### Complex
|
138
|
-
-
|
138
|
+
- `<=>`
|
139
139
|
|
140
140
|
#### Enumerable
|
141
141
|
- `filter_map`
|
@@ -167,10 +167,10 @@ itself, JRuby and Rubinius.
|
|
167
167
|
- `then`
|
168
168
|
|
169
169
|
#### Method
|
170
|
-
-
|
170
|
+
- `<<`, `>>`
|
171
171
|
|
172
172
|
#### Proc
|
173
|
-
-
|
173
|
+
- `<<`, `>>`
|
174
174
|
|
175
175
|
#### Range
|
176
176
|
- `cover?` (with `Range` argument)
|
@@ -9,7 +9,7 @@ class Integer
|
|
9
9
|
bits_shift = n.bit_length / 2 + 1
|
10
10
|
bitn_mask = root = 1 << bits_shift
|
11
11
|
loop do
|
12
|
-
root ^= bitn_mask if (root * root) > n
|
12
|
+
root ^= bitn_mask if (root * root) > n
|
13
13
|
bitn_mask >>= 1
|
14
14
|
return root if bitn_mask == 0
|
15
15
|
root |= bitn_mask
|
@@ -1,5 +1,19 @@
|
|
1
1
|
if RUBY_VERSION < '2'
|
2
2
|
warn 'Ractor not backported to Ruby 1.x'
|
3
|
+
elsif defined?(Ractor.current)
|
4
|
+
# all good
|
3
5
|
else
|
4
|
-
|
6
|
+
# Cloner:
|
7
|
+
require_relative '../2.4.0/hash/transform_values'
|
8
|
+
require_relative '../2.5.0/hash/transform_keys'
|
9
|
+
# Queues & FilteredQueue
|
10
|
+
require_relative '../2.3.0/queue/close'
|
11
|
+
|
12
|
+
class Ractor
|
13
|
+
end
|
14
|
+
|
15
|
+
module Backports
|
16
|
+
Ractor = ::Ractor
|
17
|
+
end
|
18
|
+
require_relative '../ractor/ractor'
|
5
19
|
end
|
@@ -1,91 +1,94 @@
|
|
1
|
-
|
2
|
-
require_relative '../2.5.0/hash/transform_keys'
|
1
|
+
# shareable_constant_value: literal
|
3
2
|
|
4
|
-
|
5
|
-
module Cloner
|
6
|
-
extend self
|
3
|
+
using ::RubyNext if defined?(::RubyNext)
|
7
4
|
|
8
|
-
|
9
|
-
|
5
|
+
module Backports
|
6
|
+
class Ractor
|
7
|
+
module Cloner
|
8
|
+
extend self
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
def deep_clone(obj)
|
11
|
+
return obj if Ractor.ractor_shareable_self?(obj, false) { false }
|
12
|
+
|
13
|
+
@processed = {}.compare_by_identity
|
14
|
+
@changed = nil
|
15
|
+
result = process(obj) do |r|
|
16
|
+
copy_contents(r)
|
17
|
+
end
|
18
|
+
return result if result
|
19
|
+
|
20
|
+
Ractor.ractor_mark_set_shareable(@processed)
|
21
|
+
obj
|
15
22
|
end
|
16
|
-
return result if result
|
17
23
|
|
18
|
-
|
19
|
-
obj
|
20
|
-
|
24
|
+
# Yields a deep copy.
|
25
|
+
# If no deep copy is needed, `obj` is returned and
|
26
|
+
# nothing is yielded
|
27
|
+
private def clone_deeper(obj)
|
28
|
+
return obj if Ractor.ractor_shareable_self?(obj, false) { false }
|
21
29
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
return obj if Ractor.ractor_shareable_self?(obj, false) { false }
|
30
|
+
result = process(obj) do |r|
|
31
|
+
copy_contents(r)
|
32
|
+
end
|
33
|
+
return obj unless result
|
27
34
|
|
28
|
-
|
29
|
-
|
35
|
+
yield result if block_given?
|
36
|
+
result
|
30
37
|
end
|
31
|
-
return obj unless result
|
32
|
-
|
33
|
-
yield result if block_given?
|
34
|
-
result
|
35
|
-
end
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
39
|
+
# Yields if `obj` is a new structure
|
40
|
+
# Returns the deep copy, or `false` if no deep copy is needed
|
41
|
+
private def process(obj)
|
42
|
+
@processed.fetch(obj) do
|
43
|
+
# For recursive structures, assume that we'll need a duplicate.
|
44
|
+
# If that's not the case, we will have duplicated the whole structure
|
45
|
+
# for nothing...
|
46
|
+
@processed[obj] = result = obj.dup
|
47
|
+
changed = track_change { yield result }
|
48
|
+
return false if obj.frozen? && !changed
|
47
49
|
|
48
|
-
|
49
|
-
|
50
|
+
@changed = true
|
51
|
+
result.freeze if obj.frozen?
|
50
52
|
|
51
|
-
|
53
|
+
result
|
54
|
+
end
|
52
55
|
end
|
53
|
-
end
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
57
|
+
# returns if the block called `deep clone` and that the deep copy was needed
|
58
|
+
private def track_change
|
59
|
+
prev = @changed
|
60
|
+
@changed = false
|
61
|
+
yield
|
62
|
+
@changed
|
63
|
+
ensure
|
64
|
+
@changed = prev
|
65
|
+
end
|
64
66
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
67
|
+
# modifies in place `obj` by calling `deep clone` on its contents
|
68
|
+
private def copy_contents(obj)
|
69
|
+
case obj
|
70
|
+
when ::Hash
|
71
|
+
if obj.default
|
72
|
+
clone_deeper(obj.default) do |copy|
|
73
|
+
obj.default = copy
|
74
|
+
end
|
75
|
+
end
|
76
|
+
obj.transform_keys! { |key| clone_deeper(key) }
|
77
|
+
obj.transform_values! { |value| clone_deeper(value) }
|
78
|
+
when ::Array
|
79
|
+
obj.map! { |item| clone_deeper(item) }
|
80
|
+
when ::Struct
|
81
|
+
obj.each_pair do |key, item|
|
82
|
+
clone_deeper(item) { |copy| obj[key] = copy }
|
72
83
|
end
|
73
84
|
end
|
74
|
-
obj.
|
75
|
-
|
76
|
-
|
77
|
-
|
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)
|
85
|
+
obj.instance_variables.each do |var|
|
86
|
+
clone_deeper(obj.instance_variable_get(var)) do |copy|
|
87
|
+
obj.instance_variable_set(var, copy)
|
88
|
+
end
|
86
89
|
end
|
87
90
|
end
|
88
91
|
end
|
92
|
+
private_constant :Cloner
|
89
93
|
end
|
90
|
-
private_constant :Cloner
|
91
94
|
end
|
@@ -1,16 +1,20 @@
|
|
1
|
-
|
2
|
-
class ClosedError < ::StopIteration
|
3
|
-
end
|
1
|
+
# shareable_constant_value: literal
|
4
2
|
|
5
|
-
|
6
|
-
|
3
|
+
module Backports
|
4
|
+
class Ractor
|
5
|
+
class ClosedError < ::StopIteration
|
6
|
+
end
|
7
|
+
|
8
|
+
class Error < ::StandardError
|
9
|
+
end
|
7
10
|
|
8
|
-
|
9
|
-
|
11
|
+
class RemoteError < Error
|
12
|
+
attr_reader :ractor
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
+
def initialize(message = nil)
|
15
|
+
@ractor = Ractor.current
|
16
|
+
super
|
17
|
+
end
|
14
18
|
end
|
15
19
|
end
|
16
20
|
end
|
@@ -1,10 +1,18 @@
|
|
1
|
+
# shareable_constant_value: literal
|
2
|
+
|
1
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.
|
2
10
|
class FilteredQueue
|
3
|
-
require 'backports/2.3.0/queue/close'
|
4
11
|
CONSUME_ON_ESCAPE = true
|
5
12
|
|
6
13
|
class ClosedQueueError < ::ClosedQueueError
|
7
14
|
end
|
15
|
+
|
8
16
|
class TimeoutError < ::ThreadError
|
9
17
|
end
|
10
18
|
|
@@ -20,11 +28,6 @@ module Backports
|
|
20
28
|
end
|
21
29
|
private_constant :Message
|
22
30
|
|
23
|
-
# Like ::Queue, but with
|
24
|
-
# - filtering
|
25
|
-
# - timeout
|
26
|
-
# - raises on closed queues
|
27
|
-
|
28
31
|
attr_reader :num_waiting
|
29
32
|
|
30
33
|
# Timeout processing based on https://spin.atomicobject.com/2017/06/28/queue-pop-with-timeout-fixed/
|
@@ -69,7 +72,7 @@ module Backports
|
|
69
72
|
msg = nil
|
70
73
|
exclude = [] if block # exclusion list of messages rejected by this call
|
71
74
|
timeout_time = timeout + Time.now.to_f if timeout
|
72
|
-
while true do
|
75
|
+
while true do # rubocop:disable Style/InfiniteLoop, Style/WhileUntilDo
|
73
76
|
@mutex.synchronize do
|
74
77
|
reenter if reentrant?
|
75
78
|
msg = acquire!(timeout_time, exclude)
|
@@ -167,7 +170,7 @@ module Backports
|
|
167
170
|
# private methods assume @mutex synchonized
|
168
171
|
# adds to exclude list
|
169
172
|
private def acquire!(timeout_time, exclude = nil)
|
170
|
-
while true do
|
173
|
+
while true do # rubocop:disable Style/InfiniteLoop, Style/WhileUntilDo
|
171
174
|
if (msg = available!(exclude))
|
172
175
|
msg.reserved = true
|
173
176
|
exclude << msg if exclude
|
@@ -1,62 +1,66 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
13
17
|
end
|
14
|
-
end
|
15
18
|
|
16
|
-
|
17
|
-
|
19
|
+
class IncomingQueue < BaseQueue
|
20
|
+
TYPE = :incoming
|
18
21
|
|
19
|
-
|
20
|
-
|
22
|
+
protected def reenter
|
23
|
+
raise Ractor::Error, 'Can not reenter'
|
24
|
+
end
|
21
25
|
end
|
22
|
-
end
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
27
|
+
# * Wraps exception
|
28
|
+
# * Add `ack: ` to push (blocking)
|
29
|
+
class OutgoingQueue < BaseQueue
|
30
|
+
TYPE = :outgoing
|
28
31
|
|
29
|
-
|
32
|
+
WrappedException = ::Struct.new(:exception, :ractor)
|
30
33
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
def initialize
|
35
|
+
@ack_queue = ::Queue.new
|
36
|
+
super
|
37
|
+
end
|
35
38
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
40
43
|
|
41
|
-
|
42
|
-
|
44
|
+
r
|
45
|
+
end
|
43
46
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
+
def close(how = :hard)
|
48
|
+
super()
|
49
|
+
return if how == :soft
|
47
50
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
+
clear
|
52
|
+
@ack_queue.close
|
53
|
+
end
|
51
54
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
57
62
|
end
|
58
|
-
self
|
59
63
|
end
|
64
|
+
private_constant :BaseQueue, :OutgoingQueue, :IncomingQueue
|
60
65
|
end
|
61
|
-
private_constant :BaseQueue, :OutgoingQueue, :IncomingQueue
|
62
66
|
end
|
@@ -1,261 +1,272 @@
|
|
1
|
+
# shareable_constant_value: literal
|
2
|
+
|
1
3
|
# Ruby 2.0+ backport of `Ractor` class
|
2
4
|
# Extra private methods and instance variables all start with `ractor_`
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
48
53
|
end
|
49
|
-
end
|
50
54
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
begin
|
60
|
-
result = instance_exec(*args, &block)
|
61
|
-
rescue ::Exception => err # rubocop:disable Lint/RescueException
|
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
|
62
63
|
begin
|
63
|
-
|
64
|
-
rescue
|
65
|
-
|
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)
|
66
73
|
end
|
67
|
-
ensure
|
68
|
-
ractor_thread_terminate(result)
|
69
74
|
end
|
70
75
|
end
|
71
|
-
end
|
72
76
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
85
90
|
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def send(obj, move: false)
|
89
|
-
ractor_incoming_queue << Ractor.ractor_isolate(obj, move)
|
90
|
-
self
|
91
|
-
rescue ::ClosedQueueError
|
92
|
-
raise ClosedError, 'The incoming-port is already closed'
|
93
|
-
end
|
94
|
-
alias_method :<<, :send
|
95
|
-
|
96
|
-
def take
|
97
|
-
ractor_outgoing_queue.pop(ack: true)
|
98
|
-
end
|
99
|
-
|
100
|
-
def name
|
101
|
-
@ractor_name
|
102
|
-
end
|
103
91
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
private_constant :RACTOR_STATE
|
112
|
-
|
113
|
-
def inspect
|
114
|
-
state = RACTOR_STATE[@ractor_thread ? @ractor_thread.status : 'run']
|
115
|
-
info = [
|
116
|
-
'Ractor:#1',
|
117
|
-
name,
|
118
|
-
@ractor_origin,
|
119
|
-
state
|
120
|
-
].compact.join(' ')
|
121
|
-
|
122
|
-
"#<#{info}>"
|
123
|
-
end
|
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
|
124
99
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
r
|
129
|
-
end
|
100
|
+
def take
|
101
|
+
ractor_outgoing_queue.pop(ack: true)
|
102
|
+
end
|
130
103
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
r
|
135
|
-
end
|
104
|
+
def name
|
105
|
+
@ractor_name
|
106
|
+
end
|
136
107
|
|
137
|
-
|
138
|
-
|
139
|
-
|
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
|
140
128
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
129
|
+
def close_incoming
|
130
|
+
r = ractor_incoming_queue.closed?
|
131
|
+
ractor_incoming_queue.close
|
132
|
+
r
|
133
|
+
end
|
145
134
|
|
146
|
-
|
147
|
-
|
148
|
-
|
135
|
+
def close_outgoing
|
136
|
+
r = ractor_outgoing_queue.closed?
|
137
|
+
ractor_outgoing_queue.close
|
138
|
+
r
|
139
|
+
end
|
149
140
|
|
150
|
-
|
151
|
-
|
152
|
-
|
141
|
+
private def receive
|
142
|
+
ractor_incoming_queue.pop
|
143
|
+
end
|
153
144
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
145
|
+
private def receive_if(&block)
|
146
|
+
raise ::ArgumentError, 'no block given' unless block
|
147
|
+
ractor_incoming_queue.pop(&block)
|
148
|
+
end
|
158
149
|
|
159
|
-
|
160
|
-
|
161
|
-
value = ractor_isolate(value, move)
|
162
|
-
current.ractor_outgoing_queue.push(value, ack: true)
|
163
|
-
rescue ClosedQueueError
|
164
|
-
raise ClosedError, 'The outgoing-port is already closed'
|
150
|
+
def [](key)
|
151
|
+
Ractor.current.ractor_locals[key]
|
165
152
|
end
|
166
153
|
|
167
|
-
def
|
168
|
-
current.
|
154
|
+
def []=(key, value)
|
155
|
+
Ractor.current.ractor_locals[key] = value
|
169
156
|
end
|
170
|
-
alias_method :recv, :receive
|
171
157
|
|
172
|
-
|
173
|
-
|
158
|
+
# @api private
|
159
|
+
def ractor_locals
|
160
|
+
@ractor_locals ||= {}.compare_by_identity
|
174
161
|
end
|
175
162
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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'
|
180
169
|
end
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
elsif ractors.empty?
|
185
|
-
raise ArgumentError, 'specify at least one ractor or `yield_value`'
|
170
|
+
|
171
|
+
def receive
|
172
|
+
current.__send__(:receive)
|
186
173
|
end
|
174
|
+
alias_method :recv, :receive
|
187
175
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
q.pop_non_blocking do |val|
|
192
|
-
r = ractors[i]
|
193
|
-
return [r == cur ? :receive : r, val]
|
194
|
-
end
|
195
|
-
end
|
176
|
+
def receive_if(&block)
|
177
|
+
current.__send__(:receive_if, &block)
|
178
|
+
end
|
196
179
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
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`'
|
201
190
|
end
|
202
191
|
|
203
|
-
|
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
|
204
209
|
end
|
205
|
-
end
|
206
210
|
|
207
|
-
|
208
|
-
|
211
|
+
def make_shareable(obj)
|
212
|
+
return obj if ractor_check_shareability?(obj, true)
|
209
213
|
|
210
|
-
|
211
|
-
|
214
|
+
raise Ractor::Error, '#freeze does not freeze object correctly'
|
215
|
+
end
|
212
216
|
|
213
|
-
|
214
|
-
|
215
|
-
|
217
|
+
def shareable?(obj)
|
218
|
+
ractor_check_shareability?(obj, false)
|
219
|
+
end
|
216
220
|
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
221
225
|
|
222
|
-
|
223
|
-
|
224
|
-
|
226
|
+
def count
|
227
|
+
::ObjectSpace.each_object(Ractor).count(&:ractor_live?)
|
228
|
+
end
|
225
229
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
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)
|
231
235
|
|
232
|
-
|
233
|
-
|
236
|
+
th.kill
|
237
|
+
th.join
|
238
|
+
end
|
239
|
+
Ractor.current.ractor_incoming_queue.clear
|
234
240
|
end
|
235
|
-
Ractor.current.ractor_incoming_queue.clear
|
236
|
-
end
|
237
241
|
|
238
|
-
|
242
|
+
# @api private
|
243
|
+
def ractor_next_id
|
244
|
+
@id ||= 0
|
245
|
+
@id += 1
|
246
|
+
end
|
239
247
|
|
240
|
-
|
241
|
-
@ractor_shareable = ::ObjectSpace::WeakMap.new
|
242
|
-
@main = Ractor.new { nil }
|
243
|
-
RactorThreadGroups[::ThreadGroup::Default] = @main
|
244
|
-
end
|
248
|
+
attr_reader :main
|
245
249
|
|
246
|
-
|
247
|
-
|
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
|
248
259
|
end
|
249
|
-
end
|
250
260
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
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
|
256
266
|
|
257
|
-
|
258
|
-
|
267
|
+
# @api private
|
268
|
+
attr_reader :ractor_outgoing_queue, :ractor_incoming_queue, :ractor_thread
|
259
269
|
|
260
|
-
|
270
|
+
ractor_init
|
271
|
+
end
|
261
272
|
end
|
@@ -1,92 +1,96 @@
|
|
1
|
-
|
2
|
-
class << self
|
3
|
-
# @api private
|
4
|
-
def ractor_isolate(val, move = false)
|
5
|
-
return val if move
|
1
|
+
# shareable_constant_value: literal
|
6
2
|
|
7
|
-
|
8
|
-
|
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
9
|
|
10
|
-
|
11
|
-
|
12
|
-
visited = {}
|
10
|
+
Cloner.deep_clone(val)
|
11
|
+
end
|
13
12
|
|
14
|
-
|
13
|
+
private def ractor_check_shareability?(obj, freeze_all)
|
14
|
+
ractor_shareable_self?(obj, freeze_all) do
|
15
|
+
visited = {}
|
15
16
|
|
16
|
-
|
17
|
+
return false unless ractor_shareable_parts?(obj, freeze_all, visited)
|
17
18
|
|
18
|
-
|
19
|
-
end
|
20
|
-
end
|
19
|
+
ractor_mark_set_shareable(visited)
|
21
20
|
|
22
|
-
|
23
|
-
|
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
|
21
|
+
true
|
22
|
+
end
|
30
23
|
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
24
|
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
40
33
|
end
|
41
34
|
end
|
42
35
|
|
43
|
-
|
44
|
-
|
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
45
|
|
46
|
-
|
47
|
-
visited.each do |key|
|
48
|
-
@ractor_shareable[key] = Ractor
|
46
|
+
true
|
49
47
|
end
|
50
|
-
end
|
51
48
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
Hash obj.default
|
56
|
-
yield obj.default_proc
|
57
|
-
obj.each do |key, value|
|
58
|
-
yield key
|
59
|
-
yield value
|
49
|
+
def ractor_mark_set_shareable(visited)
|
50
|
+
visited.each do |key|
|
51
|
+
@ractor_shareable[key] = Ractor
|
60
52
|
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
53
|
end
|
73
|
-
|
74
|
-
|
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
|
75
79
|
end
|
76
|
-
end
|
77
80
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
90
94
|
end
|
91
95
|
end
|
92
96
|
end
|
data/lib/backports/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module Backports
|
2
|
-
VERSION = "3.20.
|
2
|
+
VERSION = "3.20.2" 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.20.
|
4
|
+
version: 3.20.2
|
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: 2021-01-
|
11
|
+
date: 2021-01-27 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.
|
@@ -551,6 +551,7 @@ files:
|
|
551
551
|
- lib/backports/latest.rb
|
552
552
|
- lib/backports/ractor/cloner.rb
|
553
553
|
- lib/backports/ractor/errors.rb
|
554
|
+
- lib/backports/ractor/filtered_queue.rb
|
554
555
|
- lib/backports/ractor/queues.rb
|
555
556
|
- lib/backports/ractor/ractor.rb
|
556
557
|
- lib/backports/ractor/sharing.rb
|
@@ -572,7 +573,6 @@ files:
|
|
572
573
|
- lib/backports/tools/arguments.rb
|
573
574
|
- lib/backports/tools/deprecation.rb
|
574
575
|
- lib/backports/tools/extreme_object.rb
|
575
|
-
- lib/backports/tools/filtered_queue.rb
|
576
576
|
- lib/backports/tools/float_integer_conversion.rb
|
577
577
|
- lib/backports/tools/io.rb
|
578
578
|
- lib/backports/tools/make_block_optional.rb
|