gouda 0.1.3 → 0.1.5
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/.ruby-version +1 -1
- data/CHANGELOG.md +12 -0
- data/README.md +9 -6
- data/gouda.gemspec +4 -4
- data/lib/active_job/queue_adapters/gouda_adapter.rb +7 -5
- data/lib/gouda/adapter.rb +2 -1
- data/lib/gouda/bulk.rb +16 -0
- data/lib/gouda/queue_constraints.rb +25 -21
- data/lib/gouda/railtie.rb +3 -1
- data/lib/gouda/scheduler.rb +6 -0
- data/lib/gouda/version.rb +1 -1
- data/test/gouda/concurrency_extension_test.rb +160 -0
- data/test/gouda/gouda_test.rb +686 -0
- data/test/gouda/scheduler_test.rb +208 -0
- data/test/gouda/seconds_to_start_distribution.csv +280 -0
- data/test/gouda/test_helper.rb +70 -0
- data/test/gouda/worker_test.rb +116 -0
- data/test/gouda/workload_test.rb +67 -0
- data/test/support/assert_helper.rb +51 -0
- metadata +13 -5
@@ -0,0 +1,208 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "gouda/test_helper"
|
4
|
+
|
5
|
+
class GoudaSchedulerTest < ActiveSupport::TestCase
|
6
|
+
include AssertHelper
|
7
|
+
|
8
|
+
setup do
|
9
|
+
Gouda::Workload.delete_all
|
10
|
+
Gouda::JobFuse.delete_all
|
11
|
+
end
|
12
|
+
|
13
|
+
class TestJob < ActiveJob::Base
|
14
|
+
self.queue_adapter = Gouda::Adapter.new
|
15
|
+
|
16
|
+
def perform(regular = "ok", mandatory:, optional: "hidden")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class FailingJob < ActiveJob::Base
|
21
|
+
include Gouda::ActiveJobExtensions::Concurrency
|
22
|
+
self.queue_adapter = Gouda::Adapter.new
|
23
|
+
|
24
|
+
class MegaError < StandardError
|
25
|
+
end
|
26
|
+
|
27
|
+
retry_on StandardError, wait: :polynomially_longer, attempts: 5
|
28
|
+
retry_on Gouda::InterruptError, wait: 0, attempts: 5
|
29
|
+
retry_on MegaError, attempts: 3, wait: 0
|
30
|
+
|
31
|
+
def perform
|
32
|
+
raise MegaError.new "Kaboom!"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
test "keeps re-enqueueing cron jobs after failed job (also with kwargs)" do
|
37
|
+
tab = {
|
38
|
+
second_minutely: {
|
39
|
+
cron: "*/1 * * * * *", # every second
|
40
|
+
class: "GoudaSchedulerTest::FailingJob"
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
assert_nothing_raised do
|
45
|
+
Gouda::Scheduler.build_scheduler_entries_list!(tab)
|
46
|
+
Gouda::Scheduler.upsert_workloads_from_entries_list!
|
47
|
+
end
|
48
|
+
|
49
|
+
assert_equal 1, Gouda::Workload.enqueued.count
|
50
|
+
Gouda.worker_loop(n_threads: 1, check_shutdown: Gouda::TimerShutdownCheck.new(2))
|
51
|
+
|
52
|
+
refute_empty Gouda::Workload.enqueued
|
53
|
+
assert Gouda::Workload.count > 3
|
54
|
+
end
|
55
|
+
|
56
|
+
test "retries do not have a scheduler_key" do
|
57
|
+
tab = {
|
58
|
+
second_minutely: {
|
59
|
+
cron: "*/1 * * * * *", # every second
|
60
|
+
class: "GoudaSchedulerTest::FailingJob"
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
assert_nothing_raised do
|
65
|
+
Gouda::Scheduler.build_scheduler_entries_list!(tab)
|
66
|
+
Gouda::Scheduler.upsert_workloads_from_entries_list!
|
67
|
+
end
|
68
|
+
|
69
|
+
assert_equal 1, Gouda::Workload.enqueued.count
|
70
|
+
assert_equal "second_minutely_*/1 * * * * *_GoudaSchedulerTest::FailingJob", Gouda::Workload.enqueued.first.scheduler_key
|
71
|
+
sleep(2)
|
72
|
+
Gouda::Workload.checkout_and_perform_one(executing_on: "Unit test")
|
73
|
+
|
74
|
+
assert_equal 1, Gouda::Workload.retried.reload.count
|
75
|
+
assert_nil Gouda::Workload.retried.first.scheduler_key
|
76
|
+
assert_equal "enqueued", Gouda::Workload.retried.first.state
|
77
|
+
end
|
78
|
+
|
79
|
+
test "re-inserts the next subsequent job after executing the queued one" do
|
80
|
+
tab = {
|
81
|
+
second_minutely: {
|
82
|
+
cron: "*/1 * * * * *", # every second
|
83
|
+
class: "GoudaSchedulerTest::TestJob",
|
84
|
+
args: ["omg"],
|
85
|
+
kwargs: {mandatory: "WOOHOO", optional: "yeah"},
|
86
|
+
set: {priority: 150}
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
assert_nothing_raised do
|
91
|
+
Gouda::Scheduler.build_scheduler_entries_list!(tab)
|
92
|
+
end
|
93
|
+
|
94
|
+
assert_changes_by(-> { Gouda::Workload.count }, exactly: 1) do
|
95
|
+
3.times do
|
96
|
+
Gouda::Scheduler.upsert_workloads_from_entries_list!
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
job = Gouda::Workload.first
|
101
|
+
assert_equal 1, Gouda::Workload.count
|
102
|
+
|
103
|
+
sleep 1
|
104
|
+
|
105
|
+
assert_equal job, Gouda::Workload.checkout_and_lock_one(executing_on: "test")
|
106
|
+
|
107
|
+
assert_changes_by(-> { Gouda::Workload.count }, exactly: 1) do
|
108
|
+
3.times do
|
109
|
+
Gouda::Scheduler.upsert_workloads_from_entries_list!
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
assert_equal 2, Gouda::Workload.count
|
114
|
+
Gouda::Workload.all.each(&:perform_and_update_state!)
|
115
|
+
assert_equal 0, Gouda::Workload.errored.count
|
116
|
+
workload = Gouda::Workload.find_by(scheduler_key: "second_minutely_*/1 * * * * *_GoudaSchedulerTest::TestJob")
|
117
|
+
assert_equal 150, workload.priority
|
118
|
+
assert_equal ["omg", {
|
119
|
+
"optional" => "yeah",
|
120
|
+
"mandatory" => "WOOHOO",
|
121
|
+
"_aj_ruby2_keywords" => ["mandatory", "optional"]
|
122
|
+
}], workload.serialized_params["arguments"]
|
123
|
+
end
|
124
|
+
|
125
|
+
test "accepts crontab with nil args" do
|
126
|
+
tab = {
|
127
|
+
first_hourly: {
|
128
|
+
cron: "@hourly",
|
129
|
+
class: "GoudaSchedulerTest::TestJob",
|
130
|
+
args: [nil, nil]
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
assert_nothing_raised do
|
135
|
+
Gouda::Scheduler.build_scheduler_entries_list!(tab)
|
136
|
+
end
|
137
|
+
|
138
|
+
assert_changes_by(-> { Gouda::Workload.count }, exactly: 1) do
|
139
|
+
Gouda::Scheduler.upsert_workloads_from_entries_list!
|
140
|
+
end
|
141
|
+
|
142
|
+
assert_equal [nil, nil], Gouda::Workload.first.serialized_params["arguments"]
|
143
|
+
end
|
144
|
+
|
145
|
+
test "is able to accept a crontab" do
|
146
|
+
tab = {
|
147
|
+
first_hourly: {
|
148
|
+
cron: "@hourly",
|
149
|
+
class: "GoudaSchedulerTest::TestJob",
|
150
|
+
args: ["one"],
|
151
|
+
kwargs: {mandatory: "Yeah"}
|
152
|
+
},
|
153
|
+
second_minutely: {
|
154
|
+
cron: "*/1 * * * *",
|
155
|
+
class: "GoudaSchedulerTest::TestJob",
|
156
|
+
args: [6],
|
157
|
+
kwargs: {mandatory: "Yeah", optional: "something"}
|
158
|
+
},
|
159
|
+
third_hourly_with_args_and_kwargs: {
|
160
|
+
cron: "@hourly",
|
161
|
+
class: "GoudaSchedulerTest::TestJob",
|
162
|
+
args: [1],
|
163
|
+
kwargs: {mandatory: "alright"}
|
164
|
+
},
|
165
|
+
interval: {
|
166
|
+
interval_seconds: 250,
|
167
|
+
class: "GoudaSchedulerTest::TestJob",
|
168
|
+
args: [4],
|
169
|
+
kwargs: {mandatory: "tasty"}
|
170
|
+
}
|
171
|
+
}
|
172
|
+
assert_nothing_raised do
|
173
|
+
Gouda::Scheduler.build_scheduler_entries_list!(tab)
|
174
|
+
end
|
175
|
+
|
176
|
+
travel_to Time.utc(2023, 6, 23, 20, 0)
|
177
|
+
assert_changes_by(-> { Gouda::Workload.count }, exactly: 4) do
|
178
|
+
3.times do
|
179
|
+
Gouda::Scheduler.upsert_workloads_from_entries_list!
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
tab[:fifth] = {
|
184
|
+
cron: "@hourly",
|
185
|
+
class: "GoudaSchedulerTest::TestJob",
|
186
|
+
kwargs: {mandatory: "good"}
|
187
|
+
}
|
188
|
+
|
189
|
+
Gouda::Scheduler.build_scheduler_entries_list!(tab)
|
190
|
+
assert_changes_by(-> { Gouda::Workload.count }, exactly: 1) do
|
191
|
+
Gouda::Scheduler.upsert_workloads_from_entries_list!
|
192
|
+
end
|
193
|
+
|
194
|
+
assert tab.delete(:fifth)
|
195
|
+
Gouda::Scheduler.build_scheduler_entries_list!(tab)
|
196
|
+
assert_changes_by(-> { Gouda::Workload.count }, exactly: -1) do
|
197
|
+
Gouda::Scheduler.upsert_workloads_from_entries_list!
|
198
|
+
end
|
199
|
+
|
200
|
+
Gouda::Workload.all.each(&:perform_and_update_state!)
|
201
|
+
assert_equal 0, Gouda::Workload.errored.count
|
202
|
+
assert_equal [6, {
|
203
|
+
"optional" => "something",
|
204
|
+
"mandatory" => "Yeah",
|
205
|
+
"_aj_ruby2_keywords" => ["mandatory", "optional"]
|
206
|
+
}], Gouda::Workload.find_by(scheduler_key: "second_minutely_*/1 * * * *_GoudaSchedulerTest::TestJob").serialized_params["arguments"]
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,280 @@
|
|
1
|
+
"count","seconds_to_execution"
|
2
|
+
1,29146
|
3
|
+
1,28471
|
4
|
+
1,27796
|
5
|
+
1,27121
|
6
|
+
1,26446
|
7
|
+
1,25771
|
8
|
+
1,25096
|
9
|
+
1,24421
|
10
|
+
1,23746
|
11
|
+
1,23071
|
12
|
+
1,22396
|
13
|
+
1,21720
|
14
|
+
1,21045
|
15
|
+
1,20370
|
16
|
+
1,19695
|
17
|
+
1,19020
|
18
|
+
1,18345
|
19
|
+
1,17670
|
20
|
+
1,16995
|
21
|
+
1,16320
|
22
|
+
1,15645
|
23
|
+
1,14970
|
24
|
+
1,14295
|
25
|
+
1,13620
|
26
|
+
1,12945
|
27
|
+
1,12270
|
28
|
+
1,11595
|
29
|
+
1,10920
|
30
|
+
1,10245
|
31
|
+
1,9570
|
32
|
+
1,8895
|
33
|
+
1,8220
|
34
|
+
1,7545
|
35
|
+
1,6870
|
36
|
+
1,6195
|
37
|
+
1,5915
|
38
|
+
1,5752
|
39
|
+
1,5520
|
40
|
+
1,5438
|
41
|
+
1,5190
|
42
|
+
1,5114
|
43
|
+
2,4929
|
44
|
+
1,4845
|
45
|
+
1,4609
|
46
|
+
1,4489
|
47
|
+
1,4373
|
48
|
+
1,4170
|
49
|
+
1,4133
|
50
|
+
1,4106
|
51
|
+
1,4090
|
52
|
+
1,3956
|
53
|
+
1,3945
|
54
|
+
2,3834
|
55
|
+
1,3598
|
56
|
+
1,3495
|
57
|
+
1,3383
|
58
|
+
1,3377
|
59
|
+
1,3341
|
60
|
+
1,3302
|
61
|
+
1,3174
|
62
|
+
1,3139
|
63
|
+
1,2987
|
64
|
+
1,2869
|
65
|
+
1,2861
|
66
|
+
1,2820
|
67
|
+
2,2805
|
68
|
+
1,2328
|
69
|
+
1,2179
|
70
|
+
1,2145
|
71
|
+
1,1982
|
72
|
+
1,1947
|
73
|
+
1,1678
|
74
|
+
1,1673
|
75
|
+
1,1470
|
76
|
+
1,1459
|
77
|
+
1,1441
|
78
|
+
1,1318
|
79
|
+
1,1270
|
80
|
+
1,1170
|
81
|
+
1,1123
|
82
|
+
1,1079
|
83
|
+
1,974
|
84
|
+
1,970
|
85
|
+
1,957
|
86
|
+
1,893
|
87
|
+
1,795
|
88
|
+
1,778
|
89
|
+
1,740
|
90
|
+
1,478
|
91
|
+
1,442
|
92
|
+
1,262
|
93
|
+
1,207
|
94
|
+
4,197
|
95
|
+
1,166
|
96
|
+
1,131
|
97
|
+
2,123
|
98
|
+
1,122
|
99
|
+
3,120
|
100
|
+
2,119
|
101
|
+
1,118
|
102
|
+
1,117
|
103
|
+
1,116
|
104
|
+
1,114
|
105
|
+
3,111
|
106
|
+
1,110
|
107
|
+
2,109
|
108
|
+
1,108
|
109
|
+
2,107
|
110
|
+
5,106
|
111
|
+
3,105
|
112
|
+
3,104
|
113
|
+
4,103
|
114
|
+
1,102
|
115
|
+
1,101
|
116
|
+
1,100
|
117
|
+
3,99
|
118
|
+
1,98
|
119
|
+
2,97
|
120
|
+
2,96
|
121
|
+
2,95
|
122
|
+
2,94
|
123
|
+
1,93
|
124
|
+
1,92
|
125
|
+
4,91
|
126
|
+
1,90
|
127
|
+
2,89
|
128
|
+
2,88
|
129
|
+
1,87
|
130
|
+
1,86
|
131
|
+
2,85
|
132
|
+
3,84
|
133
|
+
2,83
|
134
|
+
1,82
|
135
|
+
2,81
|
136
|
+
1,80
|
137
|
+
3,79
|
138
|
+
1,78
|
139
|
+
5,77
|
140
|
+
1,76
|
141
|
+
3,75
|
142
|
+
2,74
|
143
|
+
1,73
|
144
|
+
3,72
|
145
|
+
5,71
|
146
|
+
1,70
|
147
|
+
3,69
|
148
|
+
4,68
|
149
|
+
3,67
|
150
|
+
2,66
|
151
|
+
1,65
|
152
|
+
3,64
|
153
|
+
2,63
|
154
|
+
2,62
|
155
|
+
2,61
|
156
|
+
1,60
|
157
|
+
2,59
|
158
|
+
1,58
|
159
|
+
1,57
|
160
|
+
3,56
|
161
|
+
1,55
|
162
|
+
8,54
|
163
|
+
2,53
|
164
|
+
2,52
|
165
|
+
2,51
|
166
|
+
1,50
|
167
|
+
2,49
|
168
|
+
2,48
|
169
|
+
2,47
|
170
|
+
3,46
|
171
|
+
3,45
|
172
|
+
4,43
|
173
|
+
3,42
|
174
|
+
4,41
|
175
|
+
2,40
|
176
|
+
4,39
|
177
|
+
2,37
|
178
|
+
2,35
|
179
|
+
4,34
|
180
|
+
2,33
|
181
|
+
4,32
|
182
|
+
4,31
|
183
|
+
1,30
|
184
|
+
2,29
|
185
|
+
2,28
|
186
|
+
5,27
|
187
|
+
1,26
|
188
|
+
1,25
|
189
|
+
4,24
|
190
|
+
4,23
|
191
|
+
1,22
|
192
|
+
3,21
|
193
|
+
2,20
|
194
|
+
2,19
|
195
|
+
3,17
|
196
|
+
2,16
|
197
|
+
3,15
|
198
|
+
3,13
|
199
|
+
1,12
|
200
|
+
2,11
|
201
|
+
5,10
|
202
|
+
3,9
|
203
|
+
4,8
|
204
|
+
4,7
|
205
|
+
4,6
|
206
|
+
3,5
|
207
|
+
2,4
|
208
|
+
3,3
|
209
|
+
3,2
|
210
|
+
2,1
|
211
|
+
3,0
|
212
|
+
2,-2
|
213
|
+
1,-5
|
214
|
+
2,-7
|
215
|
+
1,-8
|
216
|
+
3,-10
|
217
|
+
1,-12
|
218
|
+
3,-13
|
219
|
+
2,-16
|
220
|
+
1,-18
|
221
|
+
1,-21
|
222
|
+
2,-28
|
223
|
+
2,-30
|
224
|
+
2,-35
|
225
|
+
1,-36
|
226
|
+
3,-40
|
227
|
+
1,-41
|
228
|
+
1,-42
|
229
|
+
3,-45
|
230
|
+
1,-46
|
231
|
+
2,-47
|
232
|
+
1,-49
|
233
|
+
3,-50
|
234
|
+
2,-51
|
235
|
+
1,-52
|
236
|
+
2,-54
|
237
|
+
2,-55
|
238
|
+
1,-56
|
239
|
+
2,-57
|
240
|
+
2,-58
|
241
|
+
2,-59
|
242
|
+
2,-60
|
243
|
+
2,-62
|
244
|
+
5,-63
|
245
|
+
2,-64
|
246
|
+
3,-65
|
247
|
+
1,-66
|
248
|
+
2,-68
|
249
|
+
4,-69
|
250
|
+
2,-70
|
251
|
+
1,-71
|
252
|
+
3,-75
|
253
|
+
1,-76
|
254
|
+
1,-77
|
255
|
+
1,-80
|
256
|
+
1,-82
|
257
|
+
2,-83
|
258
|
+
2,-84
|
259
|
+
1,-85
|
260
|
+
1,-86
|
261
|
+
2,-87
|
262
|
+
1,-88
|
263
|
+
1,-89
|
264
|
+
2,-90
|
265
|
+
1,-91
|
266
|
+
2,-92
|
267
|
+
3,-93
|
268
|
+
1,-94
|
269
|
+
4,-95
|
270
|
+
1,-100
|
271
|
+
2,-101
|
272
|
+
2,-102
|
273
|
+
1,-107
|
274
|
+
1,-180
|
275
|
+
1,-270
|
276
|
+
2,-275
|
277
|
+
1,-302
|
278
|
+
2,-308
|
279
|
+
1,-455
|
280
|
+
1,-1129
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
4
|
+
require "debug"
|
5
|
+
require "active_record"
|
6
|
+
require "active_job"
|
7
|
+
require "active_support/test_case"
|
8
|
+
require "minitest/autorun"
|
9
|
+
require "minitest"
|
10
|
+
require "support/assert_helper"
|
11
|
+
require_relative "../../lib/gouda"
|
12
|
+
|
13
|
+
class ActiveSupport::TestCase
|
14
|
+
SEED_DB_NAME = -> { "gouda_tests_%s" % Random.new(Minitest.seed).hex(4) }
|
15
|
+
|
16
|
+
def self.adapter
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :case_random
|
20
|
+
|
21
|
+
setup do
|
22
|
+
create_postgres_database_if_none
|
23
|
+
@adapter || Gouda::Adapter.new
|
24
|
+
@case_random = Random.new(Minitest.seed)
|
25
|
+
Gouda::Railtie.initializers.each(&:run)
|
26
|
+
ActiveJob::Base.logger = nil
|
27
|
+
Gouda.config.logger.level = 4
|
28
|
+
end
|
29
|
+
|
30
|
+
teardown do
|
31
|
+
truncate_test_tables
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_postgres_database_if_none
|
35
|
+
ActiveRecord::Base.establish_connection(adapter: "postgresql", encoding: "unicode", database: SEED_DB_NAME.call)
|
36
|
+
ActiveRecord::Base.connection.execute("SELECT 1 FROM gouda_workloads")
|
37
|
+
rescue ActiveRecord::NoDatabaseError, ActiveRecord::ConnectionNotEstablished
|
38
|
+
create_postgres_database
|
39
|
+
retry
|
40
|
+
rescue ActiveRecord::StatementInvalid
|
41
|
+
ActiveRecord::Schema.define(version: 1) do |via_definer|
|
42
|
+
Gouda.create_tables(via_definer)
|
43
|
+
end
|
44
|
+
retry
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_postgres_database
|
48
|
+
ActiveRecord::Migration.verbose = false
|
49
|
+
ActiveRecord::Base.establish_connection(adapter: "postgresql", database: "postgres")
|
50
|
+
ActiveRecord::Base.connection.create_database(SEED_DB_NAME.call, charset: :unicode)
|
51
|
+
ActiveRecord::Base.connection.close
|
52
|
+
ActiveRecord::Base.establish_connection(adapter: "postgresql", encoding: "unicode", database: SEED_DB_NAME.call)
|
53
|
+
end
|
54
|
+
|
55
|
+
def truncate_test_tables
|
56
|
+
ActiveRecord::Base.connection.execute("TRUNCATE TABLE gouda_workloads")
|
57
|
+
ActiveRecord::Base.connection.execute("TRUNCATE TABLE gouda_job_fuses")
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_create_tables
|
61
|
+
ActiveRecord::Base.transaction do
|
62
|
+
ActiveRecord::Base.connection.execute("DROP TABLE gouda_workloads")
|
63
|
+
ActiveRecord::Base.connection.execute("DROP TABLE gouda_job_fuses")
|
64
|
+
# The adapter has to be in a variable as the schema definition is scoped to the migrator, not self
|
65
|
+
ActiveRecord::Schema.define(version: 1) do |via_definer|
|
66
|
+
Gouda.create_tables(via_definer)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "gouda/test_helper"
|
4
|
+
|
5
|
+
class GoudaWorkerTest < ActiveSupport::TestCase
|
6
|
+
include AssertHelper
|
7
|
+
|
8
|
+
# self.use_transactional_tests = false
|
9
|
+
|
10
|
+
# This is a bit obtuse but we need to be able to compute this value from inside the ActiveJob
|
11
|
+
# and the job won't have access to the test case object. To avoid mistakes, we can put it
|
12
|
+
# in a constant - which is lexically-scoped. We also evaluate it lazily since the Rails
|
13
|
+
# constant is possibly not initialized yet when this code loads.
|
14
|
+
# We need to include the PID in the path, because we might be running
|
15
|
+
# multiple test processes on the same box - and they might start touching
|
16
|
+
# files from each other.
|
17
|
+
PATH_TO_TEST_FILE = -> { File.expand_path(File.join("tmp", "#{Process.pid}-gouda-worker-test-output.bin")) }
|
18
|
+
|
19
|
+
class JobWithEnqueueKey < ActiveJob::Base
|
20
|
+
self.queue_adapter = :gouda
|
21
|
+
|
22
|
+
def enqueue_concurrency_key
|
23
|
+
"zombie"
|
24
|
+
end
|
25
|
+
|
26
|
+
def perform
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class SimpleJob < ActiveJob::Base
|
31
|
+
self.queue_adapter = :gouda
|
32
|
+
|
33
|
+
def perform
|
34
|
+
File.open(PATH_TO_TEST_FILE.call, "a") do |f|
|
35
|
+
f.write("A")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
test "runs workloads from all queues without a queue constraint" do
|
41
|
+
Gouda.in_bulk do
|
42
|
+
6.times { SimpleJob.perform_later }
|
43
|
+
6.times { SimpleJob.set(queue: "urgent").perform_later }
|
44
|
+
end
|
45
|
+
assert_equal 12, Gouda::Workload.where(state: "enqueued").count
|
46
|
+
|
47
|
+
Gouda.worker_loop(n_threads: 1, check_shutdown: Gouda::EmptyQueueShutdownCheck.new)
|
48
|
+
|
49
|
+
# Check that the side effects of the job have been performed - in this case, that
|
50
|
+
# 12 chars have been written into the file. Every job writes a char.
|
51
|
+
assert_equal 12, File.size(PATH_TO_TEST_FILE.call)
|
52
|
+
|
53
|
+
assert_equal 0, Gouda::Workload.where(state: "enqueued").count
|
54
|
+
assert_equal 12, Gouda::Workload.where(state: "finished").count
|
55
|
+
end
|
56
|
+
|
57
|
+
test "does not run workloads destined for a different queue" do
|
58
|
+
only_from_bravo = Gouda.parse_queue_constraint("queue-bravo")
|
59
|
+
bravo_queue_has_no_jobs = Gouda::EmptyQueueShutdownCheck.new(only_from_bravo)
|
60
|
+
|
61
|
+
Gouda.in_bulk do
|
62
|
+
12.times { SimpleJob.set(queue: "queue-alpha").perform_later }
|
63
|
+
end
|
64
|
+
assert_equal 12, Gouda::Workload.where(state: "enqueued").count
|
65
|
+
|
66
|
+
Gouda.worker_loop(n_threads: 1, queue_constraint: only_from_bravo, check_shutdown: bravo_queue_has_no_jobs)
|
67
|
+
|
68
|
+
assert_equal 12, Gouda::Workload.where(state: "enqueued").count
|
69
|
+
assert_equal 0, Gouda::Workload.where(state: "finished").count
|
70
|
+
end
|
71
|
+
|
72
|
+
test "reaps zombie workloads and then executes replacements" do
|
73
|
+
past = 10.minutes.ago
|
74
|
+
2.times do
|
75
|
+
zombie_job = JobWithEnqueueKey.new
|
76
|
+
Gouda::Workload.create!(
|
77
|
+
scheduled_at: past,
|
78
|
+
active_job_id: zombie_job.job_id,
|
79
|
+
execution_started_at: past,
|
80
|
+
last_execution_heartbeat_at: past,
|
81
|
+
queue_name: "default",
|
82
|
+
active_job_class_name: "GoudaWorkerTest::JobWithEnqueueKey",
|
83
|
+
serialized_params: {
|
84
|
+
job_id: zombie_job.job_id,
|
85
|
+
locale: "en",
|
86
|
+
priority: nil,
|
87
|
+
timezone: "UTC",
|
88
|
+
arguments: [],
|
89
|
+
job_class: zombie_job.class.to_s,
|
90
|
+
executions: 0,
|
91
|
+
queue_name: "default",
|
92
|
+
enqueued_at: past,
|
93
|
+
exception_executions: {}
|
94
|
+
},
|
95
|
+
state: "executing",
|
96
|
+
execution_concurrency_key: nil,
|
97
|
+
enqueue_concurrency_key: nil,
|
98
|
+
executing_on: "unit test",
|
99
|
+
position_in_bulk: 0
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
assert_equal 2, Gouda::Workload.where(state: "executing").count
|
104
|
+
|
105
|
+
Gouda.worker_loop(n_threads: 2, check_shutdown: Gouda::TimerShutdownCheck.new(2.0))
|
106
|
+
|
107
|
+
assert_equal 0, Gouda::Workload.where(state: "enqueued").count
|
108
|
+
assert_equal 0, Gouda::Workload.where(state: "executing").count
|
109
|
+
|
110
|
+
# The original 2 workloads got zombie-reaped and marked "finished".
|
111
|
+
# There should have been only 1 workload enqueued as a retry, because due to
|
112
|
+
# the enqueue concurrency key there would only be 1 allowed into the queue. It
|
113
|
+
# then should have executed normally and marked "finished".
|
114
|
+
assert_equal 3, Gouda::Workload.where(state: "finished").count
|
115
|
+
end
|
116
|
+
end
|