psyllium 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fec6f648a7f186a7ec0c885aadbf36b7770a5338453c0b22d903041901b08ea3
4
- data.tar.gz: bfdbbc18b3def92c75f9dbc654ddc5878eef182e2b762172519cdda88a45e3dc
3
+ metadata.gz: d9b892e948fd116cd3947dad9e699175f45d904d43172152bb9435e8014a923a
4
+ data.tar.gz: 1c5f06d32d119dfdd165c042bd30954614ddc7ba1d11266a041d58c30b5982b3
5
5
  SHA512:
6
- metadata.gz: 1b1fd0486f6ca2028f1630fc0ac34e16d6a99f6be152030a14ad225c2c916dbc048f69fe8e7a83d9fc021d9c25edd4a224d2caf1e71e7124be832f83532c544e
7
- data.tar.gz: 619214a39896eb66ac91d853ff622112200783d2934464b61edc57c0f045d6b128068f2dbc91b4b49fdd3a912da222bdf0eaacad311a530e8c26cfb1d5822a6c
6
+ metadata.gz: ba46941809fddc0f737220ff6e2f4f7fe66fef593c80ef0eb58ffed036cc9c71c4d5483abfc5fb03740537e98ad9a8c79f0b4f8d05c38bbc0f60238aedddfe87
7
+ data.tar.gz: 3525344cbd3da67cd91636c2cc1a796cfb98776b236c5fdf91556729bca0038de89eb9beb8bd2db00d538fc2bc050b0b8c955f25b9930fc50c49322c8642fa41
data/.rubocop.yml CHANGED
@@ -3,7 +3,7 @@ plugins:
3
3
  - rubocop-rake
4
4
 
5
5
  AllCops:
6
- TargetRubyVersion: 3.1
6
+ TargetRubyVersion: 3.3
7
7
  NewCops: enable
8
8
 
9
9
  Style/SymbolArray:
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Psyllium: Makes using Ruby Fibers easier
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/psyllium.svg?icon=si%3Arubygems)](https://badge.fury.io/rb/psyllium)
4
+ [![Main GH Actions workflow](https://github.com/eestrada/psyllium/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/eestrada/psyllium/actions/workflows/main.yml?query=branch%3Amaster)
5
+
3
6
  > Psyllium \| SIL-ee-um \|
4
7
  >
5
8
  > 1. _Dietary_ the seed of a fleawort (especially Plantago psyllium). Mainly
@@ -9,55 +12,53 @@
9
12
 
10
13
  ## What is Psyllium?
11
14
 
12
- Psyllium is a library to make it easier to use Ruby Fibers for everyday
13
- programming.
15
+ Psyllium is a library to make it easier to use auto-scheduled Ruby Fibers,
16
+ block on their execution, and retrieve their final values.
14
17
 
15
- Ruby version 3 introduced the Fiber Scheduler interface, which makes it easier
16
- to use Fibers for concurrent programming. However, native Thread objects still
18
+ Ruby 3.0 introduced the Fiber Scheduler interface, making it easier to
19
+ use Fibers for concurrent programming. However, native Thread objects still
17
20
  have several useful methods that Fibers do not have.
18
21
 
19
- The Psyllium library adds many of these methods to the builtin Fiber class such
20
- as `start`, `join`, `value`, and others to make it easier to replace Thread
21
- usage with Fiber usage, or to mix and match Thread and Fiber usage without
22
- concern for which concurrency primitive is being used.
23
-
24
- Assuming that a Fiber Scheduler is set, Psyllium Fibers can be used in ways
25
- similar to Threads, with a similar interface, and with the added benefit of
26
- much lower memory usage compared to native Threads.
27
-
28
- ## Why Psyllium?
29
-
30
- Psyllium makes it easier to use auto-scheduled fibers and to block on their
31
- execution.
22
+ By default, the Fiber interface centers around two types of usage:
32
23
 
33
- Before Psyllium, the Fiber interface seemed to be centered around two types of
34
- usage: it was assumed that Fibers would be used in one of two ways:
35
-
36
- 1. (Before Ruby 3) Explicitly and manually manipulated using `Fiber.resume`,
37
- `Fiber.yield`, and `Fiber.alive?`.
24
+ 1. (Before Ruby 3) Explicitly and manually manipulated using `Fiber.yield`,
25
+ `resume`, and `alive?`.
38
26
  2. (After Ruby 3) Fired off and forgotten about. In essence, left to the
39
27
  scheduler to deal with. If you want a final value back you must use some
40
28
  separate mechanism to track and retrieve it.
41
29
 
42
- With Psyllium, it is possible to call `join` on a Fiber, just like a Thread.
43
- Assuming other Fibers are simultaneously scheduled, they will continue
44
- executing concurrently until the Fiber in question finishes joining.
30
+ Psyllium adds many of the methods of the Thread class to the builtin Fiber
31
+ class, including `start`, `join`, and `value`. This makes it easier to replace
32
+ Thread usage with Fiber usage, or to mix and match Thread and Fiber usage
33
+ without concern for which concurrency primitive is being used.
45
34
 
46
- It is also possible to call `value` to retrieve the final value (or exception)
47
- returned from the block given to `Fiber.start`, in the exact same way as a
48
- Thread. And just like with a Thread, calling `value` will first implicitly
49
- `join` the Fiber. It is also possible to give a timeout limit when calling
50
- `join` on a Fiber, just like with a Thread.
35
+ Assuming that a Fiber Scheduler is set, Psyllium Fibers can be used in ways
36
+ similar to Threads, with a similar interface, and with the added benefit of
37
+ much lower memory usage compared to native Threads.
51
38
 
52
- By using Fibers in this way, instead of Threads, memory usage can be
53
- significantly reduced. Potentially thousands of Fibers can be spawned and
54
- joined at a fraction of the memory cost of native Threads.
39
+ ## When to use Psyllium?
55
40
 
56
41
  If your Ruby application directly manipulates Threads or Thread pools, and
57
- those Threads spend most (or all) of their time just waiting on IO, then
58
- consider trying Psyllium enhanced Fibers instead of Threads.
59
-
60
- ## Why _not_ Psyllium?
42
+ those Threads spend most (or all) of their time waiting on IO, then consider
43
+ using Psyllium enhanced Fibers instead of Threads.
44
+
45
+ Let's imagine a scenario where Psyllium (or Fibers generally) could be useful:
46
+
47
+ - You have 100 URLs that you need to retrieve values from. Each URL has 1
48
+ second of network latency. Here are some solutions with memory and speed
49
+ tradeoffs:
50
+ - Solution 1: synchronously and serially retrieve values in a loop. You
51
+ receive all 100 responses in 100 seconds using no additional memory.
52
+ - Solution 2: use a thread pool of 10 threads. You receive all 100 responses
53
+ in 10 seconds using 10MB of additional memory (~1MB additional memory per
54
+ Thread).
55
+ - Solution 3: use one thread per URL. You receive all 100 responses in 1
56
+ second using 100MB of additional memory (~1MB additional memory per Thread).
57
+ - Solution 4: use one auto-fiber per URL. You receive all 100 responses in 1
58
+ second using 1.3MB of additional memory ([~13KB of physical memory
59
+ per Fiber](https://bugs.ruby-lang.org/issues/15997)).
60
+
61
+ ## When _not_ to use Psyllium?
61
62
 
62
63
  Circumstances where you shouldn't use Psyllium (or Fibers generally):
63
64
 
@@ -90,10 +91,13 @@ thread2 = Thread.start { long_running_io_operation_with_result2() }
90
91
  thread1.join
91
92
  thread2.join
92
93
 
94
+ puts 'thread1 ended with an exception' if thread1.status.nil?
95
+ puts 'thread2 ended without an exception' if thread2.status == false
96
+
93
97
  # `value` implicitly calls `join`, so the explicit `join` calls above are
94
98
  # not strictly necessary.
95
- value1 = thread1.value
96
- value2 = thread2.value
99
+ result1 = thread1.value
100
+ result2 = thread2.value
97
101
  ```
98
102
 
99
103
  You can now do this:
@@ -111,10 +115,13 @@ fiber2 = Fiber.start { long_running_io_operation_with_result2() }
111
115
  fiber1.join
112
116
  fiber2.join
113
117
 
118
+ puts 'fiber1 ended with an exception' if fiber1.status.nil?
119
+ puts 'fiber2 ended without an exception' if fiber2.status == false
120
+
114
121
  # `value` implicitly calls `join`, so the explicit `join` calls above are
115
122
  # not strictly necessary.
116
- value1 = fiber1.value
117
- value2 = fiber2.value
123
+ result1 = fiber1.value
124
+ result2 = fiber2.value
118
125
  ```
119
126
 
120
127
  ## Development
@@ -3,8 +3,11 @@
3
3
  require 'timeout'
4
4
 
5
5
  module Psyllium
6
+ # Base Exception class for module code.
7
+ class Error < FiberError; end
8
+
6
9
  # Wrap Exception instances for propagation
7
- class ExceptionalCompletionError < FiberError
10
+ class ExceptionalCompletionError < Error
8
11
  def initialize(expt)
9
12
  @internal_exception = expt
10
13
  super(@internal_exception)
@@ -59,7 +62,7 @@ module Psyllium
59
62
 
60
63
  def state_get(fiber: Fiber.current, create_missing: false)
61
64
  # Psyllium state is a thread local variable because Fibers cannot (yet)
62
- # migrates across threads anyway.
65
+ # migrate across threads anyway.
63
66
  #
64
67
  # A `WeakKeyMap` is used so that when a Fiber is garbage collected, the
65
68
  # associated Psyllium::State will be garbage collected as well.
@@ -125,11 +128,11 @@ module Psyllium
125
128
  # `join` may be called more than once.
126
129
  def join(limit = nil) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/AbcSize
127
130
  return self if state.joined
128
- raise FiberError.new('Cannot join self') if eql?(::Fiber.current)
129
- raise FiberError.new('Cannot join when calling Fiber is blocking') if ::Fiber.current.blocking?
130
- raise FiberError.new('Cannot join when called Fiber is blocking') if blocking?
131
- raise FiberError.new('Cannot join without Fiber scheduler set') unless ::Fiber.scheduler
132
- raise FiberError.new('Cannot join unstarted Fiber') unless state.started
131
+ raise Error.new('Cannot join self') if eql?(::Fiber.current)
132
+ raise Error.new('Cannot join when calling Fiber is blocking') if ::Fiber.current.blocking?
133
+ raise Error.new('Cannot join when called Fiber is blocking') if blocking?
134
+ raise Error.new('Cannot join without Fiber scheduler set') unless ::Fiber.scheduler
135
+ raise Error.new('Cannot join unstarted Fiber') unless state.started
133
136
 
134
137
  # Once this mutex finishes synchronizing, that means the initial
135
138
  # calculation is done and we can return `self`, which is the Fiber
@@ -149,41 +152,13 @@ module Psyllium
149
152
  fiber_state
150
153
  end
151
154
  end
152
-
153
- # Inherits from the builtin Fiber class, and adds additional functionality to
154
- # make it behave more like a Thread.
155
- class Fiber < ::Fiber
156
- extend ::Psyllium::FiberClassMethods
157
- # This must be prepended so that its implementation of `initialize` is called
158
- # first.
159
- include ::Psyllium::FiberInstanceMethods
160
-
161
- # The `Fiber.kill` method only exists in later versions of Ruby.
162
- if instance_methods.include?(:kill)
163
- # Thread has the same aliases
164
- alias terminate kill
165
- alias exit kill
166
- end
167
- end
168
-
169
- # TODO: figure out how to do this properly
170
- # def self.patch_builtin_fiber!
171
- # return if ::Fiber.is_a?(FiberMethods)
172
-
173
- # ::Fiber.singleton_class.prepend(FiberMethods)
174
- # end
175
155
  end
176
156
 
177
157
  class ::Fiber # rubocop:disable Style/Documentation
178
158
  extend ::Psyllium::FiberClassMethods
179
- # This must be prepended so that its implementation of `initialize` is called
180
- # first.
181
159
  include ::Psyllium::FiberInstanceMethods
182
160
 
183
- # The `Fiber.kill` method only exists in later versions of Ruby.
184
- if instance_methods.include?(:kill)
185
- # Thread has the same aliases
186
- alias terminate kill
187
- alias exit kill
188
- end
161
+ # Thread has the same aliases
162
+ alias terminate kill
163
+ alias exit kill
189
164
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Psyllium
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: psyllium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ethan Estrada
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-03-12 00:00:00.000000000 Z
11
+ date: 2025-03-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -42,7 +42,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
42
42
  requirements:
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
- version: 3.1.0
45
+ version: 3.3.0
46
46
  required_rubygems_version: !ruby/object:Gem::Requirement
47
47
  requirements:
48
48
  - - ">="