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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/Gemfile +1 -1
  4. data/README.md +285 -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/integer/try_convert.rb +5 -0
  25. data/lib/backports/3.1.0/integer.rb +3 -0
  26. data/lib/backports/3.1.0/match_data/match.rb +5 -0
  27. data/lib/backports/3.1.0/match_data/match_length.rb +6 -0
  28. data/lib/backports/3.1.0/match_data.rb +3 -0
  29. data/lib/backports/3.1.0/struct/keyword_init.rb +5 -0
  30. data/lib/backports/3.1.0/struct.rb +3 -0
  31. data/lib/backports/3.1.0.rb +3 -0
  32. data/lib/backports/3.1.rb +1 -0
  33. data/lib/backports/latest.rb +1 -1
  34. data/lib/backports/ractor/cloner.rb +81 -69
  35. data/lib/backports/ractor/errors.rb +14 -10
  36. data/lib/backports/{tools → ractor}/filtered_queue.rb +11 -8
  37. data/lib/backports/ractor/queues.rb +50 -46
  38. data/lib/backports/ractor/ractor.rb +225 -191
  39. data/lib/backports/ractor/sharing.rb +75 -71
  40. data/lib/backports/version.rb +1 -1
  41. metadata +25 -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
+ module Integer
2
+ def self.try_convert(obj)
3
+ ::Backports.try_convert(obj, ::Integer, :to_int)
4
+ end
5
+ end unless Integer.respond_to? :try_convert
@@ -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