postburner 1.0.0.pre.12 → 1.0.0.pre.14
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/README.md +177 -82
- data/app/concerns/postburner/insertion.rb +20 -1
- data/app/models/postburner/schedule.rb +69 -0
- data/app/models/postburner/schedule_execution.rb +19 -0
- data/bin/postburner +13 -5
- data/lib/generators/postburner/install/templates/config/postburner.yml +24 -139
- data/lib/postburner/active_job/adapter.rb +98 -17
- data/lib/postburner/configuration.rb +5 -0
- data/lib/postburner/instrumentation.rb +196 -0
- data/lib/postburner/scheduler.rb +74 -20
- data/lib/postburner/strategies/{nice_queue.rb → default_queue.rb} +10 -10
- data/lib/postburner/strategies/{test_queue.rb → inline_test_queue.rb} +10 -7
- data/lib/postburner/strategies/{queue.rb → strict_queue.rb} +12 -12
- data/lib/postburner/strategies/{immediate_test_queue.rb → time_travel_test_queue.rb} +15 -12
- data/lib/postburner/test_helpers.rb +133 -0
- data/lib/postburner/version.rb +1 -1
- data/lib/postburner/worker.rb +26 -12
- data/lib/postburner.rb +51 -49
- metadata +7 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 71c6a81826b32021b0f0083c1771b27423e1b25198d7c00669cbab1150c431e7
|
|
4
|
+
data.tar.gz: 26403dba984c15f4bad94d81f1f5833d6a6bb16c0bb216a236f6bcaf07c0d019
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bc088b5d6e5b36b514562635ee3ed75955dfe809efe981e3ddbdc47c5f5387715233b7eb0eec8b4e78f97cf715202beabb7f8b90df553c605eec058eee462a9c
|
|
7
|
+
data.tar.gz: 40dbfc6f0979226d5c67780ffe31c73700a6a7b7faf97bf8e7fd127a2c889e708db91835bc9389032191ae2fae6460f3cc1b21f28ee17a6a3f00dbc84c31fbea
|
data/README.md
CHANGED
|
@@ -110,8 +110,7 @@ bundle exec rake postburner:work WORKER=default
|
|
|
110
110
|
- [Why Beanstalkd?](#why-beanstalkd)
|
|
111
111
|
- [Beanstalkd Integration](#beanstalkd-integration)
|
|
112
112
|
- [Installation](#installation)
|
|
113
|
-
- [
|
|
114
|
-
- [Web UI](#web-ui)
|
|
113
|
+
- [Web UI - v2 Coming Soon](#web-ui)
|
|
115
114
|
|
|
116
115
|
## Why
|
|
117
116
|
|
|
@@ -249,6 +248,30 @@ Jobs without `Postburner::Beanstalkd` use defaults from `config/postburner.yml`:
|
|
|
249
248
|
- `default_priority: 65536`
|
|
250
249
|
- `default_ttr: 300`
|
|
251
250
|
|
|
251
|
+
#### Configuring Third-Party Jobs
|
|
252
|
+
|
|
253
|
+
For jobs you don't control (e.g., `Turbo::Streams::BroadcastJob`, `ActionMailer::MailDeliveryJob`), use the `enqueue_options` hook:
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
# config/initializers/postburner.rb
|
|
257
|
+
Postburner.configure do |config|
|
|
258
|
+
config.enqueue_options = ->(job) do
|
|
259
|
+
case job.class.name
|
|
260
|
+
when 'Turbo::Streams::BroadcastJob'
|
|
261
|
+
{ priority: 100, ttr: 60 }
|
|
262
|
+
when 'ActionMailer::MailDeliveryJob'
|
|
263
|
+
{ priority: 500, ttr: 120 }
|
|
264
|
+
else
|
|
265
|
+
{} # Use defaults
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Priority cascade:** `job.priority` (from `.set(priority: n)`) > `enqueue_options` hook > class-level > `default_priority`
|
|
272
|
+
|
|
273
|
+
**TTR cascade:** class-level `ttr` > `enqueue_options` hook > `default_ttr`
|
|
274
|
+
|
|
252
275
|
### Tracked Jobs
|
|
253
276
|
|
|
254
277
|
Tracked jobs store full execution details in PostgreSQL, providing comprehensive audit trails (i.e. logging, timing, errors, retry tracking) for critical operations.
|
|
@@ -763,36 +786,42 @@ Postburner uses different strategies to control job execution. These affect `Pos
|
|
|
763
786
|
|
|
764
787
|
| Strategy | When to Use | Behavior | Requires Beanstalkd |
|
|
765
788
|
|----------|-------------|----------|---------------------|
|
|
766
|
-
| **
|
|
767
|
-
| **
|
|
768
|
-
| **
|
|
769
|
-
| **
|
|
770
|
-
| **
|
|
789
|
+
| **DefaultQueue** | Production | Async, gracefully requeues premature jobs | Yes |
|
|
790
|
+
| **StrictQueue** | Production | Async via Beanstalkd, raises error on premature execution | Yes |
|
|
791
|
+
| **NullQueue** | Test (usually) | Jobs created but not inserted into Beanstalkd, manual execution | No |
|
|
792
|
+
| **InlineTestQueue** | Test | Inline execution (error raised for delayed jobs) | No |
|
|
793
|
+
| **TimeTravelTestQueue** | Test | Inline execution, (auto time-travels for delayed jobs) | No |
|
|
771
794
|
|
|
772
795
|
```ruby
|
|
773
796
|
# Switch strategies
|
|
774
|
-
Postburner.
|
|
775
|
-
Postburner.
|
|
776
|
-
Postburner.
|
|
777
|
-
Postburner.
|
|
778
|
-
Postburner.
|
|
797
|
+
Postburner.default_strategy! # Default production (DefaultQueue)
|
|
798
|
+
Postburner.strict_strategy! # Strict production (StrictQueue)
|
|
799
|
+
Postburner.null_strategy! # Deferred, or manual execution
|
|
800
|
+
Postburner.inline_test_strategy! # Testing (InlineTestQueue)
|
|
801
|
+
Postburner.time_travel_test_strategy! # Testing inline but also with time travel
|
|
779
802
|
```
|
|
780
803
|
|
|
781
|
-
|
|
804
|
+
### Adding a Queue Strategy
|
|
782
805
|
|
|
783
|
-
|
|
806
|
+
There are 4 hook methods that are used with inheritance:
|
|
807
|
+
- `insert` - Inserts the job into the queue
|
|
808
|
+
- `handle_perform!` - Handles the job being performed
|
|
809
|
+
- `handle_premature_perform` - Handles jobs executed before their scheduled run_at time
|
|
810
|
+
- `testing` - Returns true if the strategy is for testing
|
|
784
811
|
|
|
785
|
-
|
|
812
|
+
## Testing
|
|
786
813
|
|
|
787
814
|
### Automatic Test Mode
|
|
788
815
|
|
|
789
|
-
In Rails test environments, Postburner automatically uses inline execution:
|
|
816
|
+
In Rails test environments, Postburner automatically uses inline execution via the `InlineTestQueue` strategy:
|
|
790
817
|
|
|
791
818
|
```ruby
|
|
792
819
|
# test/test_helper.rb - automatic!
|
|
793
820
|
Postburner.testing? # => true in tests
|
|
794
821
|
```
|
|
795
822
|
|
|
823
|
+
Postburner provides test-friendly execution modes that don't require Beanstalkd.
|
|
824
|
+
|
|
796
825
|
### Testing Default Jobs (ActiveJob)
|
|
797
826
|
|
|
798
827
|
Use standard ActiveJob test helpers:
|
|
@@ -826,26 +855,47 @@ test "tracked job logs execution" do
|
|
|
826
855
|
end
|
|
827
856
|
```
|
|
828
857
|
|
|
829
|
-
###
|
|
858
|
+
### Switching Queue Strategies
|
|
859
|
+
|
|
860
|
+
For tests requiring specific queue behaviors, use `switch_queue_strategy!` and `restore_queue_strategy!`:
|
|
830
861
|
|
|
831
862
|
```ruby
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
863
|
+
class ScheduleTest < ActiveSupport::TestCase
|
|
864
|
+
def setup
|
|
865
|
+
switch_queue_strategy! Postburner::TimeTravelTestQueue
|
|
866
|
+
end
|
|
835
867
|
|
|
836
|
-
|
|
868
|
+
def teardown
|
|
869
|
+
restore_queue_strategy!
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
test "scheduled job executes" do
|
|
873
|
+
job = MyJob.create!(args: {})
|
|
874
|
+
job.queue!(delay: 1.hour)
|
|
875
|
+
assert job.reload.processed_at # Auto time-travels
|
|
876
|
+
end
|
|
837
877
|
end
|
|
878
|
+
```
|
|
838
879
|
|
|
839
|
-
|
|
840
|
-
job = ProcessPayment.create!(args: { 'payment_id' => 123 })
|
|
880
|
+
**Block form for isolated tests:**
|
|
841
881
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
882
|
+
```ruby
|
|
883
|
+
test "specific strategy for one test" do
|
|
884
|
+
use_queue_strategy Postburner::NullQueue do
|
|
885
|
+
job = MyJob.create!(args: {})
|
|
886
|
+
job.queue!
|
|
887
|
+
assert_nil job.bkid # Not queued to Beanstalkd
|
|
845
888
|
end
|
|
846
889
|
end
|
|
847
890
|
```
|
|
848
891
|
|
|
892
|
+
**Available strategies:**
|
|
893
|
+
- `Postburner::InlineTestQueue` - Default test mode, raises on delayed jobs
|
|
894
|
+
- `Postburner::TimeTravelTestQueue` - Auto time-travel for delayed jobs
|
|
895
|
+
- `Postburner::NullQueue` - Create jobs without queueing
|
|
896
|
+
- `Postburner::StrictQueue` - Production mode with error on premature execution
|
|
897
|
+
- `Postburner::DefaultQueue` - Production mode with graceful requeue
|
|
898
|
+
|
|
849
899
|
## Workers
|
|
850
900
|
|
|
851
901
|
Postburner uses named worker configurations to support different deployment patterns. Each worker can have different fork/thread settings and process different queues, enabling flexible production deployments.
|
|
@@ -965,6 +1015,8 @@ bin/postburner --worker general # Run the 'general' worker
|
|
|
965
1015
|
bin/postburner --worker general --queues default,mailers # Only process specific queues
|
|
966
1016
|
```
|
|
967
1017
|
|
|
1018
|
+
**Note:** Prefer defining separate workers in `config/postburner.yml` rather than using `--queues` overrides. Named workers make your deployment configuration explicit and version-controlled. Use `--queues` only for debugging or temporary overrides.
|
|
1019
|
+
|
|
968
1020
|
**Rake task:**
|
|
969
1021
|
```bash
|
|
970
1022
|
bundle exec rake postburner:work # Auto-select worker
|
|
@@ -1216,6 +1268,15 @@ production: # <- environment config, i.e. defaults
|
|
|
1216
1268
|
- mailers
|
|
1217
1269
|
```
|
|
1218
1270
|
|
|
1271
|
+
**Start workers with:**
|
|
1272
|
+
|
|
1273
|
+
```bash
|
|
1274
|
+
bin/postburner # Auto-select single worker
|
|
1275
|
+
bin/postburner --worker default # Specify worker
|
|
1276
|
+
rake postburner:work # Rake task (auto-select)
|
|
1277
|
+
rake postburner:work WORKER=default
|
|
1278
|
+
```
|
|
1279
|
+
|
|
1219
1280
|
### Queue Names
|
|
1220
1281
|
|
|
1221
1282
|
Postburner automatically prefixes all queue names with `postburner.{env}.` to create Beanstalkd tube names. This namespacing prevents collisions when multiple applications share the same Beanstalkd server.
|
|
@@ -1348,16 +1409,86 @@ end
|
|
|
1348
1409
|
|
|
1349
1410
|
## Instrumentation
|
|
1350
1411
|
|
|
1351
|
-
Postburner emits ActiveSupport::Notifications events following
|
|
1412
|
+
Postburner emits ActiveSupport::Notifications events following Rails conventions. Use these for monitoring, logging, or alerting.
|
|
1413
|
+
|
|
1414
|
+
### Job Events
|
|
1415
|
+
|
|
1416
|
+
| Event | When | Payload Keys |
|
|
1417
|
+
|-------|------|--------------|
|
|
1418
|
+
| `perform_start.job.postburner` | Before job execution begins | `:job`, `:beanstalk_job_id` |
|
|
1419
|
+
| `perform.job.postburner` | Around job execution (includes duration) | `:job`, `:beanstalk_job_id` |
|
|
1420
|
+
| `retry.job.postburner` | When job is retried after error | `:job`, `:beanstalk_job_id`, `:error`, `:wait`, `:attempt` |
|
|
1421
|
+
| `retry_stopped.job.postburner` | When tracked job exhausts retries | `:job`, `:beanstalk_job_id`, `:error` |
|
|
1422
|
+
| `discard.job.postburner` | When default job exhausts retries | `:job`, `:beanstalk_job_id`, `:error` |
|
|
1423
|
+
| `enqueue.job.postburner` | When job is enqueued for immediate execution | `:job` |
|
|
1424
|
+
| `enqueue_at.job.postburner` | When job is enqueued with delay | `:job`, `:scheduled_at` |
|
|
1425
|
+
|
|
1426
|
+
**Job Payload Structure:**
|
|
1427
|
+
|
|
1428
|
+
```ruby
|
|
1429
|
+
{
|
|
1430
|
+
class: "ProcessPayment", # Job class name
|
|
1431
|
+
id: 123, # Postburner job ID (tracked jobs only)
|
|
1432
|
+
job_id: "abc-123", # ActiveJob UUID
|
|
1433
|
+
arguments: { payment_id: 456 }, # Job arguments
|
|
1434
|
+
queue_name: "critical", # Queue name
|
|
1435
|
+
beanstalk_job_id: 789, # Beanstalkd job ID
|
|
1436
|
+
tracked: true # Whether job is tracked in PostgreSQL
|
|
1437
|
+
}
|
|
1438
|
+
```
|
|
1439
|
+
|
|
1440
|
+
### Schedule Events
|
|
1441
|
+
|
|
1442
|
+
| Event | When | Payload Keys |
|
|
1443
|
+
|-------|------|--------------|
|
|
1444
|
+
| `create.schedule.postburner` | When schedule is created | `:schedule` |
|
|
1445
|
+
| `update.schedule.postburner` | When schedule is updated | `:schedule`, `:changes` |
|
|
1446
|
+
| `audit.schedule.postburner` | When scheduler audits a schedule | `:schedule` |
|
|
1447
|
+
|
|
1448
|
+
**Schedule Payload Structure:**
|
|
1352
1449
|
|
|
1353
|
-
|
|
1450
|
+
```ruby
|
|
1451
|
+
{
|
|
1452
|
+
id: 1,
|
|
1453
|
+
name: "daily_cleanup",
|
|
1454
|
+
job_class: "CleanupJob",
|
|
1455
|
+
enabled: true,
|
|
1456
|
+
cron: nil,
|
|
1457
|
+
anchor: "2025-01-01T09:00:00Z",
|
|
1458
|
+
interval: 1,
|
|
1459
|
+
interval_unit: "days"
|
|
1460
|
+
}
|
|
1461
|
+
```
|
|
1462
|
+
|
|
1463
|
+
### Schedule Execution Events
|
|
1354
1464
|
|
|
1355
1465
|
| Event | When | Payload Keys |
|
|
1356
1466
|
|-------|------|--------------|
|
|
1357
|
-
| `
|
|
1358
|
-
| `
|
|
1359
|
-
| `
|
|
1360
|
-
|
|
1467
|
+
| `create.schedule_execution.postburner` | When execution is created | `:schedule`, `:execution` |
|
|
1468
|
+
| `enqueue.schedule_execution.postburner` | When execution is enqueued to Beanstalkd | `:schedule`, `:execution`, `:beanstalk_job_id` |
|
|
1469
|
+
| `skip.schedule_execution.postburner` | When execution is skipped | `:schedule`, `:execution` |
|
|
1470
|
+
|
|
1471
|
+
**Execution Payload Structure:**
|
|
1472
|
+
|
|
1473
|
+
```ruby
|
|
1474
|
+
{
|
|
1475
|
+
id: 42,
|
|
1476
|
+
schedule_id: 1,
|
|
1477
|
+
status: "scheduled",
|
|
1478
|
+
run_at: "2025-01-15T09:00:00Z",
|
|
1479
|
+
next_run_at: "2025-01-16T09:00:00Z",
|
|
1480
|
+
enqueued_at: "2025-01-14T10:00:00Z",
|
|
1481
|
+
beanstalk_job_id: 789,
|
|
1482
|
+
job_id: 123
|
|
1483
|
+
}
|
|
1484
|
+
```
|
|
1485
|
+
|
|
1486
|
+
### Scheduler Events
|
|
1487
|
+
|
|
1488
|
+
| Event | When | Payload Keys |
|
|
1489
|
+
|-------|------|--------------|
|
|
1490
|
+
| `perform_start.scheduler.postburner` | Before scheduler watchdog runs | `:interval` |
|
|
1491
|
+
| `perform.scheduler.postburner` | Around scheduler watchdog (includes summary) | `:interval`, `:lock_acquired`, `:schedules_processed`, `:schedules_failed`, `:executions_created`, `:orphans_enqueued` |
|
|
1361
1492
|
|
|
1362
1493
|
### Subscribing to Events
|
|
1363
1494
|
|
|
@@ -1365,32 +1496,39 @@ Postburner emits ActiveSupport::Notifications events following ActiveJob convent
|
|
|
1365
1496
|
# config/initializers/postburner_instrumentation.rb
|
|
1366
1497
|
|
|
1367
1498
|
# Log all job executions
|
|
1368
|
-
ActiveSupport::Notifications.subscribe('perform.postburner') do |name, start, finish, id, payload|
|
|
1499
|
+
ActiveSupport::Notifications.subscribe('perform.job.postburner') do |name, start, finish, id, payload|
|
|
1369
1500
|
duration = (finish - start) * 1000
|
|
1370
|
-
Rails.logger.info "[Postburner] #{payload[:
|
|
1501
|
+
Rails.logger.info "[Postburner] #{payload[:job][:class]} completed in #{duration.round(2)}ms"
|
|
1371
1502
|
end
|
|
1372
1503
|
|
|
1373
1504
|
# Alert on discarded jobs
|
|
1374
|
-
ActiveSupport::Notifications.subscribe('discard.postburner') do |*args|
|
|
1505
|
+
ActiveSupport::Notifications.subscribe('discard.job.postburner') do |*args|
|
|
1375
1506
|
payload = args.last
|
|
1376
1507
|
Alerting.notify(
|
|
1377
1508
|
"Job discarded after max retries",
|
|
1378
|
-
job_class: payload[:
|
|
1509
|
+
job_class: payload[:job][:class],
|
|
1379
1510
|
error: payload[:error].message
|
|
1380
1511
|
)
|
|
1381
1512
|
end
|
|
1382
1513
|
|
|
1383
1514
|
# Track retry metrics
|
|
1384
|
-
ActiveSupport::Notifications.subscribe('retry.postburner') do |*args|
|
|
1515
|
+
ActiveSupport::Notifications.subscribe('retry.job.postburner') do |*args|
|
|
1385
1516
|
payload = args.last
|
|
1386
1517
|
StatsD.increment('postburner.retry', tags: [
|
|
1387
|
-
"job:#{payload[:
|
|
1518
|
+
"job:#{payload[:job][:class]}",
|
|
1388
1519
|
"attempt:#{payload[:attempt]}"
|
|
1389
1520
|
])
|
|
1390
1521
|
end
|
|
1522
|
+
|
|
1523
|
+
# Monitor scheduler performance
|
|
1524
|
+
ActiveSupport::Notifications.subscribe('perform.scheduler.postburner') do |name, start, finish, id, payload|
|
|
1525
|
+
duration = (finish - start) * 1000
|
|
1526
|
+
Rails.logger.info "[Scheduler] Processed #{payload[:schedules_processed]} schedules, " \
|
|
1527
|
+
"created #{payload[:executions_created]} executions in #{duration.round(2)}ms"
|
|
1528
|
+
end
|
|
1391
1529
|
```
|
|
1392
1530
|
|
|
1393
|
-
**Note:** These events
|
|
1531
|
+
**Note:** These events complement (don't replace) ActiveJob's built-in instrumentation events like `enqueue.active_job` and `perform.active_job`.
|
|
1394
1532
|
|
|
1395
1533
|
## Why Beanstalkd?
|
|
1396
1534
|
|
|
@@ -1936,49 +2074,6 @@ cp config/postburner.yml.example config/postburner.yml
|
|
|
1936
2074
|
|
|
1937
2075
|
Edit `config/postburner.yml` for your environment (see [Configuration](#configuration)).
|
|
1938
2076
|
|
|
1939
|
-
## Deployment
|
|
1940
|
-
|
|
1941
|
-
Some recipies that might be useful or instructive:
|
|
1942
|
-
|
|
1943
|
-
### Docker
|
|
1944
|
-
|
|
1945
|
-
```dockerfile
|
|
1946
|
-
# Dockerfile.worker
|
|
1947
|
-
FROM ruby:3.3
|
|
1948
|
-
|
|
1949
|
-
WORKDIR /app
|
|
1950
|
-
COPY . .
|
|
1951
|
-
RUN bundle install
|
|
1952
|
-
|
|
1953
|
-
CMD ["bundle", "exec", "postburner", "--config", "config/postburner.yml", "--env", "production"]
|
|
1954
|
-
```
|
|
1955
|
-
|
|
1956
|
-
```yaml
|
|
1957
|
-
# docker-compose.yml
|
|
1958
|
-
services:
|
|
1959
|
-
beanstalkd:
|
|
1960
|
-
image: schickling/beanstalkd
|
|
1961
|
-
command: ["-l", "0.0.0.0", "-p", "11300", "-b", "/var/lib/beanstalkd"]
|
|
1962
|
-
ports:
|
|
1963
|
-
- "11300:11300"
|
|
1964
|
-
volumes:
|
|
1965
|
-
- beanstalkd_data:/var/lib/beanstalkd
|
|
1966
|
-
|
|
1967
|
-
worker:
|
|
1968
|
-
build:
|
|
1969
|
-
context: .
|
|
1970
|
-
dockerfile: Dockerfile.worker
|
|
1971
|
-
depends_on:
|
|
1972
|
-
- beanstalkd
|
|
1973
|
-
- postgres
|
|
1974
|
-
environment:
|
|
1975
|
-
BEANSTALK_URL: beanstalk://beanstalkd:11300
|
|
1976
|
-
DATABASE_URL: postgres://postgres@postgres/myapp_production
|
|
1977
|
-
|
|
1978
|
-
volumes:
|
|
1979
|
-
beanstalkd_data:
|
|
1980
|
-
```
|
|
1981
|
-
|
|
1982
2077
|
## Migration from v0.x
|
|
1983
2078
|
|
|
1984
2079
|
Key changes in v1.0:
|
|
@@ -53,6 +53,9 @@ module Postburner
|
|
|
53
53
|
#
|
|
54
54
|
# @example Queue at specific time
|
|
55
55
|
# job.queue!(at: Time.zone.now + 2.days)
|
|
56
|
+
# job.queue!(at: Time.zone.parse('2025-01-15 09:00:00'))
|
|
57
|
+
# job.queue!(at: '2025-01-15 09:00:00'.in_time_zone)
|
|
58
|
+
# job.queue!(at: Time.parse('2025-01-15 09:00:00 EST'))
|
|
56
59
|
#
|
|
57
60
|
# @example Queue with priority
|
|
58
61
|
# job.queue!(pri: 0, delay: 30.minutes)
|
|
@@ -148,6 +151,10 @@ module Postburner
|
|
|
148
151
|
# queueing (Beanstalkd in production, inline execution in test mode).
|
|
149
152
|
# Updates bkid if the strategy returns a Beanstalkd job ID.
|
|
150
153
|
#
|
|
154
|
+
# Instruments with ActiveSupport::Notifications:
|
|
155
|
+
# - enqueue.job.postburner: When job is queued immediately
|
|
156
|
+
# - enqueue_at.job.postburner: When job is queued with delay/at
|
|
157
|
+
#
|
|
151
158
|
# @param options [Hash] Queue options (delay, pri, ttr, etc.)
|
|
152
159
|
#
|
|
153
160
|
# @return [Hash, nil] Queue strategy response
|
|
@@ -156,7 +163,6 @@ module Postburner
|
|
|
156
163
|
#
|
|
157
164
|
def insert!(options={})
|
|
158
165
|
response = Postburner.queue_strategy.insert(self, options)
|
|
159
|
-
#debugger
|
|
160
166
|
|
|
161
167
|
# Response must be a hash with an :id key (value can be nil)
|
|
162
168
|
unless response.is_a?(Hash) && response.key?(:id)
|
|
@@ -165,6 +171,19 @@ module Postburner
|
|
|
165
171
|
|
|
166
172
|
persist_metadata!(bkid: response[:id])
|
|
167
173
|
|
|
174
|
+
# Instrument enqueue event
|
|
175
|
+
job_payload = Postburner::Instrumentation.job_payload_from_model(self, beanstalk_job_id: response[:id])
|
|
176
|
+
if self.run_at.present? && self.run_at > Time.current
|
|
177
|
+
ActiveSupport::Notifications.instrument('enqueue_at.job.postburner', {
|
|
178
|
+
job: job_payload,
|
|
179
|
+
scheduled_at: self.run_at
|
|
180
|
+
})
|
|
181
|
+
else
|
|
182
|
+
ActiveSupport::Notifications.instrument('enqueue.job.postburner', {
|
|
183
|
+
job: job_payload
|
|
184
|
+
})
|
|
185
|
+
end
|
|
186
|
+
|
|
168
187
|
self.log("QUEUED: #{response}") if response
|
|
169
188
|
|
|
170
189
|
response
|
|
@@ -109,10 +109,15 @@ module Postburner
|
|
|
109
109
|
validates :name, presence: true, uniqueness: true
|
|
110
110
|
validates :job_class, presence: true
|
|
111
111
|
validates :timezone, presence: true
|
|
112
|
+
validate :validate_timezone_exists!
|
|
112
113
|
validate :validate_scheduling_mode!
|
|
113
114
|
validate :validate_job_class_exists!
|
|
114
115
|
validate :validate_cron_expression!, if: :cron?
|
|
115
116
|
|
|
117
|
+
# Instrumentation callbacks
|
|
118
|
+
after_create_commit :instrument_create
|
|
119
|
+
after_update_commit :instrument_update
|
|
120
|
+
|
|
116
121
|
# Scopes
|
|
117
122
|
scope :enabled, -> { where(enabled: true) }
|
|
118
123
|
scope :disabled, -> { where(enabled: false) }
|
|
@@ -347,6 +352,9 @@ module Postburner
|
|
|
347
352
|
# delayed queue will hold it until run_at. This ensures all future executions
|
|
348
353
|
# are already queued and ready to go.
|
|
349
354
|
#
|
|
355
|
+
# Instruments with ActiveSupport::Notifications:
|
|
356
|
+
# - create.schedule_execution.postburner: When execution is created
|
|
357
|
+
#
|
|
350
358
|
# @param after [Time, ScheduleExecution, nil] Calculate after this time/execution
|
|
351
359
|
# @return [ScheduleExecution, nil] The created execution, or nil if no more runs
|
|
352
360
|
#
|
|
@@ -357,6 +365,13 @@ module Postburner
|
|
|
357
365
|
return nil if execution.nil?
|
|
358
366
|
|
|
359
367
|
execution.save!
|
|
368
|
+
|
|
369
|
+
# Instrument execution creation
|
|
370
|
+
ActiveSupport::Notifications.instrument('create.schedule_execution.postburner', {
|
|
371
|
+
schedule: Postburner::Instrumentation.schedule_payload(self),
|
|
372
|
+
execution: Postburner::Instrumentation.execution_payload(execution)
|
|
373
|
+
})
|
|
374
|
+
|
|
360
375
|
execution.enqueue!
|
|
361
376
|
execution
|
|
362
377
|
end
|
|
@@ -387,6 +402,24 @@ module Postburner
|
|
|
387
402
|
)
|
|
388
403
|
end
|
|
389
404
|
|
|
405
|
+
# Validate that timezone is a recognized ActiveSupport timezone.
|
|
406
|
+
#
|
|
407
|
+
# Uses Time.find_zone to check if the timezone string resolves to a valid
|
|
408
|
+
# ActiveSupport::TimeZone. Invalid timezones would cause nil pointer errors
|
|
409
|
+
# during schedule calculations.
|
|
410
|
+
#
|
|
411
|
+
# @return [void]
|
|
412
|
+
#
|
|
413
|
+
# @api private
|
|
414
|
+
#
|
|
415
|
+
def validate_timezone_exists!
|
|
416
|
+
return if timezone.blank?
|
|
417
|
+
|
|
418
|
+
if Time.find_zone(timezone).nil?
|
|
419
|
+
errors.add(:timezone, "is not a recognized timezone")
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
390
423
|
# Validate that exactly one scheduling mode is configured.
|
|
391
424
|
#
|
|
392
425
|
# Ensures either anchor+interval+interval_unit OR cron is set, but not both.
|
|
@@ -699,5 +732,41 @@ module Postburner
|
|
|
699
732
|
n += 1
|
|
700
733
|
end
|
|
701
734
|
end
|
|
735
|
+
|
|
736
|
+
# Instrument schedule creation.
|
|
737
|
+
#
|
|
738
|
+
# Emits create.schedule.postburner event with schedule payload and changes.
|
|
739
|
+
#
|
|
740
|
+
# @return [void]
|
|
741
|
+
#
|
|
742
|
+
# @api private
|
|
743
|
+
#
|
|
744
|
+
def instrument_create
|
|
745
|
+
ActiveSupport::Notifications.instrument('create.schedule.postburner', {
|
|
746
|
+
schedule: Postburner::Instrumentation.schedule_payload(self),
|
|
747
|
+
changes: Postburner::Instrumentation.changes_payload(self, exclude: ['last_audit_at'])
|
|
748
|
+
})
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
# Instrument schedule update.
|
|
752
|
+
#
|
|
753
|
+
# Emits update.schedule.postburner event with schedule payload and changes.
|
|
754
|
+
# Excludes last_audit_at from changes to avoid noise from scheduler audits.
|
|
755
|
+
#
|
|
756
|
+
# @return [void]
|
|
757
|
+
#
|
|
758
|
+
# @api private
|
|
759
|
+
#
|
|
760
|
+
def instrument_update
|
|
761
|
+
changes = Postburner::Instrumentation.changes_payload(self, exclude: ['last_audit_at'])
|
|
762
|
+
|
|
763
|
+
# Skip instrumentation if only excluded attributes changed
|
|
764
|
+
return if changes.empty?
|
|
765
|
+
|
|
766
|
+
ActiveSupport::Notifications.instrument('update.schedule.postburner', {
|
|
767
|
+
schedule: Postburner::Instrumentation.schedule_payload(self),
|
|
768
|
+
changes: changes
|
|
769
|
+
})
|
|
770
|
+
end
|
|
702
771
|
end
|
|
703
772
|
end
|
|
@@ -99,6 +99,9 @@ module Postburner
|
|
|
99
99
|
#
|
|
100
100
|
# This method is idempotent - calling it multiple times will only enqueue once.
|
|
101
101
|
#
|
|
102
|
+
# Instruments with ActiveSupport::Notifications:
|
|
103
|
+
# - enqueue.schedule_execution.postburner: When execution is enqueued
|
|
104
|
+
#
|
|
102
105
|
# @return [void]
|
|
103
106
|
#
|
|
104
107
|
# @note Creating the next execution is the scheduler's responsibility via
|
|
@@ -128,6 +131,13 @@ module Postburner
|
|
|
128
131
|
enqueue_default_job!
|
|
129
132
|
end
|
|
130
133
|
end
|
|
134
|
+
|
|
135
|
+
# Instrument enqueue event (after transaction commits)
|
|
136
|
+
ActiveSupport::Notifications.instrument('enqueue.schedule_execution.postburner', {
|
|
137
|
+
schedule: Postburner::Instrumentation.schedule_payload(schedule),
|
|
138
|
+
execution: Postburner::Instrumentation.execution_payload(self.reload),
|
|
139
|
+
beanstalk_job_id: beanstalk_job_id
|
|
140
|
+
})
|
|
131
141
|
end
|
|
132
142
|
|
|
133
143
|
# Skip this execution.
|
|
@@ -138,6 +148,9 @@ module Postburner
|
|
|
138
148
|
# This is a destructive operation - skipped executions cannot be unskipped.
|
|
139
149
|
# A new execution must be created if needed.
|
|
140
150
|
#
|
|
151
|
+
# Instruments with ActiveSupport::Notifications:
|
|
152
|
+
# - skip.schedule_execution.postburner: When execution is skipped
|
|
153
|
+
#
|
|
141
154
|
# @return [Boolean] true if skipped successfully, false if already skipped
|
|
142
155
|
#
|
|
143
156
|
# @example Skip a future execution
|
|
@@ -156,6 +169,12 @@ module Postburner
|
|
|
156
169
|
update!(status: :skipped)
|
|
157
170
|
end
|
|
158
171
|
|
|
172
|
+
# Instrument skip event (after transaction commits)
|
|
173
|
+
ActiveSupport::Notifications.instrument('skip.schedule_execution.postburner', {
|
|
174
|
+
schedule: Postburner::Instrumentation.schedule_payload(schedule),
|
|
175
|
+
execution: Postburner::Instrumentation.execution_payload(self)
|
|
176
|
+
})
|
|
177
|
+
|
|
159
178
|
true
|
|
160
179
|
end
|
|
161
180
|
|
data/bin/postburner
CHANGED
|
@@ -3,14 +3,22 @@
|
|
|
3
3
|
|
|
4
4
|
# Postburner worker executable
|
|
5
5
|
#
|
|
6
|
-
# Loads configuration from YAML and starts the appropriate worker
|
|
6
|
+
# Loads configuration from YAML and starts the appropriate worker.
|
|
7
7
|
#
|
|
8
8
|
# Usage:
|
|
9
|
-
# bin/postburner [
|
|
9
|
+
# bin/postburner [options]
|
|
10
|
+
#
|
|
11
|
+
# Options:
|
|
12
|
+
# -c, --config PATH Path to YAML config (default: config/postburner.yml)
|
|
13
|
+
# -e, --env ENVIRONMENT Environment (default: RAILS_ENV, RACK_ENV, or development)
|
|
14
|
+
# -w, --worker WORKER Worker name (required if multiple workers defined)
|
|
15
|
+
# -q, --queues QUEUES Override configured queues (prefer defining workers)
|
|
10
16
|
#
|
|
11
17
|
# Examples:
|
|
12
18
|
# bin/postburner
|
|
13
|
-
# bin/postburner --
|
|
19
|
+
# bin/postburner --worker default
|
|
20
|
+
# bin/postburner --env production --worker imports
|
|
21
|
+
# bin/postburner --worker default --queues default,mailers
|
|
14
22
|
#
|
|
15
23
|
|
|
16
24
|
require 'optparse'
|
|
@@ -30,7 +38,7 @@ OptionParser.new do |opts|
|
|
|
30
38
|
options[:config] = path
|
|
31
39
|
end
|
|
32
40
|
|
|
33
|
-
opts.on('-e', '--env ENVIRONMENT', 'Environment (default: RAILS_ENV or development)') do |env|
|
|
41
|
+
opts.on('-e', '--env ENVIRONMENT', 'Environment (default: RAILS_ENV, RACK_ENV, or development)') do |env|
|
|
34
42
|
options[:env] = env
|
|
35
43
|
end
|
|
36
44
|
|
|
@@ -38,7 +46,7 @@ OptionParser.new do |opts|
|
|
|
38
46
|
options[:worker] = worker
|
|
39
47
|
end
|
|
40
48
|
|
|
41
|
-
opts.on('-q', '--queues QUEUES', '
|
|
49
|
+
opts.on('-q', '--queues QUEUES', 'Override configured queues, e.g. --queues default,mailers (prefer defining in yaml file, and selecting sets with --worker)') do |queues|
|
|
42
50
|
options[:queues] = queues.split(',').map(&:strip)
|
|
43
51
|
end
|
|
44
52
|
|