fiber-collector 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c3d63f39145b3799ab77677823d49591382983238b38df60f458092898811232
4
+ data.tar.gz: ab280299d591112ba093e985e81bf260d9d91332522b62ef1c930fe9f78da873
5
+ SHA512:
6
+ metadata.gz: a19b8a42f2a478f5fc856a66826c8cc20e98a34074b266b5fcee9ee9942940cf2d55590e5e12b166b56f2ea6c9c57343d3527eefacc01f8d1c61ae27dafaa5ab
7
+ data.tar.gz: 2748af88974099d58e9fa86c5f51680ebfe7981c080d76e3e222d6b8d274edb50de3f34b6edefe69fb305f08371db693d2b6a7e3d49190b209dcc674cf4ab5b6
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
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/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2022-12-09
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,17 @@
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 ADDED
@@ -0,0 +1,54 @@
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/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Erik Madsen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # Fiber::Collector
2
+
3
+ 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
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ $ bundle add fiber-collector
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ $ gem install fiber-collector
14
+
15
+ ## Usage
16
+
17
+ Here are some examples where `sleep` is the non-blocking operation, but it could just as well be replaced with calls to `Net::HTTP.get()` or other I/O.
18
+
19
+ ```ruby
20
+ # needs a fiber scheduler
21
+ require 'async'
22
+
23
+ # Sets the fiber scheduler and executes inside a non-blocking fiber
24
+ Async do
25
+ # Collect all results
26
+ Fiber::Collector.schedule { sleep 0.001; 'a' }
27
+ .and { sleep 0.002; 'b' }
28
+ .and { sleep 0.003; 'c' }
29
+ .all(timeout: 0.02)
30
+ end.wait
31
+ # => ['a', 'b', 'c']
32
+
33
+ Async do
34
+ Fiber::Collector.schedule { sleep 0.001; 'a' }
35
+ .and { sleep 0.002; raise 'boom' }
36
+ .and { sleep 0.003; 'c' }
37
+ .all
38
+ end.wait
39
+ # RuntimeError raised
40
+
41
+ Async do
42
+ Fiber::Collector.schedule { sleep 0.010; 'a' }
43
+ .and { sleep 0.001; raise 'e' }
44
+ .and { sleep 0.005; 'b' }
45
+ .and { sleep 0.007; 'c' }
46
+ .any(timeout: 0.02)
47
+ end.wait
48
+ # => 'b'
49
+
50
+ Async do
51
+ Fiber::Collector.schedule { sleep 0.010; 'a' }
52
+ .and { sleep 0.001; raise XError }
53
+ .and { sleep 0.005; 'b' }
54
+ .and { sleep 0.007; raise YError }
55
+ .race(timeout: 0.02)
56
+ end.wait
57
+ # XError raised
58
+
59
+ Async do
60
+ Fiber::Collector.schedule { sleep 0.010; raise XError }
61
+ .and { sleep 0.001; 'a' }
62
+ .and { sleep 0.005; 'b' }
63
+ .and { sleep 0.007; raise YError }
64
+ .race(timeout: 0.02)
65
+ end.wait
66
+ # => "a"
67
+ ```
68
+
69
+ ## Development
70
+
71
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
72
+
73
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
74
+
75
+ ## Contributing
76
+
77
+ Bug reports and pull requests are welcome on GitHub at https://github.com/beatmadsen/fiber-collector.
78
+
79
+ ## License
80
+
81
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
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]
@@ -0,0 +1,40 @@
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 = "Write a short summary, because RubyGems requires one."
13
+ spec.description = "Write a longer description or delete this line."
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
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ class Fiber
3
+ module Collector
4
+ class All
5
+ def initialize(procs)
6
+ @tasks = procs.map { Task.new(_1) }
7
+ end
8
+
9
+ 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
28
+ end
29
+ @tasks.map { |t| t.error && raise(t.error) || t.result }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ class Fiber
3
+ module Collector
4
+ class Any
5
+ def initialize(procs)
6
+ @tasks = procs.map { Task.new(_1) }
7
+ end
8
+
9
+ 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
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ class Fiber
3
+ module Collector
4
+ class Race
5
+ def initialize(procs)
6
+ @tasks = procs.map { Task.new(_1) }
7
+ end
8
+
9
+ 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
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+
2
+ class Fiber
3
+ module Collector
4
+ class Task
5
+ attr_reader :state, :error, :result
6
+
7
+ def initialize(proc)
8
+ @state = :waiting
9
+ @proc = proc
10
+ end
11
+
12
+ def done?
13
+ @state == :done
14
+ end
15
+
16
+ def run
17
+ @state = :running
18
+ @result = @proc.call
19
+ rescue StandardError => e
20
+ @error = e
21
+ ensure
22
+ @state = :done
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,36 @@
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'
6
+
7
+ class Fiber
8
+ module Collector
9
+ class Builder
10
+ def initialize
11
+ @blocks = []
12
+ end
13
+
14
+ def schedule(&block)
15
+ @blocks << block
16
+ self
17
+ end
18
+
19
+ def and(...)
20
+ schedule(...)
21
+ end
22
+
23
+ def all(timeout: nil)
24
+ All.new(@blocks).wait(timeout)
25
+ end
26
+
27
+ def any(timeout: nil)
28
+ Any.new(@blocks).wait(timeout)
29
+ end
30
+
31
+ def race(timeout: nil)
32
+ Race.new(@blocks).wait(timeout)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Fiber
4
+ module Collector
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'collector/builder'
4
+ require_relative 'collector/version'
5
+
6
+ class Fiber
7
+ module Collector
8
+ def self.schedule(&block)
9
+ Builder.new.schedule(&block)
10
+ end
11
+
12
+ class Error < StandardError; end
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fiber-collector
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Erik Madsen
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-12-09 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Write a longer description or delete this line.
14
+ email:
15
+ - beatmadsen@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".rubocop.yml"
21
+ - CHANGELOG.md
22
+ - Gemfile
23
+ - Gemfile.lock
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - fiber-collector.gemspec
28
+ - lib/fiber/collector.rb
29
+ - lib/fiber/collector/builder.rb
30
+ - lib/fiber/collector/builder/all.rb
31
+ - lib/fiber/collector/builder/any.rb
32
+ - lib/fiber/collector/builder/race.rb
33
+ - lib/fiber/collector/builder/task.rb
34
+ - lib/fiber/collector/version.rb
35
+ homepage: https://github.com/beatmadsen/fiber-collector
36
+ licenses:
37
+ - MIT
38
+ metadata:
39
+ homepage_uri: https://github.com/beatmadsen/fiber-collector
40
+ source_code_uri: https://github.com/beatmadsen/fiber-collector
41
+ changelog_uri: https://github.com/beatmadsen/fiber-collector/CHANGELOG.md
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '3.1'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubygems_version: 3.3.26
58
+ signing_key:
59
+ specification_version: 4
60
+ summary: Write a short summary, because RubyGems requires one.
61
+ test_files: []