backports 3.20.1 → 3.22.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile +1 -1
  4. data/README.md +281 -158
  5. data/lib/backports/2.1.0/module/singleton_class.rb +8 -0
  6. data/lib/backports/2.3.0/struct/dig.rb +2 -0
  7. data/lib/backports/2.4.0/string/unpack1.rb +7 -0
  8. data/lib/backports/2.5.0/dir/children.rb +4 -0
  9. data/lib/backports/2.5.0/dir/each_child.rb +7 -0
  10. data/lib/backports/2.5.0/integer/sqrt.rb +1 -1
  11. data/lib/backports/2.7.0/complex/{comparision.rb → comparison.rb} +0 -0
  12. data/lib/backports/2.7.0/enumerable/tally.rb +4 -3
  13. data/lib/backports/3.0.0/ractor.rb +15 -1
  14. data/lib/backports/3.1.0/array/intersect.rb +16 -0
  15. data/lib/backports/3.1.0/array.rb +3 -0
  16. data/lib/backports/3.1.0/class/descendants.rb +11 -0
  17. data/lib/backports/3.1.0/class/subclasses.rb +11 -0
  18. data/lib/backports/3.1.0/class.rb +3 -0
  19. data/lib/backports/3.1.0/enumerable/compact.rb +5 -0
  20. data/lib/backports/3.1.0/enumerable/tally.rb +18 -0
  21. data/lib/backports/3.1.0/enumerable.rb +3 -0
  22. data/lib/backports/3.1.0/file/dirname.rb +16 -0
  23. data/lib/backports/3.1.0/file.rb +3 -0
  24. data/lib/backports/3.1.0/match_data/match.rb +5 -0
  25. data/lib/backports/3.1.0/match_data/match_length.rb +6 -0
  26. data/lib/backports/3.1.0/match_data.rb +3 -0
  27. data/lib/backports/3.1.0/struct/keyword_init.rb +5 -0
  28. data/lib/backports/3.1.0/struct.rb +3 -0
  29. data/lib/backports/3.1.0.rb +3 -0
  30. data/lib/backports/3.1.rb +1 -0
  31. data/lib/backports/latest.rb +1 -1
  32. data/lib/backports/ractor/cloner.rb +81 -69
  33. data/lib/backports/ractor/errors.rb +14 -10
  34. data/lib/backports/{tools → ractor}/filtered_queue.rb +11 -8
  35. data/lib/backports/ractor/queues.rb +50 -46
  36. data/lib/backports/ractor/ractor.rb +224 -213
  37. data/lib/backports/ractor/sharing.rb +75 -71
  38. data/lib/backports/version.rb +1 -1
  39. metadata +23 -4
@@ -0,0 +1,8 @@
1
+ if Module.method_defined? :singleton_class?
2
+ class Module
3
+ def singleton_class?
4
+ # Hacky...
5
+ inspect.start_with? '#<Class:#'
6
+ end
7
+ end
8
+ end
@@ -1,6 +1,8 @@
1
1
  unless Struct.method_defined? :dig
2
2
  class Struct
3
3
  def dig(key, *rest)
4
+ return self[key] if key.respond_to?(:to_int)
5
+
4
6
  return nil unless respond_to?(key)
5
7
  val = public_send(key)
6
8
  return val if rest.empty? || val == nil
@@ -0,0 +1,7 @@
1
+ unless String.method_defined? :unpack1
2
+ class String
3
+ def unpack1(fmt)
4
+ unpack(fmt)[0]
5
+ end
6
+ end
7
+ end
@@ -3,4 +3,8 @@ class Dir
3
3
  def self.children(*args)
4
4
  entries(*args) - Backports::EXCLUDED_CHILDREN
5
5
  end
6
+
7
+ def children
8
+ self.class.children(path)
9
+ end
6
10
  end unless Dir.respond_to? :children
@@ -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 # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
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,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
- each_with_object(Hash.new(0)) { |item, res| res[item] += 1 }.tap { |h| h.default = nil }
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
- require_relative '../ractor/ractor' unless defined?(Ractor.current)
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,3 @@
1
+ require 'backports/tools/require_relative_dir'
2
+
3
+ Backports.require_relative_dir
@@ -0,0 +1,11 @@
1
+ unless Class.method_defined? :descendants
2
+ require 'backports/2.1.0/module/singleton_class'
3
+
4
+ class Class
5
+ def descendants
6
+ ObjectSpace.each_object(singleton_class).reject do |klass|
7
+ klass.singleton_class? || klass == self
8
+ end
9
+ end
10
+ end
11
+ 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,3 @@
1
+ require 'backports/tools/require_relative_dir'
2
+
3
+ Backports.require_relative_dir
@@ -0,0 +1,5 @@
1
+ module Enumerable
2
+ def compact
3
+ reject { |elem| nil == elem }
4
+ end
5
+ end unless Enumerable.method_defined? :compact
@@ -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,3 @@
1
+ require 'backports/tools/require_relative_dir'
2
+
3
+ Backports.require_relative_dir
@@ -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,3 @@
1
+ require 'backports/tools/require_relative_dir'
2
+
3
+ Backports.require_relative_dir
@@ -0,0 +1,5 @@
1
+ class MatchData
2
+ def match(index)
3
+ self[index]
4
+ end
5
+ end unless MatchData.method_defined? :match
@@ -0,0 +1,6 @@
1
+ class MatchData
2
+ def match_length(index)
3
+ m = self[index]
4
+ m && m.length
5
+ end
6
+ end unless MatchData.method_defined? :match_length
@@ -0,0 +1,3 @@
1
+ require 'backports/tools/require_relative_dir'
2
+
3
+ Backports.require_relative_dir
@@ -0,0 +1,5 @@
1
+ unless Struct.respond_to?(:keyword_init?)
2
+ def Struct.keyword_init?
3
+ new(1) && false rescue true
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ require 'backports/tools/require_relative_dir'
2
+
3
+ Backports.require_relative_dir
@@ -0,0 +1,3 @@
1
+ # require this file to load all the backports up to Ruby 3.0
2
+ require 'backports/3.0.0'
3
+ Backports.require_relative_dir if RUBY_VERSION < '3.1'
@@ -0,0 +1 @@
1
+ require 'backports/3.1.0'
@@ -1,4 +1,4 @@
1
1
  # require this file to load all the backports
2
2
  # NOTE: This is NOT recommended.
3
3
  # Best to require the specific backports you need
4
- require 'backports/2.7.0'
4
+ require 'backports/3.1.0'
@@ -1,91 +1,103 @@
1
- require_relative '../2.4.0/hash/transform_values'
2
- require_relative '../2.5.0/hash/transform_keys'
1
+ # shareable_constant_value: literal
3
2
 
4
- class Ractor
5
- module Cloner
6
- extend self
3
+ using ::RubyNext if defined?(::RubyNext)
7
4
 
8
- def deep_clone(obj)
9
- return obj if Ractor.ractor_shareable_self?(obj, false) { false }
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
- @processed = {}.compare_by_identity
12
- @changed = nil
13
- result = process(obj) do |r|
14
- copy_contents(r)
12
+ new.deep_clone(obj)
13
+ end
14
+
15
+ private :new
15
16
  end
16
- return result if result
17
17
 
18
- Ractor.ractor_mark_set_shareable(@processed)
19
- obj
20
- end
18
+ def initialize
19
+ @processed = {}.compare_by_identity
20
+ @changed = nil
21
+ end
21
22
 
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 }
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
- result = process(obj) do |r|
29
- copy_contents(r)
29
+ Ractor.ractor_mark_set_shareable(@processed)
30
+ obj
30
31
  end
31
- return obj unless result
32
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
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
- @changed = true
49
- result.freeze if obj.frozen?
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
- # 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
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
- # 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 }
59
+ @changed = true
60
+ result.freeze if obj.frozen?
61
+
62
+ result
81
63
  end
82
64
  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)
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
- class Ractor
2
- class ClosedError < ::StopIteration
3
- end
1
+ # shareable_constant_value: literal
4
2
 
5
- class Error < ::StandardError
6
- end
3
+ module Backports
4
+ class Ractor
5
+ class ClosedError < ::StopIteration
6
+ end
7
+
8
+ class Error < ::StandardError
9
+ end
7
10
 
8
- class RemoteError < Error
9
- attr_reader :ractor
11
+ class RemoteError < Error
12
+ attr_reader :ractor
10
13
 
11
- def initialize(message = nil)
12
- @ractor = Ractor.current
13
- super
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
- 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
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
- class IncomingQueue < BaseQueue
17
- TYPE = :incoming
19
+ class IncomingQueue < BaseQueue
20
+ TYPE = :incoming
18
21
 
19
- protected def reenter
20
- raise ::Ractor::Error, 'Can not reenter'
22
+ protected def reenter
23
+ raise Ractor::Error, 'Can not reenter'
24
+ end
21
25
  end
22
- end
23
26
 
24
- # * Wraps exception
25
- # * Add `ack: ` to push (blocking)
26
- class OutgoingQueue < BaseQueue
27
- TYPE = :outgoing
27
+ # * Wraps exception
28
+ # * Add `ack: ` to push (blocking)
29
+ class OutgoingQueue < BaseQueue
30
+ TYPE = :outgoing
28
31
 
29
- WrappedException = ::Struct.new(:exception, :ractor)
32
+ WrappedException = ::Struct.new(:exception, :ractor)
30
33
 
31
- def initialize
32
- @ack_queue = ::Queue.new
33
- super
34
- end
34
+ def initialize
35
+ @ack_queue = ::Queue.new
36
+ super
37
+ end
35
38
 
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
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
- r
42
- end
44
+ r
45
+ end
43
46
 
44
- def close(how = :hard)
45
- super()
46
- return if how == :soft
47
+ def close(how = :hard)
48
+ super()
49
+ return if how == :soft
47
50
 
48
- clear
49
- @ack_queue.close
50
- end
51
+ clear
52
+ @ack_queue.close
53
+ end
51
54
 
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
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