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