backports 3.20.0 → 3.22.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 +4 -4
- data/CHANGELOG.md +13 -0
- data/Gemfile +1 -1
- data/README.md +285 -158
- data/lib/backports/2.1.0/module/singleton_class.rb +8 -0
- data/lib/backports/2.3.0/struct/dig.rb +2 -0
- data/lib/backports/2.4.0/string/unpack1.rb +7 -0
- data/lib/backports/2.5.0/dir/children.rb +4 -0
- data/lib/backports/2.5.0/dir/each_child.rb +7 -0
- data/lib/backports/2.5.0/integer/sqrt.rb +1 -1
- data/lib/backports/2.7.0/complex/{comparision.rb → comparison.rb} +0 -0
- data/lib/backports/2.7.0/enumerable/tally.rb +4 -3
- data/lib/backports/3.0.0/ractor.rb +15 -1
- data/lib/backports/3.1.0/array/intersect.rb +16 -0
- data/lib/backports/3.1.0/array.rb +3 -0
- data/lib/backports/3.1.0/class/descendants.rb +11 -0
- data/lib/backports/3.1.0/class/subclasses.rb +11 -0
- data/lib/backports/3.1.0/class.rb +3 -0
- data/lib/backports/3.1.0/enumerable/compact.rb +5 -0
- data/lib/backports/3.1.0/enumerable/tally.rb +18 -0
- data/lib/backports/3.1.0/enumerable.rb +3 -0
- data/lib/backports/3.1.0/file/dirname.rb +16 -0
- data/lib/backports/3.1.0/file.rb +3 -0
- data/lib/backports/3.1.0/integer/try_convert.rb +5 -0
- data/lib/backports/3.1.0/integer.rb +3 -0
- data/lib/backports/3.1.0/match_data/match.rb +5 -0
- data/lib/backports/3.1.0/match_data/match_length.rb +6 -0
- data/lib/backports/3.1.0/match_data.rb +3 -0
- data/lib/backports/3.1.0/struct/keyword_init.rb +5 -0
- data/lib/backports/3.1.0/struct.rb +3 -0
- data/lib/backports/3.1.0.rb +3 -0
- data/lib/backports/3.1.rb +1 -0
- data/lib/backports/latest.rb +1 -1
- data/lib/backports/ractor/cloner.rb +81 -69
- 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 +225 -191
- data/lib/backports/ractor/sharing.rb +75 -71
- data/lib/backports/version.rb +1 -1
- metadata +25 -4
@@ -4,4 +4,11 @@ class Dir
|
|
4
4
|
return to_enum(__method__, *args) unless block_given?
|
5
5
|
foreach(*args) { |f| yield f unless Backports::EXCLUDED_CHILDREN.include? f }
|
6
6
|
end
|
7
|
+
|
8
|
+
def each_child(&block)
|
9
|
+
return to_enum(__method__) unless block_given?
|
10
|
+
|
11
|
+
Dir.each_child(path, &block)
|
12
|
+
self
|
13
|
+
end
|
7
14
|
end unless Dir.respond_to? :each_child
|
@@ -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
|
File without changes
|
@@ -1,10 +1,11 @@
|
|
1
|
-
require 'backports/1.9.1/enumerable/each_with_object' unless Enumerable.method_defined? :each_with_object
|
2
|
-
|
3
1
|
unless Enumerable.method_defined? :tally
|
4
2
|
module Enumerable
|
5
3
|
def tally
|
4
|
+
h = {}
|
6
5
|
# NB: By spec, tally should return default-less hash
|
7
|
-
|
6
|
+
each_entry { |item| h[item] = h.fetch(item, 0) + 1 }
|
7
|
+
|
8
|
+
h
|
8
9
|
end
|
9
10
|
end
|
10
11
|
end
|
@@ -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
|
@@ -0,0 +1,16 @@
|
|
1
|
+
unless Array.method_defined? :intersect?
|
2
|
+
require 'backports/tools/arguments'
|
3
|
+
|
4
|
+
class Array
|
5
|
+
def intersect?(array)
|
6
|
+
array = Backports.coerce_to_ary(array)
|
7
|
+
|
8
|
+
if size < array.size
|
9
|
+
smaller = self
|
10
|
+
else
|
11
|
+
smaller, array = array, self
|
12
|
+
end
|
13
|
+
(array & smaller).size > 0
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
unless Class.method_defined? :subclasses
|
2
|
+
require 'backports/2.1.0/module/singleton_class'
|
3
|
+
|
4
|
+
class Class
|
5
|
+
def subclasses
|
6
|
+
ObjectSpace.each_object(singleton_class).reject do |klass|
|
7
|
+
klass.superclass != self || klass.singleton_class?
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
unless ([].tally({}) rescue false)
|
2
|
+
require 'backports/tools/arguments'
|
3
|
+
require 'backports/2.7.0/enumerable/tally'
|
4
|
+
require 'backports/tools/alias_method_chain'
|
5
|
+
|
6
|
+
module Enumerable
|
7
|
+
def tally_with_hash_argument(h = ::Backports::Undefined)
|
8
|
+
return tally_without_hash_argument if h.equal? ::Backports::Undefined
|
9
|
+
|
10
|
+
h = ::Backports.coerce_to_hash(h)
|
11
|
+
|
12
|
+
each_entry { |item| h[item] = h.fetch(item, 0) + 1 }
|
13
|
+
|
14
|
+
h
|
15
|
+
end
|
16
|
+
::Backports.alias_method_chain self, :tally, :hash_argument
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
unless (File.dirname("", 0) rescue false)
|
2
|
+
require 'backports/tools/alias_method_chain'
|
3
|
+
|
4
|
+
class File
|
5
|
+
def self.dirname_with_depth(path, depth = 1)
|
6
|
+
return dirname_without_depth(path) if depth == 1
|
7
|
+
|
8
|
+
raise ArgumentError, "negative depth #{depth}" if depth < 0
|
9
|
+
|
10
|
+
depth.times { path = dirname_without_depth(path) }
|
11
|
+
|
12
|
+
path
|
13
|
+
end
|
14
|
+
Backports.alias_method_chain singleton_class, :dirname, :depth
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'backports/3.1.0'
|
data/lib/backports/latest.rb
CHANGED
@@ -1,91 +1,103 @@
|
|
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
|
+
class Cloner
|
8
|
+
class << self
|
9
|
+
def deep_clone(obj)
|
10
|
+
return obj if Ractor.ractor_shareable_self?(obj, false) { false }
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
new.deep_clone(obj)
|
13
|
+
end
|
14
|
+
|
15
|
+
private :new
|
15
16
|
end
|
16
|
-
return result if result
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
def initialize
|
19
|
+
@processed = {}.compare_by_identity
|
20
|
+
@changed = nil
|
21
|
+
end
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
def deep_clone(obj)
|
24
|
+
result = process(obj) do |r|
|
25
|
+
copy_contents(r)
|
26
|
+
end
|
27
|
+
return result if result
|
27
28
|
|
28
|
-
|
29
|
-
|
29
|
+
Ractor.ractor_mark_set_shareable(@processed)
|
30
|
+
obj
|
30
31
|
end
|
31
|
-
return obj unless result
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
33
|
+
# Yields a deep copy.
|
34
|
+
# If no deep copy is needed, `obj` is returned and
|
35
|
+
# nothing is yielded
|
36
|
+
private def clone_deeper(obj)
|
37
|
+
return obj if Ractor.ractor_shareable_self?(obj, false) { false }
|
47
38
|
|
48
|
-
|
49
|
-
|
39
|
+
result = process(obj) do |r|
|
40
|
+
copy_contents(r)
|
41
|
+
end
|
42
|
+
return obj unless result
|
50
43
|
|
44
|
+
yield result if block_given?
|
51
45
|
result
|
52
46
|
end
|
53
|
-
end
|
54
47
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
48
|
+
# Yields if `obj` is a new structure
|
49
|
+
# Returns the deep copy, or `false` if no deep copy is needed
|
50
|
+
private def process(obj)
|
51
|
+
@processed.fetch(obj) do
|
52
|
+
# For recursive structures, assume that we'll need a duplicate.
|
53
|
+
# If that's not the case, we will have duplicated the whole structure
|
54
|
+
# for nothing...
|
55
|
+
@processed[obj] = result = obj.dup
|
56
|
+
changed = track_change { yield result }
|
57
|
+
return false if obj.frozen? && !changed
|
64
58
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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 }
|
59
|
+
@changed = true
|
60
|
+
result.freeze if obj.frozen?
|
61
|
+
|
62
|
+
result
|
81
63
|
end
|
82
64
|
end
|
83
|
-
|
84
|
-
|
85
|
-
|
65
|
+
|
66
|
+
# returns if the block called `deep clone` and that the deep copy was needed
|
67
|
+
private def track_change
|
68
|
+
prev = @changed
|
69
|
+
@changed = false
|
70
|
+
yield
|
71
|
+
@changed
|
72
|
+
ensure
|
73
|
+
@changed = prev
|
74
|
+
end
|
75
|
+
|
76
|
+
# modifies in place `obj` by calling `deep clone` on its contents
|
77
|
+
private def copy_contents(obj)
|
78
|
+
case obj
|
79
|
+
when ::Hash
|
80
|
+
if obj.default
|
81
|
+
clone_deeper(obj.default) do |copy|
|
82
|
+
obj.default = copy
|
83
|
+
end
|
84
|
+
end
|
85
|
+
obj.transform_keys! { |key| clone_deeper(key) }
|
86
|
+
obj.transform_values! { |value| clone_deeper(value) }
|
87
|
+
when ::Array
|
88
|
+
obj.map! { |item| clone_deeper(item) }
|
89
|
+
when ::Struct
|
90
|
+
obj.each_pair do |key, item|
|
91
|
+
clone_deeper(item) { |copy| obj[key] = copy }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
obj.instance_variables.each do |var|
|
95
|
+
clone_deeper(obj.instance_variable_get(var)) do |copy|
|
96
|
+
obj.instance_variable_set(var, copy)
|
97
|
+
end
|
86
98
|
end
|
87
99
|
end
|
88
100
|
end
|
101
|
+
private_constant :Cloner
|
89
102
|
end
|
90
|
-
private_constant :Cloner
|
91
103
|
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
|