tobox 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -5
- data/README.md +115 -12
- data/lib/tobox/application.rb +5 -4
- data/lib/tobox/configuration.rb +13 -5
- data/lib/tobox/fetcher.rb +29 -5
- data/lib/tobox/plugins/datadog/configuration.rb +1 -0
- data/lib/tobox/plugins/datadog.rb +6 -2
- data/lib/tobox/plugins/sentry.rb +4 -0
- data/lib/tobox/pool/fiber_pool.rb +1 -11
- data/lib/tobox/pool/threaded_pool.rb +33 -18
- data/lib/tobox/pool.rb +23 -1
- data/lib/tobox/version.rb +1 -1
- data/lib/tobox/worker.rb +7 -0
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ecf0e9576a8a59834eb6b84eed5c648a2fda3e50b4be49fba068f2bcfc785978
|
4
|
+
data.tar.gz: fb8dc2819591dfe63a177dc1f27b9a947712db00bf451a8f3633d305ee12172b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b0d7eae10175ce51332e32ad89613196f7b56d01f54670bd78a36af0d411081fda20af93f7c2eee0fd032da84f7c098ad662b6ce00e01b8ab603b6f1f06cc4f
|
7
|
+
data.tar.gz: 6db739a849af63cee915970ea1abc7df4a9d4aff91edb4c76cc4c78273e7231750771638ff0dc477bbb586fc24f0dfab1de793d875cd22bb395c5ff1d3772641
|
data/CHANGELOG.md
CHANGED
@@ -1,30 +1,72 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
-
## [0.
|
3
|
+
## [0.2.0] - 2022-12-05
|
4
|
+
|
5
|
+
### Features
|
6
|
+
|
7
|
+
#### Ordered event processing
|
8
|
+
|
9
|
+
When the outbox table contains a `:group_id` table (and the producer fills up events with it), then a group of events with the same `:group_id` will be processed one by one, by order of insertion.
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
# migration
|
13
|
+
create_table(:outbox) do
|
14
|
+
column :message_group_id, :integer
|
15
|
+
|
16
|
+
# tobox.rb
|
17
|
+
message_group_column :group_id
|
18
|
+
|
19
|
+
# event production
|
20
|
+
DB[:outbox].insert(event_type: "order_created", message_group_id: order.id, ....
|
21
|
+
DB[:outbox].insert(event_type: "billing_event_started", message_group_id: order.id, ....
|
22
|
+
|
23
|
+
# order_created handled first, billing_event_started only after
|
24
|
+
```
|
25
|
+
|
26
|
+
#### on_error_worker callback
|
27
|
+
|
28
|
+
The config option `on_error_worker { |error| }` gets called when an error happens in a worker **before** events are processed (p.ex. when the database connection becomes unhealthy). You can use it to report such errors to an error reporting system (the `sentry` plugin relies on it).
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
# tobox.rb
|
32
|
+
on_error_worker { |error| Sentry.capture_exception(error, hint: { background: false }) }
|
33
|
+
```
|
34
|
+
|
35
|
+
### Bugfixes
|
36
|
+
|
37
|
+
Thread workers: when errors happen which bring down the workers (such as database becoming unresponsive), workers will be restarted.
|
38
|
+
|
39
|
+
## [0.1.6] - 2022-10-06
|
40
|
+
|
41
|
+
### Bugfixes
|
42
|
+
|
43
|
+
Allow passing datadog options, initialize tracing from plugin.
|
44
|
+
|
45
|
+
## [0.1.5] - 2022-10-06
|
4
46
|
|
5
47
|
### Bugfixes
|
6
48
|
|
7
49
|
Fixing datadog plugin name.
|
8
50
|
|
9
|
-
## [0.1.4] -
|
51
|
+
## [0.1.4] - 2022-10-06
|
10
52
|
|
11
53
|
### Bugfixes
|
12
54
|
|
13
55
|
Actual fix for missing datadog constants.
|
14
56
|
|
15
|
-
## [0.1.3] -
|
57
|
+
## [0.1.3] - 2022-10-06
|
16
58
|
|
17
59
|
### Bugfixes
|
18
60
|
|
19
61
|
Datadog constants unproperly namespaced.
|
20
62
|
|
21
|
-
## [0.1.2] -
|
63
|
+
## [0.1.2] - 2022-09-14
|
22
64
|
|
23
65
|
### Bugfixes
|
24
66
|
|
25
67
|
Actual fix for foregoing json parsing.
|
26
68
|
|
27
|
-
## [0.1.1] -
|
69
|
+
## [0.1.1] - 2022-09-14
|
28
70
|
|
29
71
|
### Chore
|
30
72
|
|
data/README.md
CHANGED
@@ -1,11 +1,33 @@
|
|
1
1
|
# Tobox: Transactional outbox pattern implementation in ruby
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/tobox.svg)](http://rubygems.org/gems/tobox)
|
4
|
-
[![pipeline status](https://gitlab.com/
|
5
|
-
[![coverage report](https://gitlab.com/
|
4
|
+
[![pipeline status](https://gitlab.com/os85/tobox/badges/master/pipeline.svg)](https://gitlab.com/os85/tobox/pipelines?page=1&scope=all&ref=master)
|
5
|
+
[![coverage report](https://gitlab.com/os85/tobox/badges/master/coverage.svg?job=coverage)](https://os85.gitlab.io/tobox/#_AllFiles)
|
6
6
|
|
7
7
|
Simple, data-first events processing framework based on the [transactional outbox pattern](https://microservices.io/patterns/data/transactional-outbox.html).
|
8
8
|
|
9
|
+
<!-- TOC -->
|
10
|
+
|
11
|
+
- [Requirements](#requirements)
|
12
|
+
- [Installation](#installation)
|
13
|
+
- [Usage](#usage)
|
14
|
+
- [Configuration](#configuration)
|
15
|
+
- [Event](#event)
|
16
|
+
- [Features](#features)
|
17
|
+
- [Ordered event processing](#ordered-event-processing)
|
18
|
+
- [Plugins](#plugins)
|
19
|
+
- [Zeitwerk](#zeitwerk)
|
20
|
+
- [Sentry](#sentry)
|
21
|
+
- [Datadog](#datadog)
|
22
|
+
- [Supported Rubies](#supported-rubies)
|
23
|
+
- [Rails support](#rails-support)
|
24
|
+
- [Why?](#why)
|
25
|
+
- [Development](#development)
|
26
|
+
- [Contributing](#contributing)
|
27
|
+
|
28
|
+
<!-- /TOC -->
|
29
|
+
|
30
|
+
<a id="markdown-requirements" name="requirements"></a>
|
9
31
|
## Requirements
|
10
32
|
|
11
33
|
`tobox` requires integration with RDBMS which supports `SKIP LOCKED` functionality. As of today, that's:
|
@@ -15,6 +37,7 @@ Simple, data-first events processing framework based on the [transactional outbo
|
|
15
37
|
* Oracle
|
16
38
|
* Microsoft SQL Server
|
17
39
|
|
40
|
+
<a id="markdown-installation" name="installation"></a>
|
18
41
|
## Installation
|
19
42
|
|
20
43
|
Add this line to your application's Gemfile:
|
@@ -22,7 +45,7 @@ Add this line to your application's Gemfile:
|
|
22
45
|
```ruby
|
23
46
|
gem "tobox"
|
24
47
|
|
25
|
-
# You'll also need to
|
48
|
+
# You'll also need to add the right database client gem for the target RDBMS
|
26
49
|
# ex, for postgresql:
|
27
50
|
#
|
28
51
|
# gem "pg"
|
@@ -37,6 +60,8 @@ Or install it yourself as:
|
|
37
60
|
|
38
61
|
$ gem install tobox
|
39
62
|
|
63
|
+
|
64
|
+
<a id="markdown-usage" name="usage"></a>
|
40
65
|
## Usage
|
41
66
|
|
42
67
|
1. create the `outbox` table in your application's database:
|
@@ -80,11 +105,14 @@ end
|
|
80
105
|
on("user_updated") do |event|
|
81
106
|
# ...
|
82
107
|
end
|
108
|
+
on("user_created", "user_updated") do |event|
|
109
|
+
# ...
|
110
|
+
end
|
83
111
|
```
|
84
112
|
|
85
113
|
3. Start the `tobox` process
|
86
114
|
|
87
|
-
```
|
115
|
+
```bash
|
88
116
|
> bundle exec tobox -C path/to/tobox.rb -r path/to/file_requiring_application_code.rb
|
89
117
|
```
|
90
118
|
|
@@ -135,11 +163,12 @@ CREATE TRIGGER order_created_outbox_event
|
|
135
163
|
EXECUTE PROCEDURE order_created_outbox_event();
|
136
164
|
```
|
137
165
|
|
166
|
+
<a id="markdown-configuration" name="configuration"></a>
|
138
167
|
## Configuration
|
139
168
|
|
140
169
|
As mentioned above, configuration can be set in a particular file. The following options are configurable:
|
141
170
|
|
142
|
-
### `environment
|
171
|
+
### `environment`
|
143
172
|
|
144
173
|
Sets the application environment (either "development" or "production"). Can be set directly, or via `APP_ENV` environment variable (defaults to "development").
|
145
174
|
|
@@ -228,6 +257,15 @@ callback executed when an exception was raised while processing an event.
|
|
228
257
|
on_error_event { |event, exception| Sentry.capture_exception(exception) }
|
229
258
|
```
|
230
259
|
|
260
|
+
### `on_error_worker { |error| }`
|
261
|
+
|
262
|
+
callback executed when an exception was raised in the worker, before processing events.
|
263
|
+
|
264
|
+
|
265
|
+
```ruby
|
266
|
+
on_error_worker { |exception| Sentry.capture_exception(exception) }
|
267
|
+
```
|
268
|
+
|
231
269
|
### `message_to_arguments { |event| }`
|
232
270
|
|
233
271
|
if exposing raw data to the `on` handlers is not what you'd want, you can always override the behaviour by providing an alternative "before/after fetcher" implementation.
|
@@ -257,6 +295,11 @@ Overrides the internal logger (an instance of `Logger`).
|
|
257
295
|
|
258
296
|
Overrides the default log level ("info" when in "production" environment, "debug" otherwise).
|
259
297
|
|
298
|
+
### group_column
|
299
|
+
|
300
|
+
Defines the column to be used for event grouping, when [ordered processing of events is a requirement](#ordered-event-processing).
|
301
|
+
|
302
|
+
<a id="markdown-event" name="event"></a>
|
260
303
|
## Event
|
261
304
|
|
262
305
|
The event is composed of the following properties:
|
@@ -269,20 +312,57 @@ The event is composed of the following properties:
|
|
269
312
|
|
270
313
|
(*NOTE*: The event is also composed of other properties which are only relevant for `tobox`.)
|
271
314
|
|
272
|
-
|
315
|
+
<a id="markdown-features" name="features"></a>
|
316
|
+
## Features
|
273
317
|
|
274
|
-
|
318
|
+
There are a few extra features you can run on top a "vanilla" transactional outbox implementation. This is how you can accomplish them using `tobox`.
|
275
319
|
|
276
|
-
|
277
|
-
|
320
|
+
<a id="markdown-ordered-event-processing" name="ordered-event-processing"></a>
|
321
|
+
### Ordered event processing
|
322
|
+
|
323
|
+
By default, events are taken and processed from the "outbox" table concurrently by workers, which means that, while worker A may process the most recent event, and worker B takes the following, worker B may process it faster than worker A. This may be an issue if the consumer expects events from a certain context to arrive in a certain order.
|
324
|
+
|
325
|
+
One solution is to have a single worker processing the "outbox" events. Another is to use the `group_column` configuration.
|
326
|
+
|
327
|
+
What you have to do is:
|
328
|
+
|
329
|
+
1. add a "group id" column to the "outbox" table
|
330
|
+
|
331
|
+
```ruby
|
332
|
+
create_table(:outbox) do
|
333
|
+
primary_key :id
|
334
|
+
column :group_id, :integer
|
335
|
+
# The type is irrelevant, could also be :string, :uuid...
|
336
|
+
# ..
|
278
337
|
```
|
279
338
|
|
280
|
-
|
339
|
+
2. set the "group_column" configuration
|
281
340
|
|
282
341
|
```ruby
|
283
|
-
|
342
|
+
# in your tobox.rb
|
343
|
+
group_column :group_id
|
344
|
+
index :group_id
|
284
345
|
```
|
285
346
|
|
347
|
+
3. insert related outbox events with the same group id
|
348
|
+
|
349
|
+
```ruby
|
350
|
+
order = Order.new(
|
351
|
+
item_id: item.id,
|
352
|
+
price: 20_20,
|
353
|
+
currency: "EUR"
|
354
|
+
)
|
355
|
+
DB.transaction do
|
356
|
+
order.save
|
357
|
+
DB[:outbox].insert(event_type: "order_created", group_id: order.id, data_after: order.to_hash)
|
358
|
+
DB[:outbox].insert(event_type: "billing_event_started", group_id: order.id, data_after: order.to_hash)
|
359
|
+
end
|
360
|
+
|
361
|
+
# "order_created" will be processed first
|
362
|
+
# "billing_event_created" will only start processing once "order_created" finishes
|
363
|
+
```
|
364
|
+
|
365
|
+
<a id="markdown-plugins" name="plugins"></a>
|
286
366
|
## Plugins
|
287
367
|
|
288
368
|
`tobox` ships with a very simple plugin system. (TODO: add docs).
|
@@ -296,6 +376,7 @@ plugin(:plugin_name)
|
|
296
376
|
|
297
377
|
It ships with the following integrations.
|
298
378
|
|
379
|
+
<a id="markdown-zeitwerk" name="zeitwerk"></a>
|
299
380
|
### Zeitwerk
|
300
381
|
|
301
382
|
(requires the `zeitwerk` gem.)
|
@@ -310,6 +391,7 @@ zeitwerk_loader do |loader|
|
|
310
391
|
end
|
311
392
|
```
|
312
393
|
|
394
|
+
<a id="markdown-sentry" name="sentry"></a>
|
313
395
|
### Sentry
|
314
396
|
|
315
397
|
(requires the `sentry-ruby` gem.)
|
@@ -321,6 +403,7 @@ Plugin for the [sentry](https://github.com/getsentry/sentry-ruby) ruby SDK for e
|
|
321
403
|
plugin(:sentry)
|
322
404
|
```
|
323
405
|
|
406
|
+
<a id="markdown-datadog" name="datadog"></a>
|
324
407
|
### Datadog
|
325
408
|
|
326
409
|
(requires the `ddtrace` gem.)
|
@@ -337,10 +420,28 @@ end
|
|
337
420
|
plugin(:datadog)
|
338
421
|
```
|
339
422
|
|
423
|
+
<a id="markdown-supported-rubies" name="supported-rubies"></a>
|
340
424
|
## Supported Rubies
|
341
425
|
|
342
426
|
All Rubies greater or equal to 2.6, and always latest JRuby and Truffleruby.
|
343
427
|
|
428
|
+
|
429
|
+
<a id="markdown-rails-support" name="rails-support"></a>
|
430
|
+
## Rails support
|
431
|
+
|
432
|
+
Rails is supported out of the box by adding the [sequel-activerecord_connection](https://github.com/janko/sequel-activerecord_connection) gem into your Gemfile, and requiring the rails application in the `tobox` cli call:
|
433
|
+
|
434
|
+
```bash
|
435
|
+
> bundle exec tobox -C path/to/tobox.rb -r path/to/rails_app/config/environment.rb
|
436
|
+
```
|
437
|
+
|
438
|
+
In the `tobox` config, you can set the environment:
|
439
|
+
|
440
|
+
```ruby
|
441
|
+
environment Rails.env
|
442
|
+
```
|
443
|
+
|
444
|
+
<a id="markdown-why" name="why"></a>
|
344
445
|
## Why?
|
345
446
|
|
346
447
|
### Simple and lightweight, framework (and programming language) agnostic
|
@@ -375,10 +476,12 @@ By using the database as the message broker, `tobox` can rely on good old transa
|
|
375
476
|
|
376
477
|
(The actual processing may change this to "at least once", as issues may happen before the event is successfully deleted from the outbox. Still, "at least once" is acceptable and solvable using idempotency mechanisms).
|
377
478
|
|
479
|
+
<a id="markdown-development" name="development"></a>
|
378
480
|
## Development
|
379
481
|
|
380
482
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
381
483
|
|
484
|
+
<a id="markdown-contributing" name="contributing"></a>
|
382
485
|
## Contributing
|
383
486
|
|
384
|
-
Bug reports and pull requests are welcome on GitHub at https://gitlab.com/
|
487
|
+
Bug reports and pull requests are welcome on GitHub at https://gitlab.com/os85/tobox.
|
data/lib/tobox/application.rb
CHANGED
@@ -5,10 +5,6 @@ module Tobox
|
|
5
5
|
def initialize(configuration)
|
6
6
|
@configuration = configuration
|
7
7
|
@running = false
|
8
|
-
end
|
9
|
-
|
10
|
-
def start
|
11
|
-
return if @running
|
12
8
|
|
13
9
|
worker = @configuration[:worker]
|
14
10
|
|
@@ -17,7 +13,12 @@ module Tobox
|
|
17
13
|
when :fiber then FiberPool
|
18
14
|
else worker
|
19
15
|
end.new(@configuration)
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
return if @running
|
20
20
|
|
21
|
+
@pool.start
|
21
22
|
@running = true
|
22
23
|
end
|
23
24
|
|
data/lib/tobox/configuration.rb
CHANGED
@@ -17,6 +17,7 @@ module Tobox
|
|
17
17
|
log_level: nil,
|
18
18
|
database_uri: nil,
|
19
19
|
table: :outbox,
|
20
|
+
group_column: nil,
|
20
21
|
max_attempts: 10,
|
21
22
|
exponential_retry_factor: 4,
|
22
23
|
wait_for_events_delay: 5,
|
@@ -60,8 +61,10 @@ module Tobox
|
|
60
61
|
freeze
|
61
62
|
end
|
62
63
|
|
63
|
-
def on(
|
64
|
-
|
64
|
+
def on(*events, &callback)
|
65
|
+
events.each do |event|
|
66
|
+
(@handlers[event.to_sym] ||= []) << callback
|
67
|
+
end
|
65
68
|
self
|
66
69
|
end
|
67
70
|
|
@@ -80,12 +83,17 @@ module Tobox
|
|
80
83
|
self
|
81
84
|
end
|
82
85
|
|
86
|
+
def on_error_worker(&callback)
|
87
|
+
(@lifecycle_events[:error_worker] ||= []) << callback
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
83
91
|
def message_to_arguments(&callback)
|
84
92
|
@arguments_handler = callback
|
85
93
|
self
|
86
94
|
end
|
87
95
|
|
88
|
-
def plugin(plugin,
|
96
|
+
def plugin(plugin, **options, &block)
|
89
97
|
raise Error, "Cannot add a plugin to a frozen config" if frozen?
|
90
98
|
|
91
99
|
plugin = Plugins.load_plugin(plugin) if plugin.is_a?(Symbol)
|
@@ -93,11 +101,11 @@ module Tobox
|
|
93
101
|
return if @plugins.include?(plugin)
|
94
102
|
|
95
103
|
@plugins << plugin
|
96
|
-
plugin.load_dependencies(self, &block) if plugin.respond_to?(:load_dependencies)
|
104
|
+
plugin.load_dependencies(self, **options, &block) if plugin.respond_to?(:load_dependencies)
|
97
105
|
|
98
106
|
extend(plugin::ConfigurationMethods) if defined?(plugin::ConfigurationMethods)
|
99
107
|
|
100
|
-
plugin.configure(self, &block) if plugin.respond_to?(:configure)
|
108
|
+
plugin.configure(self, **options, &block) if plugin.respond_to?(:configure)
|
101
109
|
end
|
102
110
|
|
103
111
|
def freeze
|
data/lib/tobox/fetcher.rb
CHANGED
@@ -19,6 +19,7 @@ module Tobox
|
|
19
19
|
@db.loggers << @logger unless @configuration[:environment] == "production"
|
20
20
|
|
21
21
|
@table = configuration[:table]
|
22
|
+
@group_column = configuration[:group_column]
|
22
23
|
@exponential_retry_factor = configuration[:exponential_retry_factor]
|
23
24
|
|
24
25
|
max_attempts = configuration[:max_attempts]
|
@@ -33,9 +34,6 @@ module Tobox
|
|
33
34
|
@pick_next_sql = @ds.where(Sequel[@table][:attempts] < max_attempts) # filter out exhausted attempts
|
34
35
|
.where(run_at_conds)
|
35
36
|
.order(Sequel.desc(:run_at, nulls: :first), :id)
|
36
|
-
.for_update
|
37
|
-
.skip_locked
|
38
|
-
.limit(1)
|
39
37
|
|
40
38
|
@before_event_handlers = Array(@configuration.lifecycle_events[:before_event])
|
41
39
|
@after_event_handlers = Array(@configuration.lifecycle_events[:after_event])
|
@@ -44,8 +42,34 @@ module Tobox
|
|
44
42
|
|
45
43
|
def fetch_events(&blk)
|
46
44
|
num_events = 0
|
47
|
-
@db.transaction do
|
48
|
-
|
45
|
+
@db.transaction(savepoint: false) do
|
46
|
+
if @group_column
|
47
|
+
group = @pick_next_sql.for_update
|
48
|
+
.skip_locked
|
49
|
+
.limit(1)
|
50
|
+
.select(@group_column)
|
51
|
+
|
52
|
+
# get total from a group, to compare to the number of future locked rows.
|
53
|
+
total_from_group = @ds.where(@group_column => group).count
|
54
|
+
|
55
|
+
event_ids = @ds.where(@group_column => group)
|
56
|
+
.order(Sequel.desc(:run_at, nulls: :first), :id)
|
57
|
+
.for_update.skip_locked.select_map(:id)
|
58
|
+
|
59
|
+
if event_ids.size != total_from_group
|
60
|
+
# this happens if concurrent workers locked different rows from the same group,
|
61
|
+
# or when new rows from a given group have been inserted after the lock has been
|
62
|
+
# acquired
|
63
|
+
event_ids = []
|
64
|
+
end
|
65
|
+
|
66
|
+
# lock all, process 1
|
67
|
+
event_ids = event_ids[0, 1]
|
68
|
+
else
|
69
|
+
event_ids = @pick_next_sql.for_update
|
70
|
+
.skip_locked
|
71
|
+
.limit(1).select_map(:id) # lock starts here
|
72
|
+
end
|
49
73
|
|
50
74
|
events = nil
|
51
75
|
error = nil
|
@@ -88,15 +88,19 @@ module Tobox
|
|
88
88
|
require "uri"
|
89
89
|
end
|
90
90
|
|
91
|
-
def configure(config)
|
91
|
+
def configure(config, **datadog_options, &blk)
|
92
92
|
event_handler = EventHandler.new(config)
|
93
93
|
config.on_before_event(&event_handler.method(:on_start))
|
94
94
|
config.on_after_event(&event_handler.method(:on_finish))
|
95
95
|
config.on_error_event(&event_handler.method(:on_error))
|
96
|
+
::Datadog.configure do |c|
|
97
|
+
c.tracing.instrument :tobox, datadog_options
|
98
|
+
yield(c) if blk
|
99
|
+
end
|
96
100
|
end
|
97
101
|
end
|
98
102
|
end
|
99
103
|
|
100
|
-
register_plugin :
|
104
|
+
register_plugin :datadog, Datadog
|
101
105
|
end
|
102
106
|
end
|
data/lib/tobox/plugins/sentry.rb
CHANGED
@@ -130,6 +130,10 @@ module Tobox
|
|
130
130
|
config.on_after_event(&event_handler.method(:on_finish))
|
131
131
|
config.on_error_event(&event_handler.method(:on_error))
|
132
132
|
|
133
|
+
config.on_error_worker do |error|
|
134
|
+
::Sentry.capture_exception(error, hint: { background: false })
|
135
|
+
end
|
136
|
+
|
133
137
|
::Sentry::Configuration.attr_reader(:tobox)
|
134
138
|
::Sentry::Configuration.add_post_initialization_callback do
|
135
139
|
@tobox = Plugins::Sentry::Configuration.new
|
@@ -5,12 +5,9 @@ require "fiber_scheduler"
|
|
5
5
|
|
6
6
|
module Tobox
|
7
7
|
class FiberPool < Pool
|
8
|
-
class KillError < Interrupt; end
|
9
|
-
|
10
8
|
def initialize(_configuration)
|
11
9
|
Sequel.extension(:fiber_concurrency)
|
12
10
|
super
|
13
|
-
@error_handlers = Array(@configuration.lifecycle_events[:error])
|
14
11
|
end
|
15
12
|
|
16
13
|
def start
|
@@ -19,14 +16,7 @@ module Tobox
|
|
19
16
|
|
20
17
|
FiberScheduler do
|
21
18
|
@workers.each_with_index do |wk, _idx|
|
22
|
-
Fiber.schedule
|
23
|
-
wk.work
|
24
|
-
rescue KillError
|
25
|
-
# noop
|
26
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
27
|
-
@error_handlers.each { |hd| hd.call(:tobox_error, e) }
|
28
|
-
raise e
|
29
|
-
end
|
19
|
+
Fiber.schedule { do_work(wk) }
|
30
20
|
end
|
31
21
|
end
|
32
22
|
end
|
@@ -1,32 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "monitor"
|
4
|
+
|
3
5
|
module Tobox
|
4
6
|
class ThreadedPool < Pool
|
5
|
-
class KillError < Interrupt; end
|
6
|
-
|
7
7
|
def initialize(_configuration)
|
8
|
+
@parent_thread = Thread.main
|
8
9
|
@threads = []
|
10
|
+
@threads.extend(MonitorMixin)
|
9
11
|
super
|
10
|
-
@error_handlers = Array(@configuration.lifecycle_events[:error])
|
11
12
|
end
|
12
13
|
|
13
14
|
def start
|
14
|
-
@workers.
|
15
|
-
th =
|
16
|
-
|
17
|
-
|
18
|
-
begin
|
19
|
-
wk.work
|
20
|
-
rescue KillError
|
21
|
-
# noop
|
22
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
23
|
-
@error_handlers.each { |hd| hd.call(:tobox_error, e) }
|
24
|
-
raise e
|
25
|
-
end
|
26
|
-
|
27
|
-
@threads.delete(Thread.current)
|
15
|
+
@workers.each do |wk|
|
16
|
+
th = start_thread_worker(wk)
|
17
|
+
@threads.synchronize do
|
18
|
+
@threads << th
|
28
19
|
end
|
29
|
-
@threads << th
|
30
20
|
end
|
31
21
|
end
|
32
22
|
|
@@ -51,5 +41,30 @@ module Tobox
|
|
51
41
|
th.value # waits
|
52
42
|
end
|
53
43
|
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def start_thread_worker(wrk)
|
48
|
+
Thread.start(wrk) do |worker|
|
49
|
+
Thread.current.name = worker.label
|
50
|
+
|
51
|
+
do_work(worker)
|
52
|
+
|
53
|
+
@threads.synchronize do
|
54
|
+
@threads.delete(Thread.current)
|
55
|
+
|
56
|
+
if worker.finished? && @running
|
57
|
+
idx = @workers.index(worker)
|
58
|
+
|
59
|
+
subst_worker = Worker.new(worker.label, @configuration)
|
60
|
+
@workers[idx] = subst_worker
|
61
|
+
subst_thread = start_thread_worker(subst_worker)
|
62
|
+
@threads << subst_thread
|
63
|
+
end
|
64
|
+
# all workers went down abruply, we need to kill the process.
|
65
|
+
# @parent_thread.raise(Interrupt) if wk.finished? && @threads.empty? && @running
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
54
69
|
end
|
55
70
|
end
|
data/lib/tobox/pool.rb
CHANGED
@@ -2,17 +2,39 @@
|
|
2
2
|
|
3
3
|
module Tobox
|
4
4
|
class Pool
|
5
|
+
class KillError < Interrupt; end
|
6
|
+
|
5
7
|
def initialize(configuration)
|
6
8
|
@configuration = configuration
|
9
|
+
@logger = @configuration.default_logger
|
7
10
|
@num_workers = configuration[:concurrency]
|
8
11
|
@workers = Array.new(@num_workers) do |idx|
|
9
12
|
Worker.new("tobox-worker-#{idx}", configuration)
|
10
13
|
end
|
11
|
-
|
14
|
+
@worker_error_handlers = Array(@configuration.lifecycle_events[:error_worker])
|
15
|
+
@running = true
|
12
16
|
end
|
13
17
|
|
14
18
|
def stop
|
19
|
+
return unless @running
|
20
|
+
|
15
21
|
@workers.each(&:finish!)
|
22
|
+
@running = false
|
23
|
+
end
|
24
|
+
|
25
|
+
def do_work(wrk)
|
26
|
+
wrk.work
|
27
|
+
rescue KillError
|
28
|
+
# noop
|
29
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
30
|
+
wrk.finish!
|
31
|
+
@logger.error do
|
32
|
+
"(worker: #{wrk.label}) -> " \
|
33
|
+
"crashed with error\n" \
|
34
|
+
"#{e.class}: #{e.message}\n" \
|
35
|
+
"#{e.backtrace.join("\n")}"
|
36
|
+
end
|
37
|
+
@worker_error_handlers.each { |hd| hd.call(e) }
|
16
38
|
end
|
17
39
|
end
|
18
40
|
|
data/lib/tobox/version.rb
CHANGED
data/lib/tobox/worker.rb
CHANGED
@@ -2,7 +2,10 @@
|
|
2
2
|
|
3
3
|
module Tobox
|
4
4
|
class Worker
|
5
|
+
attr_reader :label
|
6
|
+
|
5
7
|
def initialize(label, configuration)
|
8
|
+
@label = label
|
6
9
|
@wait_for_events_delay = configuration[:wait_for_events_delay]
|
7
10
|
@handlers = configuration.handlers || {}
|
8
11
|
@fetcher = Fetcher.new(label, configuration)
|
@@ -13,6 +16,10 @@ module Tobox
|
|
13
16
|
define_singleton_method(:message_to_arguments, &message_to_arguments)
|
14
17
|
end
|
15
18
|
|
19
|
+
def finished?
|
20
|
+
@finished
|
21
|
+
end
|
22
|
+
|
16
23
|
def finish!
|
17
24
|
@finished = true
|
18
25
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tobox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- HoneyryderChuck
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-12-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -52,15 +52,15 @@ files:
|
|
52
52
|
- lib/tobox/pool/threaded_pool.rb
|
53
53
|
- lib/tobox/version.rb
|
54
54
|
- lib/tobox/worker.rb
|
55
|
-
homepage: https://gitlab.com/
|
55
|
+
homepage: https://gitlab.com/os85/tobox
|
56
56
|
licenses: []
|
57
57
|
metadata:
|
58
|
-
homepage_uri: https://gitlab.com/
|
58
|
+
homepage_uri: https://gitlab.com/os85/tobox
|
59
59
|
allowed_push_host: https://rubygems.org
|
60
|
-
source_code_uri: https://gitlab.com/
|
61
|
-
bug_tracker_uri: https://gitlab.com/
|
62
|
-
documentation_uri: https://gitlab.com/
|
63
|
-
changelog_uri: https://gitlab.com/
|
60
|
+
source_code_uri: https://gitlab.com/os85/tobox
|
61
|
+
bug_tracker_uri: https://gitlab.com/os85/tobox/issues
|
62
|
+
documentation_uri: https://gitlab.com/os85/tobox
|
63
|
+
changelog_uri: https://gitlab.com/os85/tobox/-/blob/master/CHANGELOG.md
|
64
64
|
rubygems_mfa_required: 'true'
|
65
65
|
post_install_message:
|
66
66
|
rdoc_options: []
|