ruby_reactor 0.3.1 → 0.4.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-config.json +15 -0
  3. data/.release-please-manifest.json +3 -0
  4. data/.tool-versions +1 -0
  5. data/CHANGELOG.md +13 -0
  6. data/README.md +194 -9
  7. data/lib/ruby_reactor/configuration.rb +18 -1
  8. data/lib/ruby_reactor/context_serializer.rb +10 -1
  9. data/lib/ruby_reactor/dsl/lockable.rb +130 -0
  10. data/lib/ruby_reactor/executor/result_handler.rb +19 -0
  11. data/lib/ruby_reactor/executor/step_executor.rb +5 -0
  12. data/lib/ruby_reactor/executor.rb +145 -2
  13. data/lib/ruby_reactor/lock.rb +92 -0
  14. data/lib/ruby_reactor/map/result_enumerator.rb +4 -3
  15. data/lib/ruby_reactor/period.rb +67 -0
  16. data/lib/ruby_reactor/rate_limit.rb +74 -0
  17. data/lib/ruby_reactor/reactor.rb +1 -0
  18. data/lib/ruby_reactor/rspec/matchers.rb +171 -4
  19. data/lib/ruby_reactor/semaphore.rb +58 -0
  20. data/lib/ruby_reactor/sidekiq_workers/worker.rb +128 -9
  21. data/lib/ruby_reactor/storage/redis_adapter.rb +2 -0
  22. data/lib/ruby_reactor/storage/redis_locking.rb +251 -0
  23. data/lib/ruby_reactor/version.rb +1 -1
  24. data/lib/ruby_reactor.rb +49 -0
  25. metadata +13 -51
  26. data/documentation/DAG.md +0 -457
  27. data/documentation/README.md +0 -123
  28. data/documentation/async_reactors.md +0 -369
  29. data/documentation/composition.md +0 -199
  30. data/documentation/core_concepts.md +0 -662
  31. data/documentation/data_pipelines.md +0 -230
  32. data/documentation/examples/inventory_management.md +0 -749
  33. data/documentation/examples/order_processing.md +0 -365
  34. data/documentation/examples/payment_processing.md +0 -654
  35. data/documentation/getting_started.md +0 -224
  36. data/documentation/images/failed_order_processing.png +0 -0
  37. data/documentation/images/payment_workflow.png +0 -0
  38. data/documentation/interrupts.md +0 -161
  39. data/documentation/retry_configuration.md +0 -357
  40. data/documentation/testing.md +0 -812
  41. data/gui/.gitignore +0 -24
  42. data/gui/README.md +0 -73
  43. data/gui/eslint.config.js +0 -23
  44. data/gui/index.html +0 -13
  45. data/gui/package-lock.json +0 -5925
  46. data/gui/package.json +0 -46
  47. data/gui/postcss.config.js +0 -6
  48. data/gui/public/vite.svg +0 -1
  49. data/gui/src/App.css +0 -42
  50. data/gui/src/App.tsx +0 -51
  51. data/gui/src/assets/react.svg +0 -1
  52. data/gui/src/components/DagVisualizer.tsx +0 -424
  53. data/gui/src/components/Dashboard.tsx +0 -163
  54. data/gui/src/components/ErrorBoundary.tsx +0 -47
  55. data/gui/src/components/ReactorDetail.tsx +0 -135
  56. data/gui/src/components/StepInspector.tsx +0 -492
  57. data/gui/src/components/__tests__/DagVisualizer.test.tsx +0 -140
  58. data/gui/src/components/__tests__/ReactorDetail.test.tsx +0 -111
  59. data/gui/src/components/__tests__/StepInspector.test.tsx +0 -408
  60. data/gui/src/globals.d.ts +0 -7
  61. data/gui/src/index.css +0 -14
  62. data/gui/src/lib/utils.ts +0 -13
  63. data/gui/src/main.tsx +0 -14
  64. data/gui/src/test/setup.ts +0 -11
  65. data/gui/tailwind.config.js +0 -11
  66. data/gui/tsconfig.app.json +0 -28
  67. data/gui/tsconfig.json +0 -7
  68. data/gui/tsconfig.node.json +0 -26
  69. data/gui/vite.config.ts +0 -8
  70. data/gui/vitest.config.ts +0 -13
@@ -1,357 +0,0 @@
1
- # Retry Configuration
2
-
3
- RubyReactor provides flexible, non-blocking retry mechanisms that requeue jobs instead of blocking worker threads. Retry policies can be configured at both reactor and step levels.
4
-
5
- ## Overview
6
-
7
- The retry system offers:
8
-
9
- - **Non-blocking retries on Async**: Jobs are requeued with calculated delays
10
- - **Multiple backoff strategies**: Exponential, linear, and fixed delays
11
- - **Step-level control**: Different retry policies for different steps
12
- - **Full observability**: Complete visibility into retry attempts
13
-
14
- ### Retry Flow Architecture
15
-
16
- ```mermaid
17
- graph TD
18
- A[Step Execution] --> B{Step<br/>Succeeds?}
19
- B -->|Yes| C[Continue to Next Step]
20
- B -->|No| D{Can Retry?<br/>attempts < max_attempts}
21
- D -->|No| E[Final Failure<br/>Run Compensation]
22
- D -->|Yes| F[Calculate Backoff Delay<br/>exponential/linear/fixed]
23
- F --> G[Serialize Context<br/>with Retry State]
24
- G --> H[Queue Job for Retry<br/>with Calculated Delay]
25
- H --> I[Worker Freed<br/>No Thread Blocking]
26
- I --> J[Delay Elapses]
27
- J --> K[Worker Picks Up<br/>Retry Job]
28
- K --> L[Deserialize Context]
29
- L --> M[Resume Execution<br/>from Failed Step]
30
- M --> A
31
- ```
32
-
33
- ## Basic Retry Configuration
34
-
35
- ### Step-Level Retry
36
-
37
- ```ruby
38
- class PaymentReactor < RubyReactor::Reactor
39
- async true
40
-
41
- step :charge_card do
42
- retries max_attempts: 3, backoff: :exponential, base_delay: 5.seconds
43
- run { PaymentService.charge(card_token, amount) }
44
- end
45
- end
46
- ```
47
-
48
- ### Reactor-Level Defaults
49
-
50
- ```ruby
51
- class PaymentReactor < RubyReactor::Reactor
52
- async true
53
-
54
- # All steps inherit these defaults
55
- retry_defaults max_attempts: 3, backoff: :exponential, base_delay: 2.seconds
56
-
57
- step :validate_card do
58
- # Uses reactor defaults
59
- run { validate_card_details }
60
- end
61
-
62
- step :charge_card do
63
- # Override for this specific step
64
- retries max_attempts: 5, backoff: :linear, base_delay: 10.seconds
65
- run { PaymentService.charge(card_token, amount) }
66
- end
67
- end
68
- ```
69
-
70
- ## Retry Parameters
71
-
72
- ### max_attempts
73
- Maximum number of execution attempts (including the initial attempt).
74
-
75
- ```ruby
76
- retries max_attempts: 5 # 1 initial + 4 retries = 5 total attempts
77
- ```
78
-
79
- ### backoff
80
- The backoff strategy for calculating delays between retry attempts.
81
-
82
- **Options:**
83
- - `:exponential` (default): Delay doubles with each attempt
84
- - `:linear`: Delay increases linearly
85
- - `:fixed`: Same delay for each attempt
86
-
87
- ### base_delay
88
- The base delay for retry calculations. Can be a number (seconds) or ActiveSupport duration.
89
-
90
- ```ruby
91
- retry base_delay: 5.seconds
92
- retry base_delay: 300 # 5 minutes in seconds
93
- ```
94
-
95
- ## Backoff Strategies
96
-
97
- ### Exponential Backoff
98
-
99
- Delay doubles with each retry attempt. Best for external services that may be temporarily overloaded.
100
-
101
- ```ruby
102
- retries max_attempts: 4, backoff: :exponential, base_delay: 1.second
103
- # Delays: 1s, 2s, 4s (total: 7 seconds)
104
- ```
105
-
106
- **Use cases:**
107
- - API rate limiting
108
- - Temporary service unavailability
109
- - Network timeouts
110
-
111
- ### Linear Backoff
112
-
113
- Delay increases linearly with each attempt. Provides predictable, gradually increasing delays.
114
-
115
- ```ruby
116
- retries max_attempts: 4, backoff: :linear, base_delay: 5.seconds
117
- # Delays: 5s, 10s, 15s (total: 30 seconds)
118
- ```
119
-
120
- **Use cases:**
121
- - Database connection issues
122
- - Resource contention
123
- - Gradual backpressure
124
-
125
- ### Fixed Backoff
126
-
127
- Same delay between each retry attempt. Simplest strategy with predictable timing.
128
-
129
- ```ruby
130
- retries max_attempts: 4, backoff: :fixed, base_delay: 10.seconds
131
- # Delays: 10s, 10s, 10s (total: 30 seconds)
132
- ```
133
-
134
- **Use cases:**
135
- - Simple retry scenarios
136
- - When timing precision matters
137
- - Testing environments
138
-
139
- ## Idempotency
140
-
141
- ### What is Idempotency?
142
-
143
- An operation is idempotent if executing it multiple times produces the same result as executing it once.
144
-
145
- ### Idempotent vs Non-Idempotent Operations
146
-
147
- **Idempotent operations (safe to retry):**
148
- - Reading data
149
- - Updating records with same values
150
- - Sending notifications (with deduplication)
151
- - Idempotent API calls
152
-
153
- **Non-idempotent operations (unsafe to retry):**
154
- - Creating new records
155
- - Charging payments (without deduplication)
156
- - Sending unique messages
157
- - File system operations
158
-
159
- ### Best Practices
160
-
161
- 1. **Design for idempotency**: Structure operations to be safely retryable
162
- 2. **Use idempotency keys**: For payments, orders, etc.
163
- 3. **Test thoroughly**: Verify retry behavior doesn't cause issues
164
-
165
- ## Advanced Configuration
166
-
167
- ### Complex Retry Scenarios
168
-
169
- ```ruby
170
- class OrderProcessingReactor < RubyReactor::Reactor
171
- async true
172
-
173
- retry_defaults max_attempts: 3, backoff: :exponential, base_delay: 2.seconds
174
-
175
- step :validate_order do
176
- # Quick validation - no retry needed
177
- run { validate_order_exists(order_id) }
178
- end
179
-
180
- step :check_inventory do
181
- # Inventory checks can be retried
182
- retries max_attempts: 5, backoff: :linear, base_delay: 1.second
183
- run { check_inventory_availability(order) }
184
- end
185
-
186
- step :reserve_inventory do
187
- # Inventory reservation - must be idempotent
188
- retries max_attempts: 3, backoff: :fixed, base_delay: 5.seconds
189
- run { InventoryService.reserve_items(order.items) }
190
-
191
- compensate do
192
- # Release reservation on failure
193
- InventoryService.release_reservation(order.items)
194
- end
195
- end
196
-
197
- step :process_payment do
198
- # Payment processing - critical, fewer retries
199
- retries max_attempts: 2, backoff: :exponential, base_delay: 10.seconds
200
- run { PaymentService.charge(order.total, order.card_token) }
201
-
202
- compensate do |payment_id:|
203
- # Refund on failure
204
- PaymentService.refund(payment_id) if payment_id
205
- end
206
- end
207
-
208
- step :confirm_order do
209
- # Final confirmation - must succeed
210
- run { OrderService.mark_completed(order_id) }
211
- end
212
- end
213
- ```
214
-
215
- ### Conditional Retry Logic
216
-
217
- For more complex retry logic, you can implement custom retry handlers:
218
-
219
- ```ruby
220
- class CustomRetryReactor < RubyReactor::Reactor
221
- async true
222
-
223
- step :call_external_api do
224
- retries max_attempts: 5, backoff: :exponential, base_delay: 1.second
225
- run do
226
- result = ExternalAPI.call
227
- # Raise specific errors based on response
228
- case result.status
229
- when 429 # Rate limited
230
- Failure(RateLimitError.new(result) retryable: true)
231
- when 500 # Server error
232
- Failure(ServerError.new(result) retryable: true)
233
- when 400 # Bad request
234
- Failure(ValidationError.new(result) retryable: false)
235
- else
236
- result
237
- end
238
- end
239
- end
240
- end
241
- ```
242
-
243
- ## Monitoring and Observability
244
-
245
- ### Retry Metrics
246
-
247
- Track these important metrics:
248
-
249
- ```ruby
250
- # In your monitoring system
251
- retry_attempt_count(step_name)
252
- retry_success_rate(step_name)
253
- average_retry_delay(step_name)
254
- retry_timeout_count(step_name)
255
- ```
256
-
257
-
258
- ### Sidekiq Web UI
259
-
260
- Retry jobs are visible in Sidekiq web interface with:
261
- - Step name and attempt number
262
- - Failure reason and stack trace
263
- - Scheduled retry time
264
- - Job arguments and context
265
-
266
- ## Performance Considerations
267
-
268
- ### Retry Storm Prevention
269
-
270
- Avoid retry storms by:
271
-
272
- 1. **Reasonable delays**: Don't use very short base delays
273
- 2. **Limited attempts**: Set appropriate max_attempts limits
274
- 3. **Circuit breakers**: Implement circuit breaker patterns for external services
275
- 4. **Rate limiting**: Consider rate limiting at the application level
276
-
277
- ### Resource Usage
278
-
279
- - **Worker threads**: Retries don't block workers, improving utilization
280
- - **Memory**: Context serialization adds memory overhead
281
- - **Redis**: Job storage and queue management
282
- - **Database**: Potential increased load from idempotent operations
283
-
284
- ### Tuning Guidelines
285
-
286
- ```ruby
287
- # Fast-retry scenario (API calls)
288
- retries max_attempts: 3, backoff: :exponential, base_delay: 1.second
289
-
290
- # Slow-retry scenario (batch processing)
291
- retries max_attempts: 5, backoff: :linear, base_delay: 5.minutes
292
-
293
- # Critical operations (payments)
294
- retries max_attempts: 2, backoff: :fixed, base_delay: 30.seconds
295
- ```
296
-
297
- ## Error Types and Handling
298
-
299
- ### Retryable Errors
300
-
301
- ```ruby
302
- class NetworkTimeoutError < StandardError
303
- def retryable?
304
- true
305
- end
306
- end
307
-
308
- class ValidationError < StandardError
309
- def retryable?
310
- false # Don't retry validation errors
311
- end
312
- end
313
- ```
314
-
315
- ## Testing Retry Behavior
316
-
317
- ### Unit Testing
318
-
319
- ```ruby
320
- RSpec.describe PaymentReactor do
321
- it "retries failed payment with exponential backoff" do
322
- allow(PaymentService).to receive(:charge)
323
- .and_raise(NetworkError.new("Timeout"))
324
- .and_raise(NetworkError.new("Timeout"))
325
- .and_return(payment_result)
326
-
327
- expect(PaymentService).to receive(:charge).exactly(3).times
328
-
329
- result = PaymentReactor.run(card_token: "tok_123", amount: 100)
330
-
331
- expect(result).to be_success
332
- expect(result.step_results[:charge_card][:payment_id]).to eq("pay_123")
333
- end
334
- end
335
- ```
336
-
337
- ### Integration Testing
338
-
339
- ```ruby
340
- describe "Retry integration" do
341
- it "handles real Sidekiq retry scenarios" do
342
- # Test with actual Sidekiq worker
343
- Sidekiq::Testing.fake! do
344
- result = FailingReactor.run(input: "test")
345
-
346
- # Verify job was queued for retry
347
- expect(RubyReactor::SidekiqWorkers::Worker.jobs.size).to eq(1)
348
-
349
- # Process the retry
350
- RubyReactor::SidekiqWorkers::Worker.drain
351
-
352
- # Verify final success
353
- expect(result).to be_success
354
- end
355
- end
356
- end
357
- ```