Skip to content

Commit 3aa0a8b

Browse files
committed
feat(rails): initial support for Solid Queue
1 parent 036a44d commit 3aa0a8b

5 files changed

Lines changed: 221 additions & 1 deletion

File tree

sentry-rails/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
/doc/
66
/pkg/
77
/spec/reports/
8-
/spec/dummy/test_rails_app/db*
8+
/spec/dummy/test_rails_app/**/*.sqlite3*
99
/tmp/
1010

1111
# rspec failure tracking

sentry-rails/Gemfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@ gem "rails", "~> #{rails_version}"
3232

3333
if rails_version >= Gem::Version.new("8.1.0")
3434
gem "rspec-rails", "~> 8.0.0"
35+
gem "solid_queue"
3536
gem "sqlite3", "~> 2.1.1", platform: :ruby
3637
elsif rails_version >= Gem::Version.new("8.0.0")
3738
gem "rspec-rails", "~> 8.0.0"
39+
gem "solid_queue"
3840
gem "sqlite3", "~> 2.1.1", platform: :ruby
3941
elsif rails_version >= Gem::Version.new("7.1.0")
4042
gem "psych", "~> 4.0.0"
4143
gem "rspec-rails", "~> 7.0"
44+
gem "solid_queue"
4245
gem "sqlite3", "~> 1.7.3", platform: :ruby
4346
elsif rails_version >= Gem::Version.new("6.1.0")
4447
gem "rspec-rails", "~> 6.0"
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# frozen_string_literal: true
2+
3+
require "spec_helper"
4+
5+
if RAILS_VERSION >= 7.1 && RUBY_VERSION >= "3.1"
6+
require "solid_queue"
7+
8+
RSpec.describe "Sentry + ActiveJob on SolidQueue", type: :job do
9+
include ActiveSupport::Testing::TimeHelpers
10+
include_context "active_job backend harness", adapter: :solid_queue
11+
12+
# Instantiated once. Each SolidQueueAdapter.new registers a
13+
# SolidQueue.on_worker_stop callback at class-load time (mutating
14+
# global SolidQueue state), so creating a fresh adapter per example
15+
# would accumulate callbacks across the run.
16+
SOLID_QUEUE_ADAPTER_FOR_TEST = ::ActiveJob::QueueAdapters::SolidQueueAdapter.new
17+
18+
def queue_adapter_for_test
19+
SOLID_QUEUE_ADAPTER_FOR_TEST
20+
end
21+
22+
def boot_adapter(_adapter)
23+
Sentry::Rails::Test::Application.load_queue_schema
24+
end
25+
26+
def reset_adapter(_adapter)
27+
[
28+
SolidQueue::ReadyExecution,
29+
SolidQueue::ClaimedExecution,
30+
SolidQueue::FailedExecution,
31+
SolidQueue::BlockedExecution,
32+
SolidQueue::ScheduledExecution,
33+
SolidQueue::RecurringExecution,
34+
SolidQueue::Process,
35+
SolidQueue::Job
36+
].each(&:delete_all)
37+
end
38+
39+
def drain(at: nil)
40+
process = SolidQueue::Process.register(
41+
kind: "Worker",
42+
pid: ::Process.pid,
43+
name: "spec-#{SecureRandom.hex(4)}"
44+
)
45+
46+
# Loop until both ready and scheduled tables are empty so that
47+
# retry_on cascades cleanly: a failing perform pushes the job into
48+
# SolidQueue::ScheduledExecution (via enqueue_at), which the next
49+
# iteration promotes to ReadyExecution and claims for execution.
50+
# A single dispatch+claim pass would only observe the first
51+
# attempt.
52+
run = lambda do
53+
loop do
54+
SolidQueue::ScheduledExecution.dispatch_next_batch(100)
55+
ready = SolidQueue::ReadyExecution.claim("*", 100, process.id)
56+
break if ready.empty? && SolidQueue::ScheduledExecution.none?
57+
ready.each(&:perform)
58+
end
59+
end
60+
61+
# Only wrap in travel_to when the caller explicitly asks for a future
62+
# time — otherwise nested travel_to (e.g. from a spec that already
63+
# called `travel`) raises.
64+
at ? travel_to(at, &run) : run.call
65+
end
66+
67+
def last_enqueued_payload
68+
SolidQueue::Job.order(:id).last&.arguments
69+
end
70+
71+
it_behaves_like "a Sentry-instrumented ActiveJob backend"
72+
it_behaves_like "an ActiveJob backend that supports distributed tracing"
73+
end
74+
end

sentry-rails/spec/dummy/test_rails_app/config/application.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ def self.schema_file
4545
@schema_file ||= root_path.join("db/schema.rb")
4646
end
4747

48+
def self.queue_schema_file
49+
@queue_schema_file ||= root_path.join("db/queue_schema.rb")
50+
end
51+
4852
def self.db_path
4953
@db_path ||= root_path.join("db", "db.sqlite3")
5054
end
@@ -77,6 +81,14 @@ def self.load_test_schema
7781
end
7882
end
7983

84+
def self.load_queue_schema
85+
@__queue_schema_loaded__ ||= begin
86+
load_test_schema
87+
require Test::Application.queue_schema_file
88+
true
89+
end
90+
end
91+
8092
# Configure method that sets up base configuration
8193
# This can be inherited and extended by subclasses
8294
def configure
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# frozen_string_literal: true
2+
3+
ActiveRecord::Schema[7.1].define(version: 1) do
4+
create_table "solid_queue_blocked_executions", force: :cascade do |t|
5+
t.bigint "job_id", null: false
6+
t.string "queue_name", null: false
7+
t.integer "priority", default: 0, null: false
8+
t.string "concurrency_key", null: false
9+
t.datetime "expires_at", null: false
10+
t.datetime "created_at", null: false
11+
t.index [ "concurrency_key", "priority", "job_id" ], name: "index_solid_queue_blocked_executions_for_release"
12+
t.index [ "expires_at", "concurrency_key" ], name: "index_solid_queue_blocked_executions_for_maintenance"
13+
t.index [ "job_id" ], name: "index_solid_queue_blocked_executions_on_job_id", unique: true
14+
end
15+
16+
create_table "solid_queue_claimed_executions", force: :cascade do |t|
17+
t.bigint "job_id", null: false
18+
t.bigint "process_id"
19+
t.datetime "created_at", null: false
20+
t.index [ "job_id" ], name: "index_solid_queue_claimed_executions_on_job_id", unique: true
21+
t.index [ "process_id", "job_id" ], name: "index_solid_queue_claimed_executions_on_process_id_and_job_id"
22+
end
23+
24+
create_table "solid_queue_failed_executions", force: :cascade do |t|
25+
t.bigint "job_id", null: false
26+
t.text "error"
27+
t.datetime "created_at", null: false
28+
t.index [ "job_id" ], name: "index_solid_queue_failed_executions_on_job_id", unique: true
29+
end
30+
31+
create_table "solid_queue_jobs", force: :cascade do |t|
32+
t.string "queue_name", null: false
33+
t.string "class_name", null: false
34+
t.text "arguments"
35+
t.integer "priority", default: 0, null: false
36+
t.string "active_job_id"
37+
t.datetime "scheduled_at"
38+
t.datetime "finished_at"
39+
t.string "concurrency_key"
40+
t.datetime "created_at", null: false
41+
t.datetime "updated_at", null: false
42+
t.index [ "active_job_id" ], name: "index_solid_queue_jobs_on_active_job_id"
43+
t.index [ "class_name" ], name: "index_solid_queue_jobs_on_class_name"
44+
t.index [ "finished_at" ], name: "index_solid_queue_jobs_on_finished_at"
45+
t.index [ "queue_name", "finished_at" ], name: "index_solid_queue_jobs_for_filtering"
46+
t.index [ "scheduled_at", "finished_at" ], name: "index_solid_queue_jobs_for_alerting"
47+
end
48+
49+
create_table "solid_queue_pauses", force: :cascade do |t|
50+
t.string "queue_name", null: false
51+
t.datetime "created_at", null: false
52+
t.index [ "queue_name" ], name: "index_solid_queue_pauses_on_queue_name", unique: true
53+
end
54+
55+
create_table "solid_queue_processes", force: :cascade do |t|
56+
t.string "kind", null: false
57+
t.datetime "last_heartbeat_at", null: false
58+
t.bigint "supervisor_id"
59+
t.integer "pid", null: false
60+
t.string "hostname"
61+
t.text "metadata"
62+
t.datetime "created_at", null: false
63+
t.string "name", null: false
64+
t.index [ "last_heartbeat_at" ], name: "index_solid_queue_processes_on_last_heartbeat_at"
65+
t.index [ "name", "supervisor_id" ], name: "index_solid_queue_processes_on_name_and_supervisor_id", unique: true
66+
t.index [ "supervisor_id" ], name: "index_solid_queue_processes_on_supervisor_id"
67+
end
68+
69+
create_table "solid_queue_ready_executions", force: :cascade do |t|
70+
t.bigint "job_id", null: false
71+
t.string "queue_name", null: false
72+
t.integer "priority", default: 0, null: false
73+
t.datetime "created_at", null: false
74+
t.index [ "job_id" ], name: "index_solid_queue_ready_executions_on_job_id", unique: true
75+
t.index [ "priority", "job_id" ], name: "index_solid_queue_poll_all"
76+
t.index [ "queue_name", "priority", "job_id" ], name: "index_solid_queue_poll_by_queue"
77+
end
78+
79+
create_table "solid_queue_recurring_executions", force: :cascade do |t|
80+
t.bigint "job_id", null: false
81+
t.string "task_key", null: false
82+
t.datetime "run_at", null: false
83+
t.datetime "created_at", null: false
84+
t.index [ "job_id" ], name: "index_solid_queue_recurring_executions_on_job_id", unique: true
85+
t.index [ "task_key", "run_at" ], name: "index_solid_queue_recurring_executions_on_task_key_and_run_at", unique: true
86+
end
87+
88+
create_table "solid_queue_recurring_tasks", force: :cascade do |t|
89+
t.string "key", null: false
90+
t.string "schedule", null: false
91+
t.string "command", limit: 2048
92+
t.string "class_name"
93+
t.text "arguments"
94+
t.string "queue_name"
95+
t.integer "priority", default: 0
96+
t.boolean "static", default: true, null: false
97+
t.text "description"
98+
t.datetime "created_at", null: false
99+
t.datetime "updated_at", null: false
100+
t.index [ "key" ], name: "index_solid_queue_recurring_tasks_on_key", unique: true
101+
t.index [ "static" ], name: "index_solid_queue_recurring_tasks_on_static"
102+
end
103+
104+
create_table "solid_queue_scheduled_executions", force: :cascade do |t|
105+
t.bigint "job_id", null: false
106+
t.string "queue_name", null: false
107+
t.integer "priority", default: 0, null: false
108+
t.datetime "scheduled_at", null: false
109+
t.datetime "created_at", null: false
110+
t.index [ "job_id" ], name: "index_solid_queue_scheduled_executions_on_job_id", unique: true
111+
t.index [ "scheduled_at", "priority", "job_id" ], name: "index_solid_queue_dispatch_all"
112+
end
113+
114+
create_table "solid_queue_semaphores", force: :cascade do |t|
115+
t.string "key", null: false
116+
t.integer "value", default: 1, null: false
117+
t.datetime "expires_at", null: false
118+
t.datetime "created_at", null: false
119+
t.datetime "updated_at", null: false
120+
t.index [ "expires_at" ], name: "index_solid_queue_semaphores_on_expires_at"
121+
t.index [ "key", "value" ], name: "index_solid_queue_semaphores_on_key_and_value"
122+
t.index [ "key" ], name: "index_solid_queue_semaphores_on_key", unique: true
123+
end
124+
125+
add_foreign_key "solid_queue_blocked_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
126+
add_foreign_key "solid_queue_claimed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
127+
add_foreign_key "solid_queue_failed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
128+
add_foreign_key "solid_queue_ready_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
129+
add_foreign_key "solid_queue_recurring_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
130+
add_foreign_key "solid_queue_scheduled_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
131+
end

0 commit comments

Comments
 (0)