fractor 0.1.1 → 0.1.3
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_todo.yml +27 -11
- data/lib/fractor/result_aggregator.rb +2 -2
- data/lib/fractor/supervisor.rb +35 -48
- data/lib/fractor/version.rb +1 -1
- data/lib/fractor/worker.rb +5 -0
- data/lib/fractor/wrapped_ractor.rb +23 -16
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b51bfbe1d8aac8575dcd85deb88b286b8f3b26e391a228e01d43f3a466d91f26
|
4
|
+
data.tar.gz: '08198a0b664d2ddfa9cbef0a173f1672f9584dba1700f82374b0b9916cad91a6'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af7c2e42e19b28ced53b9e3aecf37409b9aff3c92d8829a0835312a4a5852069530b36f57c01c84727d09ad9de3b308ecfa05a5a812454402a300219938bce19
|
7
|
+
data.tar.gz: e08de69150ff8d528d481fcb1e872cd2d274e6b332f6d2ad3ad3b879e13328d50af3fae3294aecd81851dc7d6f3f6cc317570a7978b0f1a8adcd142d4b18a8f5
|
data/.rubocop_todo.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on 2025-05-
|
3
|
+
# on 2025-05-08 09:25:37 UTC using RuboCop version 1.75.5.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
@@ -14,6 +14,11 @@ Lint/ConstantDefinitionInBlock:
|
|
14
14
|
- 'spec/fractor/integration_spec.rb'
|
15
15
|
- 'spec/fractor/work_spec.rb'
|
16
16
|
|
17
|
+
# Offense count: 1
|
18
|
+
Lint/DuplicateMethods:
|
19
|
+
Exclude:
|
20
|
+
- 'lib/fractor/supervisor.rb'
|
21
|
+
|
17
22
|
# Offense count: 1
|
18
23
|
Lint/HashCompareByIdentity:
|
19
24
|
Exclude:
|
@@ -30,36 +35,41 @@ Lint/RescueException:
|
|
30
35
|
Exclude:
|
31
36
|
- 'lib/fractor/wrapped_ractor.rb'
|
32
37
|
|
33
|
-
# Offense count:
|
38
|
+
# Offense count: 18
|
34
39
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
35
40
|
Metrics/AbcSize:
|
36
|
-
Max:
|
41
|
+
Max: 98
|
37
42
|
|
38
|
-
# Offense count:
|
43
|
+
# Offense count: 9
|
39
44
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
40
45
|
# AllowedMethods: refine
|
41
46
|
Metrics/BlockLength:
|
42
|
-
Max:
|
47
|
+
Max: 101
|
43
48
|
|
44
49
|
# Offense count: 2
|
45
50
|
# Configuration parameters: CountComments, CountAsOne.
|
46
51
|
Metrics/ClassLength:
|
47
|
-
Max:
|
52
|
+
Max: 159
|
48
53
|
|
49
54
|
# Offense count: 3
|
50
55
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
51
56
|
Metrics/CyclomaticComplexity:
|
52
|
-
Max:
|
57
|
+
Max: 37
|
53
58
|
|
54
|
-
# Offense count:
|
59
|
+
# Offense count: 33
|
55
60
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
56
61
|
Metrics/MethodLength:
|
57
|
-
Max:
|
62
|
+
Max: 67
|
63
|
+
|
64
|
+
# Offense count: 1
|
65
|
+
# Configuration parameters: Max, CountKeywordArgs.
|
66
|
+
Metrics/ParameterLists:
|
67
|
+
MaxOptionalParameters: 4
|
58
68
|
|
59
69
|
# Offense count: 2
|
60
70
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
61
71
|
Metrics/PerceivedComplexity:
|
62
|
-
Max:
|
72
|
+
Max: 37
|
63
73
|
|
64
74
|
# Offense count: 1
|
65
75
|
Security/Eval:
|
@@ -74,7 +84,13 @@ Style/Documentation:
|
|
74
84
|
- 'test/**/*'
|
75
85
|
- 'examples/hierarchical_hasher/hierarchical_hasher.rb'
|
76
86
|
|
77
|
-
# Offense count:
|
87
|
+
# Offense count: 13
|
88
|
+
# This cop supports safe autocorrection (--autocorrect).
|
89
|
+
Style/IfUnlessModifier:
|
90
|
+
Exclude:
|
91
|
+
- 'lib/fractor/supervisor.rb'
|
92
|
+
|
93
|
+
# Offense count: 6
|
78
94
|
# This cop supports safe autocorrection (--autocorrect).
|
79
95
|
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
|
80
96
|
# URISchemes: http, https
|
@@ -13,10 +13,10 @@ module Fractor
|
|
13
13
|
|
14
14
|
def add_result(result)
|
15
15
|
if result.success?
|
16
|
-
puts "Work completed successfully: Result: #{result.result}"
|
16
|
+
puts "Work completed successfully: Result: #{result.result}" if ENV["FRACTOR_DEBUG"]
|
17
17
|
@results << result
|
18
18
|
else
|
19
|
-
puts "Error processing work: #{result}"
|
19
|
+
puts "Error processing work: #{result}" if ENV["FRACTOR_DEBUG"]
|
20
20
|
@errors << result
|
21
21
|
end
|
22
22
|
|
data/lib/fractor/supervisor.rb
CHANGED
@@ -37,9 +37,7 @@ module Fractor
|
|
37
37
|
# Adds a single work item to the queue.
|
38
38
|
# The item must be an instance of Fractor::Work or a subclass.
|
39
39
|
def add_work_item(work)
|
40
|
-
unless work.is_a?(Fractor::Work)
|
41
|
-
raise ArgumentError, "#{work.class} must be an instance of Fractor::Work"
|
42
|
-
end
|
40
|
+
raise ArgumentError, "#{work.class} must be an instance of Fractor::Work" unless work.is_a?(Fractor::Work)
|
43
41
|
|
44
42
|
@work_queue << work
|
45
43
|
@total_work_count += 1
|
@@ -90,19 +88,32 @@ module Fractor
|
|
90
88
|
|
91
89
|
# Sets up a signal handler for graceful shutdown (Ctrl+C).
|
92
90
|
def setup_signal_handler
|
93
|
-
#
|
91
|
+
# Store instance variables in local variables for the signal handler
|
94
92
|
workers_ref = @workers
|
93
|
+
|
94
|
+
# Trap INT signal (Ctrl+C)
|
95
95
|
Signal.trap("INT") do
|
96
|
-
puts "\nCtrl+C received. Initiating immediate shutdown..."
|
97
|
-
|
96
|
+
puts "\nCtrl+C received. Initiating immediate shutdown..." if ENV["FRACTOR_DEBUG"]
|
97
|
+
|
98
|
+
# Set running to false to break the main loop
|
99
|
+
@running = false
|
100
|
+
|
101
|
+
puts "Sending shutdown message to all Ractors..." if ENV["FRACTOR_DEBUG"]
|
102
|
+
|
103
|
+
# Send shutdown message to each worker Ractor
|
98
104
|
workers_ref.each do |w|
|
99
|
-
w.
|
100
|
-
puts "
|
105
|
+
w.send(:shutdown)
|
106
|
+
puts "Sent shutdown to Ractor: #{w.name}" if ENV["FRACTOR_DEBUG"]
|
101
107
|
rescue StandardError => e
|
102
|
-
puts "Error
|
108
|
+
puts "Error sending shutdown to Ractor #{w.name}: #{e.message}" if ENV["FRACTOR_DEBUG"]
|
103
109
|
end
|
104
|
-
|
105
|
-
|
110
|
+
|
111
|
+
puts "Exiting now." if ENV["FRACTOR_DEBUG"]
|
112
|
+
exit!(1) # Use exit! to exit immediately without running at_exit handlers
|
113
|
+
rescue Exception => e
|
114
|
+
puts "Error in signal handler: #{e.class}: #{e.message}" if ENV["FRACTOR_DEBUG"]
|
115
|
+
puts e.backtrace.join("\n") if ENV["FRACTOR_DEBUG"]
|
116
|
+
exit!(1)
|
106
117
|
end
|
107
118
|
end
|
108
119
|
|
@@ -139,9 +150,7 @@ module Fractor
|
|
139
150
|
|
140
151
|
# Break if no active workers and queue is empty, but work remains (indicates potential issue)
|
141
152
|
if active_ractors.empty? && @work_queue.empty? && !@continuous_mode && processed_count < @total_work_count
|
142
|
-
if ENV["FRACTOR_DEBUG"]
|
143
|
-
puts "Warning: No active workers and queue is empty, but not all work is processed. Exiting loop."
|
144
|
-
end
|
153
|
+
puts "Warning: No active workers and queue is empty, but not all work is processed. Exiting loop." if ENV["FRACTOR_DEBUG"]
|
145
154
|
break
|
146
155
|
end
|
147
156
|
|
@@ -160,30 +169,22 @@ module Fractor
|
|
160
169
|
# Find the corresponding WrappedRactor instance
|
161
170
|
wrapped_ractor = @ractors_map[ready_ractor_obj]
|
162
171
|
unless wrapped_ractor
|
163
|
-
if ENV["FRACTOR_DEBUG"]
|
164
|
-
puts "Warning: Received message from unknown Ractor: #{ready_ractor_obj}. Ignoring."
|
165
|
-
end
|
172
|
+
puts "Warning: Received message from unknown Ractor: #{ready_ractor_obj}. Ignoring." if ENV["FRACTOR_DEBUG"]
|
166
173
|
next
|
167
174
|
end
|
168
175
|
|
169
|
-
if ENV["FRACTOR_DEBUG"]
|
170
|
-
puts "Selected Ractor: #{wrapped_ractor.name}, Message Type: #{message[:type]}"
|
171
|
-
end
|
176
|
+
puts "Selected Ractor: #{wrapped_ractor.name}, Message Type: #{message[:type]}" if ENV["FRACTOR_DEBUG"]
|
172
177
|
|
173
178
|
# Process the received message
|
174
179
|
case message[:type]
|
175
180
|
when :initialize
|
176
|
-
if ENV["FRACTOR_DEBUG"]
|
177
|
-
puts "Ractor initialized: #{message[:processor]}"
|
178
|
-
end
|
181
|
+
puts "Ractor initialized: #{message[:processor]}" if ENV["FRACTOR_DEBUG"]
|
179
182
|
# Send work immediately upon initialization if available
|
180
183
|
send_next_work_if_available(wrapped_ractor)
|
181
184
|
when :result
|
182
185
|
# The message[:result] should be a WorkResult object
|
183
186
|
work_result = message[:result]
|
184
|
-
if ENV["FRACTOR_DEBUG"]
|
185
|
-
puts "Completed work: #{work_result.inspect} in Ractor: #{message[:processor]}"
|
186
|
-
end
|
187
|
+
puts "Completed work: #{work_result.inspect} in Ractor: #{message[:processor]}" if ENV["FRACTOR_DEBUG"]
|
187
188
|
@results.add_result(work_result)
|
188
189
|
if ENV["FRACTOR_DEBUG"]
|
189
190
|
puts "Result processed. Total processed: #{@results.results.size + @results.errors.size}"
|
@@ -194,9 +195,7 @@ module Fractor
|
|
194
195
|
when :error
|
195
196
|
# The message[:result] should be a WorkResult object containing the error
|
196
197
|
error_result = message[:result]
|
197
|
-
if ENV["FRACTOR_DEBUG"]
|
198
|
-
puts "Error processing work #{error_result.work&.inspect} in Ractor: #{message[:processor]}: #{error_result.error}"
|
199
|
-
end
|
198
|
+
puts "Error processing work #{error_result.work&.inspect} in Ractor: #{message[:processor]}: #{error_result.error}" if ENV["FRACTOR_DEBUG"]
|
200
199
|
@results.add_result(error_result) # Add error to aggregator
|
201
200
|
if ENV["FRACTOR_DEBUG"]
|
202
201
|
puts "Error handled. Total processed: #{@results.results.size + @results.errors.size}"
|
@@ -205,17 +204,13 @@ module Fractor
|
|
205
204
|
# Send next piece of work even after an error
|
206
205
|
send_next_work_if_available(wrapped_ractor)
|
207
206
|
else
|
208
|
-
if ENV["FRACTOR_DEBUG"]
|
209
|
-
puts "Unknown message type received: #{message[:type]} from #{wrapped_ractor.name}"
|
210
|
-
end
|
207
|
+
puts "Unknown message type received: #{message[:type]} from #{wrapped_ractor.name}" if ENV["FRACTOR_DEBUG"]
|
211
208
|
end
|
212
209
|
# Update processed count for the loop condition
|
213
210
|
processed_count = @results.results.size + @results.errors.size
|
214
211
|
end
|
215
212
|
|
216
|
-
if ENV["FRACTOR_DEBUG"]
|
217
|
-
puts "Main loop finished."
|
218
|
-
end
|
213
|
+
puts "Main loop finished." if ENV["FRACTOR_DEBUG"]
|
219
214
|
return if @continuous_mode
|
220
215
|
|
221
216
|
return unless ENV["FRACTOR_DEBUG"]
|
@@ -226,7 +221,7 @@ module Fractor
|
|
226
221
|
# Stop the supervisor (for continuous mode)
|
227
222
|
def stop
|
228
223
|
@running = false
|
229
|
-
puts "Stopping supervisor..."
|
224
|
+
puts "Stopping supervisor..." if ENV["FRACTOR_DEBUG"]
|
230
225
|
end
|
231
226
|
|
232
227
|
private
|
@@ -238,17 +233,11 @@ module Fractor
|
|
238
233
|
if !@work_queue.empty?
|
239
234
|
work_item = @work_queue.pop # Now directly a Work object
|
240
235
|
|
241
|
-
if ENV["FRACTOR_DEBUG"]
|
242
|
-
puts "Sending next work #{work_item.inspect} to Ractor: #{wrapped_ractor.name}"
|
243
|
-
end
|
236
|
+
puts "Sending next work #{work_item.inspect} to Ractor: #{wrapped_ractor.name}" if ENV["FRACTOR_DEBUG"]
|
244
237
|
wrapped_ractor.send(work_item) # Send the Work object
|
245
|
-
if ENV["FRACTOR_DEBUG"]
|
246
|
-
puts "Work sent to #{wrapped_ractor.name}."
|
247
|
-
end
|
238
|
+
puts "Work sent to #{wrapped_ractor.name}." if ENV["FRACTOR_DEBUG"]
|
248
239
|
else
|
249
|
-
if ENV["FRACTOR_DEBUG"]
|
250
|
-
puts "Work queue empty. Not sending new work to Ractor #{wrapped_ractor.name}."
|
251
|
-
end
|
240
|
+
puts "Work queue empty. Not sending new work to Ractor #{wrapped_ractor.name}." if ENV["FRACTOR_DEBUG"]
|
252
241
|
# In continuous mode, don't close workers as more work may come
|
253
242
|
unless @continuous_mode
|
254
243
|
# Consider closing the Ractor if the queue is empty and no more work is expected.
|
@@ -260,9 +249,7 @@ module Fractor
|
|
260
249
|
end
|
261
250
|
end
|
262
251
|
else
|
263
|
-
if ENV["FRACTOR_DEBUG"]
|
264
|
-
puts "Attempted to send work to an invalid or closed Ractor: #{wrapped_ractor&.name || "unknown"}."
|
265
|
-
end
|
252
|
+
puts "Attempted to send work to an invalid or closed Ractor: #{wrapped_ractor&.name || "unknown"}." if ENV["FRACTOR_DEBUG"]
|
266
253
|
# Remove from map if found but closed
|
267
254
|
@ractors_map.delete(wrapped_ractor.ractor) if wrapped_ractor && @ractors_map.key?(wrapped_ractor.ractor)
|
268
255
|
end
|
data/lib/fractor/version.rb
CHANGED
data/lib/fractor/worker.rb
CHANGED
@@ -4,6 +4,11 @@ module Fractor
|
|
4
4
|
# Base class for defining work processors.
|
5
5
|
# Subclasses must implement the `process` method.
|
6
6
|
class Worker
|
7
|
+
def initialize(name: nil, **options)
|
8
|
+
@name = name
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
7
12
|
def process(work)
|
8
13
|
raise NotImplementedError, "Subclasses must implement the 'process' method."
|
9
14
|
end
|
@@ -9,7 +9,7 @@ module Fractor
|
|
9
9
|
# Initializes the WrappedRactor with a name and the Worker class to instantiate.
|
10
10
|
# The worker_class parameter allows flexibility in specifying the worker type.
|
11
11
|
def initialize(name, worker_class)
|
12
|
-
puts "Creating Ractor #{name} with worker #{worker_class}"
|
12
|
+
puts "Creating Ractor #{name} with worker #{worker_class}" if ENV["FRACTOR_DEBUG"]
|
13
13
|
@name = name
|
14
14
|
@worker_class = worker_class # Store the worker class
|
15
15
|
@ractor = nil # Initialize ractor as nil
|
@@ -17,31 +17,38 @@ module Fractor
|
|
17
17
|
|
18
18
|
# Starts the underlying Ractor.
|
19
19
|
def start
|
20
|
-
puts "Starting Ractor #{@name}"
|
20
|
+
puts "Starting Ractor #{@name}" if ENV["FRACTOR_DEBUG"]
|
21
21
|
# Pass worker_class to the Ractor block
|
22
22
|
@ractor = Ractor.new(@name, @worker_class) do |name, worker_cls|
|
23
|
-
puts "Ractor #{name} started with worker class #{worker_cls}"
|
23
|
+
puts "Ractor #{name} started with worker class #{worker_cls}" if ENV["FRACTOR_DEBUG"]
|
24
24
|
# Yield an initialization message
|
25
25
|
Ractor.yield({ type: :initialize, processor: name })
|
26
26
|
|
27
27
|
# Instantiate the specific worker inside the Ractor
|
28
|
-
worker = worker_cls.new
|
28
|
+
worker = worker_cls.new(name: name)
|
29
29
|
|
30
30
|
loop do
|
31
31
|
# Ractor.receive will block until a message is received
|
32
|
-
puts "Waiting for work in #{name}"
|
32
|
+
puts "Waiting for work in #{name}" if ENV["FRACTOR_DEBUG"]
|
33
33
|
work = Ractor.receive
|
34
|
-
|
34
|
+
|
35
|
+
# Handle shutdown message
|
36
|
+
if work == :shutdown
|
37
|
+
puts "Received shutdown message in Ractor #{name}, terminating..." if ENV["FRACTOR_DEBUG"]
|
38
|
+
break
|
39
|
+
end
|
40
|
+
|
41
|
+
puts "Received work #{work.inspect} in #{name}" if ENV["FRACTOR_DEBUG"]
|
35
42
|
|
36
43
|
begin
|
37
44
|
# Process the work using the instantiated worker
|
38
45
|
result = worker.process(work)
|
39
|
-
puts "Sending result #{result.inspect} from Ractor #{name}"
|
46
|
+
puts "Sending result #{result.inspect} from Ractor #{name}" if ENV["FRACTOR_DEBUG"]
|
40
47
|
# Yield the result back
|
41
48
|
Ractor.yield({ type: :result, result: result, processor: name })
|
42
49
|
rescue StandardError => e
|
43
50
|
# Handle errors during processing
|
44
|
-
puts "Error processing work #{work.inspect} in Ractor #{name}: #{e.message}\n#{e.backtrace.join("\n")}"
|
51
|
+
puts "Error processing work #{work.inspect} in Ractor #{name}: #{e.message}\n#{e.backtrace.join("\n")}" if ENV["FRACTOR_DEBUG"]
|
45
52
|
# Yield an error message back
|
46
53
|
# Ensure the original work object is included in the error result
|
47
54
|
error_result = Fractor::WorkResult.new(error: e.message, work: work)
|
@@ -49,14 +56,14 @@ module Fractor
|
|
49
56
|
end
|
50
57
|
end
|
51
58
|
rescue Ractor::ClosedError
|
52
|
-
puts "Ractor #{name} closed."
|
59
|
+
puts "Ractor #{name} closed." if ENV["FRACTOR_DEBUG"]
|
53
60
|
rescue StandardError => e
|
54
|
-
puts "Unexpected error in Ractor #{name}: #{e.message}\n#{e.backtrace.join("\n")}"
|
61
|
+
puts "Unexpected error in Ractor #{name}: #{e.message}\n#{e.backtrace.join("\n")}" if ENV["FRACTOR_DEBUG"]
|
55
62
|
# Optionally yield a critical error message if needed
|
56
63
|
ensure
|
57
|
-
puts "Ractor #{name} shutting down."
|
64
|
+
puts "Ractor #{name} shutting down." if ENV["FRACTOR_DEBUG"]
|
58
65
|
end
|
59
|
-
puts "Ractor #{@name} instance created: #{@ractor}"
|
66
|
+
puts "Ractor #{@name} instance created: #{@ractor}" if ENV["FRACTOR_DEBUG"]
|
60
67
|
end
|
61
68
|
|
62
69
|
# Sends work to the Ractor if it's active.
|
@@ -66,11 +73,11 @@ module Fractor
|
|
66
73
|
@ractor.send(work)
|
67
74
|
true
|
68
75
|
rescue Exception => e
|
69
|
-
puts "Warning: Error sending work to Ractor #{@name}: #{e.message}"
|
76
|
+
puts "Warning: Error sending work to Ractor #{@name}: #{e.message}" if ENV["FRACTOR_DEBUG"]
|
70
77
|
false
|
71
78
|
end
|
72
79
|
else
|
73
|
-
puts "Warning: Attempted to send work to nil Ractor #{@name}"
|
80
|
+
puts "Warning: Attempted to send work to nil Ractor #{@name}" if ENV["FRACTOR_DEBUG"]
|
74
81
|
false
|
75
82
|
end
|
76
83
|
end
|
@@ -108,7 +115,7 @@ module Fractor
|
|
108
115
|
|
109
116
|
true
|
110
117
|
rescue Exception => e
|
111
|
-
puts "Warning: Error closing Ractor #{@name}: #{e.message}"
|
118
|
+
puts "Warning: Error closing Ractor #{@name}: #{e.message}" if ENV["FRACTOR_DEBUG"]
|
112
119
|
# Consider it closed even if there was an error
|
113
120
|
@ractor = nil
|
114
121
|
true
|
@@ -131,7 +138,7 @@ module Fractor
|
|
131
138
|
false
|
132
139
|
rescue Exception => e
|
133
140
|
# If we get an exception, the Ractor is likely terminated
|
134
|
-
puts "Ractor #{@name} appears to be terminated: #{e.message}"
|
141
|
+
puts "Ractor #{@name} appears to be terminated: #{e.message}" if ENV["FRACTOR_DEBUG"]
|
135
142
|
@ractor = nil
|
136
143
|
true
|
137
144
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fractor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ronald Tse
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-05-
|
11
|
+
date: 2025-05-08 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Fractor is a lightweight Ruby framework designed to simplify the process
|
14
14
|
of distributing computational work across multiple Ractors.
|