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 +4 -4
- data/.rubocop.yml +1 -1
- data/README.md +48 -41
- data/lib/psyllium/fiber.rb +13 -38
- data/lib/psyllium/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d9b892e948fd116cd3947dad9e699175f45d904d43172152bb9435e8014a923a
|
4
|
+
data.tar.gz: 1c5f06d32d119dfdd165c042bd30954614ddc7ba1d11266a041d58c30b5982b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba46941809fddc0f737220ff6e2f4f7fe66fef593c80ef0eb58ffed036cc9c71c4d5483abfc5fb03740537e98ad9a8c79f0b4f8d05c38bbc0f60238aedddfe87
|
7
|
+
data.tar.gz: 3525344cbd3da67cd91636c2cc1a796cfb98776b236c5fdf91556729bca0038de89eb9beb8bd2db00d538fc2bc050b0b8c955f25b9930fc50c49322c8642fa41
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# Psyllium: Makes using Ruby Fibers easier
|
2
2
|
|
3
|
+
[](https://badge.fury.io/rb/psyllium)
|
4
|
+
[](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
|
13
|
-
|
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
|
16
|
-
|
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
|
-
|
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
|
34
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
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
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
96
|
-
|
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
|
-
|
117
|
-
|
123
|
+
result1 = fiber1.value
|
124
|
+
result2 = fiber2.value
|
118
125
|
```
|
119
126
|
|
120
127
|
## Development
|
data/lib/psyllium/fiber.rb
CHANGED
@@ -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 <
|
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
|
-
#
|
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
|
129
|
-
raise
|
130
|
-
raise
|
131
|
-
raise
|
132
|
-
raise
|
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
|
-
#
|
184
|
-
|
185
|
-
|
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
|
data/lib/psyllium/version.rb
CHANGED
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.
|
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-
|
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.
|
45
|
+
version: 3.3.0
|
46
46
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
47
|
requirements:
|
48
48
|
- - ">="
|