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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9d15481939349c5d4ad4f3b09368d0221b690cf06a737007b5f247a20cda6e6
4
- data.tar.gz: 66aca66a7c4b1ac1559a77fa97d20917bd52b3a9cf3c4da04906f2a6295ded75
3
+ metadata.gz: b51bfbe1d8aac8575dcd85deb88b286b8f3b26e391a228e01d43f3a466d91f26
4
+ data.tar.gz: '08198a0b664d2ddfa9cbef0a173f1672f9584dba1700f82374b0b9916cad91a6'
5
5
  SHA512:
6
- metadata.gz: 205fbcb518ea078314f5964e3e60a61212c95e3f2959bb3420e6a060bb17f8bbcef0fbd7a74e27ec5304718ead2c8defd41591f4707757f7ce25bcb22374d0c0
7
- data.tar.gz: c66a1af867247746dfd5d63bd30348ae0cf18bb13186b549ccd1380eb3829dd4aa662f4dc359a9479d7666f418cb0e586e6d0b0caf0b74c45f17f29282048326
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-06 11:12:48 UTC using RuboCop version 1.75.5.
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: 15
38
+ # Offense count: 18
34
39
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
35
40
  Metrics/AbcSize:
36
- Max: 83
41
+ Max: 98
37
42
 
38
- # Offense count: 8
43
+ # Offense count: 9
39
44
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
40
45
  # AllowedMethods: refine
41
46
  Metrics/BlockLength:
42
- Max: 78
47
+ Max: 101
43
48
 
44
49
  # Offense count: 2
45
50
  # Configuration parameters: CountComments, CountAsOne.
46
51
  Metrics/ClassLength:
47
- Max: 155
52
+ Max: 159
48
53
 
49
54
  # Offense count: 3
50
55
  # Configuration parameters: AllowedMethods, AllowedPatterns.
51
56
  Metrics/CyclomaticComplexity:
52
- Max: 25
57
+ Max: 37
53
58
 
54
- # Offense count: 32
59
+ # Offense count: 33
55
60
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
56
61
  Metrics/MethodLength:
57
- Max: 60
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: 25
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: 8
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
 
@@ -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
- # Need access to @workers within the trap block
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
- puts "Attempting to close worker Ractors..."
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.close # Use the close method of WrappedRactor
100
- puts "Closed Ractor: #{w.name}"
105
+ w.send(:shutdown)
106
+ puts "Sent shutdown to Ractor: #{w.name}" if ENV["FRACTOR_DEBUG"]
101
107
  rescue StandardError => e
102
- puts "Error closing Ractor #{w.name}: #{e.message}"
108
+ puts "Error sending shutdown to Ractor #{w.name}: #{e.message}" if ENV["FRACTOR_DEBUG"]
103
109
  end
104
- puts "Exiting now."
105
- exit(1) # Exit immediately
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Fractor
4
4
  # Fractor version
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.3"
6
6
  end
@@ -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
- puts "Received work #{work.inspect} in #{name}"
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.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-07 00:00:00.000000000 Z
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.