fiber-collector 0.1.1 → 1.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d766d23a129b1199c9c8fad131eb750dcec6df27756506ba7b1af487c3754ba1
4
- data.tar.gz: e85c74bfc3e2af76aa91c8fcfb013ae09d09c4bacc88df63ed525d2c57043ef9
3
+ metadata.gz: e18213ff1d06e8e74852e8e97d62f08bf02d4c5633654ee74351f74d1320e5e6
4
+ data.tar.gz: 212d2754251189410de0f87d544df0801eb42197ad61773cbdb93964dc65c535
5
5
  SHA512:
6
- metadata.gz: 0b5e2f0b6a23a30bb5bb321e9b2f84eadb583eaa3e4533fd4a39845c1056645f8c633555bc99eeec32014dba9429d07a61303fd74daa83f552f49f47898dc7cb
7
- data.tar.gz: 12cd120470d5158c374cb24f7606f4f9e3197e2f5a5c308b406a3d94fe1bc35c7e4130bfd0b2d239e7f350ee545082e996e90e6d5d2f4b17223127935c1f238a
6
+ metadata.gz: 84fd72f509e19cf60e942dc18853659fd348db3c7066e8b7090736afb84aa9f1c48b4300f5408d866ad683b0f25aceb4ad8c87476f2cb941c6a9d0e03121edcf
7
+ data.tar.gz: ddfdf89f536393f708aa1ad019eb3b82f59a64af7a7081f89dbcaf0be7a23b77e853f534762e2bde4bb39b80c1b3bb24160d360c1f82d6b6a13a849fa5dbb4fd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,3 @@
1
- ## [Unreleased]
2
-
3
1
  ## [0.1.0] - 2022-12-09
4
2
 
5
3
  - Initial release
@@ -7,3 +5,19 @@
7
5
  ## [0.1.1] - 2022-12-09
8
6
 
9
7
  - Documentation bump for the RubyGems page
8
+
9
+ ## [1.0.0] - 2023-01-26
10
+
11
+ - Updating dependencies, fixing CHANGELOG link, adding gem badge to README
12
+
13
+ ## [1.0.1] - 2026-09-20
14
+
15
+ - Runs on Ruby 4. The pinned rake required ostruct, which left the standard
16
+ library, so `rake` could not start.
17
+ - `all`, `any` and `race` share one polling implementation instead of each
18
+ carrying its own copy with the timeout and no-timeout cases written out twice.
19
+ No change in behaviour.
20
+ - The default `timeout: nil` and an expiring timeout both have tests. Every
21
+ existing test passed a timeout that never expired, so neither path was covered.
22
+ - The published gem no longer carries the Rakefile, Gemfile or rubocop config.
23
+ - Publishing requires multi-factor auth.
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Fiber::Collector
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/fiber-collector.svg)](https://badge.fury.io/rb/fiber-collector)
4
+
3
5
  An easy way to schedule and aggregate the results from multiple concurrent tasks inspired by JavaScript's `Promise.all()`, `Promise.any()` and `Promise.race()` methods.
4
6
 
5
7
  ## Installation
@@ -1,32 +1,24 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Fiber
3
4
  module Collector
5
+ # Settles once every task has finished, failing as soon as any one of them
6
+ # does.
4
7
  class All
8
+ include Polling
9
+
5
10
  def initialize(procs)
6
11
  @tasks = procs.map { Task.new(_1) }
7
12
  end
8
13
 
9
14
  def wait(timeout)
10
- raise "must run on non-blocking fiber" if Fiber.current.blocking?
11
- @tasks.each do |task|
12
- Fiber.schedule { task.run }
13
- end
14
- if timeout.nil?
15
- sleep 0.001 until @tasks.all?(&:done?)
16
- else
17
- elapsed = 0
18
- until @tasks.all?(&:done?)
19
- task = @tasks.find { |t| t.error }
20
- unless task.nil?
21
- raise task.error
22
- end
23
- t = 0.001
24
- sleep t
25
- elapsed += t
26
- raise "timeout" if elapsed > timeout
27
- end
15
+ schedule_tasks
16
+ poll_until(timeout) do
17
+ failed = @tasks.find(&:error)
18
+ raise failed.error unless failed.nil?
19
+
20
+ finished(@tasks.map(&:result)) if @tasks.all?(&:done?)
28
21
  end
29
- @tasks.map { |t| t.error && raise(t.error) || t.result }
30
22
  end
31
23
  end
32
24
  end
@@ -1,34 +1,22 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Fiber
3
4
  module Collector
5
+ # Settles on the first task to succeed, and fails only if they all fail.
4
6
  class Any
7
+ include Polling
8
+
5
9
  def initialize(procs)
6
10
  @tasks = procs.map { Task.new(_1) }
7
11
  end
8
12
 
9
13
  def wait(timeout)
10
- raise "must run on non-blocking fiber" if Fiber.current.blocking?
11
- @tasks.each do |task|
12
- Fiber.schedule { task.run }
13
- end
14
- if timeout.nil?
15
- loop do
16
- task = @tasks.find { |t| t.done? && t.error.nil? }
17
- return task.result unless task.nil?
18
- raise @tasks.first.error if @tasks.all? { |t| t.done? && t.error }
19
- sleep 0.001
20
- end
21
- else
22
- elapsed = 0
23
- loop do
24
- task = @tasks.find { |t| t.done? && t.error.nil? }
25
- return task.result unless task.nil?
26
- raise @tasks.first.error if @tasks.all? { |t| t.done? && t.error }
27
- t = 0.001
28
- sleep t
29
- elapsed += t
30
- raise "timeout" if elapsed > timeout
31
- end
14
+ schedule_tasks
15
+ poll_until(timeout) do
16
+ task = @tasks.find { |t| t.done? && t.error.nil? }
17
+ next finished(task.result) unless task.nil?
18
+
19
+ raise @tasks.first.error if @tasks.all? { |t| t.done? && t.error }
32
20
  end
33
21
  end
34
22
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Fiber
4
+ module Collector
5
+ # Shared machinery for the three collectors. Each schedules its tasks, then
6
+ # polls until the block it is given returns something other than nil, or
7
+ # until the timeout passes. A nil timeout waits indefinitely.
8
+ module Polling
9
+ INTERVAL = 0.001
10
+
11
+ private
12
+
13
+ def schedule_tasks
14
+ raise "must run on non-blocking fiber" if Fiber.current.blocking?
15
+
16
+ @tasks.each { |task| Fiber.schedule { task.run } }
17
+ end
18
+
19
+ def poll_until(timeout)
20
+ elapsed = 0
21
+ loop do
22
+ outcome = yield
23
+ return outcome.first unless outcome.nil?
24
+
25
+ sleep INTERVAL
26
+ elapsed += INTERVAL
27
+ raise "timeout" if timeout && elapsed > timeout
28
+ end
29
+ end
30
+
31
+ # Wraps a value so that poll_until can tell "finished with nil" from
32
+ # "not finished yet".
33
+ def finished(value) = [value]
34
+
35
+ def result_of(task)
36
+ raise task.error unless task.error.nil?
37
+
38
+ task.result
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,38 +1,20 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Fiber
3
4
  module Collector
5
+ # Settles as soon as any task finishes, whether it succeeded or failed.
4
6
  class Race
7
+ include Polling
8
+
5
9
  def initialize(procs)
6
10
  @tasks = procs.map { Task.new(_1) }
7
11
  end
8
12
 
9
13
  def wait(timeout)
10
- raise "must run on non-blocking fiber" if Fiber.current.blocking?
11
- @tasks.each do |task|
12
- Fiber.schedule { task.run }
13
- end
14
- if timeout.nil?
15
- loop do
16
- task = @tasks.find { |t| t.done? }
17
- unless task.nil?
18
- raise task.error unless task.error.nil?
19
- return task.result
20
- end
21
- sleep 0.001
22
- end
23
- else
24
- elapsed = 0
25
- loop do
26
- task = @tasks.find { |t| t.done? }
27
- unless task.nil?
28
- raise task.error unless task.error.nil?
29
- return task.result
30
- end
31
- t = 0.001
32
- sleep t
33
- elapsed += t
34
- raise "timeout" if elapsed > timeout
35
- end
14
+ schedule_tasks
15
+ poll_until(timeout) do
16
+ task = @tasks.find(&:done?)
17
+ finished(result_of(task)) unless task.nil?
36
18
  end
37
19
  end
38
20
  end
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  class Fiber
3
4
  module Collector
5
+ # One scheduled block, and whatever it produced: a result, or the error it
6
+ # raised. Never raises out of #run, so a collector can inspect every task.
4
7
  class Task
5
8
  attr_reader :state, :error, :result
6
9
 
@@ -23,4 +26,4 @@ class Fiber
23
26
  end
24
27
  end
25
28
  end
26
- end
29
+ end
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
- require_relative 'builder/all'
3
- require_relative 'builder/any'
4
- require_relative 'builder/race'
5
- require_relative 'builder/task'
2
+
3
+ require_relative "builder/polling"
4
+ require_relative "builder/all"
5
+ require_relative "builder/any"
6
+ require_relative "builder/race"
7
+ require_relative "builder/task"
6
8
 
7
9
  class Fiber
8
- module Collector
10
+ module Collector
11
+ # Gathers the scheduled blocks, then hands them to whichever collector the
12
+ # terminating call names: #all, #any or #race.
9
13
  class Builder
10
14
  def initialize
11
15
  @blocks = []
@@ -23,7 +27,7 @@ class Fiber
23
27
  def all(timeout: nil)
24
28
  All.new(@blocks).wait(timeout)
25
29
  end
26
-
30
+
27
31
  def any(timeout: nil)
28
32
  Any.new(@blocks).wait(timeout)
29
33
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  class Fiber
4
4
  module Collector
5
- VERSION = "0.1.1"
5
+ VERSION = "1.0.1"
6
6
  end
7
7
  end
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'collector/builder'
4
- require_relative 'collector/version'
3
+ require_relative "collector/builder"
4
+ require_relative "collector/version"
5
5
 
6
6
  class Fiber
7
+ # Collects the results of blocks run concurrently on non-blocking fibers,
8
+ # in the manner of JavaScript's Promise.all, Promise.any and Promise.race.
7
9
  module Collector
8
10
  def self.schedule(&block)
9
11
  Builder.new.schedule(&block)
metadata CHANGED
@@ -1,35 +1,31 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fiber-collector
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erik Madsen
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2022-12-09 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: Simple constructs to collect results from multiple concurrently scheduled
14
- tasks inspired by JavaScript's Promise.all(), Promise.any() and Promise.race() methods
13
+ tasks, inspired by JavaScript's Promise.all(), Promise.any() and Promise.race()
14
+ methods
15
15
  email:
16
16
  - beatmadsen@gmail.com
17
17
  executables: []
18
18
  extensions: []
19
19
  extra_rdoc_files: []
20
20
  files:
21
- - ".rubocop.yml"
22
21
  - CHANGELOG.md
23
- - Gemfile
24
- - Gemfile.lock
25
22
  - LICENSE.txt
26
23
  - README.md
27
- - Rakefile
28
- - fiber-collector.gemspec
29
24
  - lib/fiber/collector.rb
30
25
  - lib/fiber/collector/builder.rb
31
26
  - lib/fiber/collector/builder/all.rb
32
27
  - lib/fiber/collector/builder/any.rb
28
+ - lib/fiber/collector/builder/polling.rb
33
29
  - lib/fiber/collector/builder/race.rb
34
30
  - lib/fiber/collector/builder/task.rb
35
31
  - lib/fiber/collector/version.rb
@@ -39,8 +35,10 @@ licenses:
39
35
  metadata:
40
36
  homepage_uri: https://github.com/beatmadsen/fiber-collector
41
37
  source_code_uri: https://github.com/beatmadsen/fiber-collector
42
- changelog_uri: https://github.com/beatmadsen/fiber-collector/CHANGELOG.md
43
- post_install_message:
38
+ changelog_uri: https://github.com/beatmadsen/fiber-collector/blob/main/CHANGELOG.md
39
+ bug_tracker_uri: https://github.com/beatmadsen/fiber-collector/issues
40
+ documentation_uri: https://github.com/beatmadsen/fiber-collector#readme
41
+ rubygems_mfa_required: 'true'
44
42
  rdoc_options: []
45
43
  require_paths:
46
44
  - lib
@@ -55,8 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
53
  - !ruby/object:Gem::Version
56
54
  version: '0'
57
55
  requirements: []
58
- rubygems_version: 3.3.26
59
- signing_key:
56
+ rubygems_version: 4.0.9
60
57
  specification_version: 4
61
58
  summary: Collect results from multiple concurrently scheduled tasks
62
59
  test_files: []
data/.rubocop.yml DELETED
@@ -1,13 +0,0 @@
1
- AllCops:
2
- TargetRubyVersion: 3.1
3
-
4
- Style/StringLiterals:
5
- Enabled: true
6
- EnforcedStyle: double_quotes
7
-
8
- Style/StringLiteralsInInterpolation:
9
- Enabled: true
10
- EnforcedStyle: double_quotes
11
-
12
- Layout/LineLength:
13
- Max: 120
data/Gemfile DELETED
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- # Specify your gem's dependencies in fiber-collector.gemspec
6
- gemspec
7
-
8
- gem "rake", "~> 13.0"
9
-
10
- gem "minitest", "~> 5.0"
11
-
12
- gem "rubocop", "~> 1.21"
13
-
14
-
15
- group :development do
16
- gem 'async', '> 2.0'
17
- end
data/Gemfile.lock DELETED
@@ -1,54 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- fiber-collector (0.1.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- ast (2.4.2)
10
- async (2.3.0)
11
- console (~> 1.10)
12
- io-event (~> 1.1)
13
- timers (~> 4.1)
14
- console (1.16.2)
15
- fiber-local
16
- fiber-local (1.0.0)
17
- io-event (1.1.4)
18
- json (2.6.3)
19
- minitest (5.16.3)
20
- parallel (1.22.1)
21
- parser (3.1.3.0)
22
- ast (~> 2.4.1)
23
- rainbow (3.1.1)
24
- rake (13.0.6)
25
- regexp_parser (2.6.1)
26
- rexml (3.2.5)
27
- rubocop (1.40.0)
28
- json (~> 2.3)
29
- parallel (~> 1.10)
30
- parser (>= 3.1.2.1)
31
- rainbow (>= 2.2.2, < 4.0)
32
- regexp_parser (>= 1.8, < 3.0)
33
- rexml (>= 3.2.5, < 4.0)
34
- rubocop-ast (>= 1.23.0, < 2.0)
35
- ruby-progressbar (~> 1.7)
36
- unicode-display_width (>= 1.4.0, < 3.0)
37
- rubocop-ast (1.24.0)
38
- parser (>= 3.1.1.0)
39
- ruby-progressbar (1.11.0)
40
- timers (4.3.5)
41
- unicode-display_width (2.3.0)
42
-
43
- PLATFORMS
44
- x86_64-darwin-20
45
-
46
- DEPENDENCIES
47
- async (> 2.0)
48
- fiber-collector!
49
- minitest (~> 5.0)
50
- rake (~> 13.0)
51
- rubocop (~> 1.21)
52
-
53
- BUNDLED WITH
54
- 2.3.26
data/Rakefile DELETED
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rake/testtask"
5
-
6
- Rake::TestTask.new(:test) do |t|
7
- t.libs << "test"
8
- t.libs << "lib"
9
- t.test_files = FileList["test/**/test_*.rb"]
10
- end
11
-
12
- require "rubocop/rake_task"
13
-
14
- RuboCop::RakeTask.new
15
-
16
- task default: %i[test rubocop]
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/fiber/collector/version"
4
-
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "fiber-collector"
8
- spec.version = Fiber::Collector::VERSION
9
- spec.authors = ["Erik Madsen"]
10
- spec.email = ["beatmadsen@gmail.com"]
11
-
12
- spec.summary = "Collect results from multiple concurrently scheduled tasks"
13
- spec.description = "Simple constructs to collect results from multiple concurrently scheduled tasks inspired by JavaScript's Promise.all(), Promise.any() and Promise.race() methods"
14
- spec.homepage = 'https://github.com/beatmadsen/fiber-collector'
15
- spec.license = "MIT"
16
- spec.required_ruby_version = ">= 3.1"
17
-
18
- # spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
19
-
20
- spec.metadata["homepage_uri"] = spec.homepage
21
- spec.metadata["source_code_uri"] = 'https://github.com/beatmadsen/fiber-collector'
22
- spec.metadata["changelog_uri"] = 'https://github.com/beatmadsen/fiber-collector/CHANGELOG.md'
23
-
24
- # Specify which files should be added to the gem when it is released.
25
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
26
- spec.files = Dir.chdir(__dir__) do
27
- `git ls-files -z`.split("\x0").reject do |f|
28
- (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
29
- end
30
- end
31
- spec.bindir = "exe"
32
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
- spec.require_paths = ["lib"]
34
-
35
- # Uncomment to register a new dependency of your gem
36
- # spec.add_dependency "example-gem", "~> 1.0"
37
-
38
- # For more information and examples about making a new gem, check out our
39
- # guide at: https://bundler.io/guides/creating_gem.html
40
- end