ci-queue 0.22.1 → 0.24.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2847d2cc56c64030f51a65ef6b9262d04b647b15b6d6ef846cf76adb90715e44
4
- data.tar.gz: bf4f65911c9f39c2e6049abc6006f853f8de1aa444dfe8d5c7237cd698d0a59f
3
+ metadata.gz: 5f51b9eaf62e347a724e6e1fc533c26d504cca7922a4296b750ae2599c7cfa6e
4
+ data.tar.gz: 0b9833d46d95e90d67663a2ff5bea2a8ad17c781b812a9f8b4ec7485bf0789d0
5
5
  SHA512:
6
- metadata.gz: b7c62dd52f53069dd2aad2b51724334e7ea2e8fc2cdc8f19f022b6dfd926bb7323437c3f6d1c4ffe7071316c07cde6b924671be07a2e7b402e2d528becd16acf
7
- data.tar.gz: 056b3a585d95b88479f79448dd204eef72e16635c0c2df5c619ad78be2caf3b97a6e52c15edf09f2a2dcccf80afa3ea0018cfe9361114f6d03914383ab080efb
6
+ metadata.gz: b627008782774ad37ae5dbd5a2db0ae24df1f3fcda1dae9e0c0064f7c8df7890b926734855fd0f35d440e5e03d25441b6b9dc60c48bcaa19b8c5537c5d33c98b
7
+ data.tar.gz: e3d1c41abfe02341deb545fda1e6eebb94826801ca104788d2aa48fab1886e6093acd402ef097f58c6b2bc4f403568386e45d1b8032f1323d4f7cfbf6f7f381b
data/README.md CHANGED
@@ -70,3 +70,9 @@ rspec-queue --queue redis://example.com --timeout 600 --report
70
70
  #### Limitations
71
71
 
72
72
  Because of how `ci-queue` executes the examples, `before(:all)` and `after(:all)` hooks are not supported. `rspec-queue` will explicitly reject them.
73
+
74
+ ## Custom Redis Expiry
75
+
76
+ `ci-queue` expects the Redis server to have an [eviction policy](https://redis.io/docs/manual/eviction/#eviction-policies) of `allkeys-lru`.
77
+
78
+ You can also use `--redis-ttl` to set a custom expiration time for all CI Queue keys, this defaults to 8 hours (28,800 seconds)
data/dev.yml CHANGED
@@ -5,7 +5,7 @@ name: ci-queue
5
5
  up:
6
6
  - ruby: 2.6.5
7
7
  - bundler
8
- - railgun
8
+ - isogun
9
9
 
10
10
  commands:
11
11
  test: REDIS_HOST=ci-queue.railgun bundle exec rake test
@@ -3,11 +3,8 @@
3
3
  name: ci-queue
4
4
 
5
5
  vm:
6
- image: /opt/dev/misc/railgun-images/default
7
6
  ip_address: 192.168.64.245
8
7
  memory: 1G
9
8
  cores: 2
10
- volumes:
11
- root: 512M
12
9
  services:
13
10
  - redis
@@ -5,10 +5,10 @@ module CI
5
5
  attr_accessor :timeout, :worker_id, :max_requeues, :grind_count, :failure_file
6
6
  attr_accessor :requeue_tolerance, :namespace, :failing_test, :statsd_endpoint
7
7
  attr_accessor :max_test_duration, :max_test_duration_percentile, :track_test_duration
8
- attr_accessor :max_test_failed
8
+ attr_accessor :max_test_failed, :redis_ttl
9
9
  attr_reader :circuit_breakers
10
10
  attr_writer :seed, :build_id
11
- attr_writer :queue_init_timeout
11
+ attr_writer :queue_init_timeout, :report_timeout, :inactive_workers_timeout
12
12
 
13
13
  class << self
14
14
  def from_env(env)
@@ -18,6 +18,7 @@ module CI
18
18
  seed: env['CIRCLE_SHA1'] || env['BUILDKITE_COMMIT'] || env['TRAVIS_COMMIT'] || env['HEROKU_TEST_RUN_COMMIT_VERSION'] || env['SEMAPHORE_GIT_SHA'],
19
19
  flaky_tests: load_flaky_tests(env['CI_QUEUE_FLAKY_TESTS']),
20
20
  statsd_endpoint: env['CI_QUEUE_STATSD_ADDR'],
21
+ redis_ttl: env['CI_QUEUE_REDIS_TTL']&.to_i || 8 * 60 * 60,
21
22
  )
22
23
  end
23
24
 
@@ -34,7 +35,7 @@ module CI
34
35
  namespace: nil, seed: nil, flaky_tests: [], statsd_endpoint: nil, max_consecutive_failures: nil,
35
36
  grind_count: nil, max_duration: nil, failure_file: nil, max_test_duration: nil,
36
37
  max_test_duration_percentile: 0.5, track_test_duration: false, max_test_failed: nil,
37
- queue_init_timeout: nil
38
+ queue_init_timeout: nil, redis_ttl: 8 * 60 * 60, report_timeout: nil, inactive_workers_timeout: nil
38
39
  )
39
40
  @build_id = build_id
40
41
  @circuit_breakers = [CircuitBreaker::Disabled]
@@ -55,12 +56,23 @@ module CI
55
56
  @worker_id = worker_id
56
57
  self.max_consecutive_failures = max_consecutive_failures
57
58
  self.max_duration = max_duration
59
+ @redis_ttl = redis_ttl
60
+ @report_timeout = report_timeout
61
+ @inactive_workers_timeout = inactive_workers_timeout
58
62
  end
59
63
 
60
64
  def queue_init_timeout
61
65
  @queue_init_timeout || timeout
62
66
  end
63
67
 
68
+ def report_timeout
69
+ @report_timeout || timeout
70
+ end
71
+
72
+ def inactive_workers_timeout
73
+ @inactive_workers_timeout || timeout
74
+ end
75
+
64
76
  def max_consecutive_failures=(max)
65
77
  if max
66
78
  @circuit_breakers << CircuitBreaker::MaxConsecutiveFailures.new(max_consecutive_failures: max)
@@ -5,6 +5,7 @@ module CI
5
5
  class Base
6
6
  include Common
7
7
 
8
+ TEN_MINUTES = 60 * 10
8
9
  CONNECTION_ERRORS = [
9
10
  ::Redis::BaseConnectionError,
10
11
  ::SocketError, # https://github.com/redis/redis-rb/pull/631
@@ -20,6 +21,15 @@ module CI
20
21
  queue_initialized? && size == 0
21
22
  end
22
23
 
24
+ def expired?
25
+ if (created_at = redis.get(key('master-created-at')))
26
+ (created_at.to_f + config.redis_ttl + TEN_MINUTES) < Time.now.to_f
27
+ else
28
+ # if there is no created at set anymore we assume queue is expired
29
+ true
30
+ end
31
+ end
32
+
23
33
  def size
24
34
  redis.multi do |transaction|
25
35
  transaction.llen(key('queue'))
@@ -41,6 +41,7 @@ module CI
41
41
  id.dup.force_encoding(Encoding::BINARY),
42
42
  payload.dup.force_encoding(Encoding::BINARY),
43
43
  )
44
+ pipeline.expire(key('error-reports'), config.redis_ttl)
44
45
  record_stats(stats, pipeline: pipeline)
45
46
  end
46
47
  nil
@@ -90,6 +91,7 @@ module CI
90
91
  return unless stats
91
92
  stats.each do |stat_name, stat_value|
92
93
  pipeline.hset(key(stat_name), config.worker_id, stat_value)
94
+ pipeline.expire(key(stat_name), config.redis_ttl)
93
95
  end
94
96
  end
95
97
 
@@ -16,6 +16,7 @@ module CI
16
16
  key('error-reports'),
17
17
  payload.force_encoding(Encoding::BINARY),
18
18
  )
19
+ pipeline.expire(key('error-reports'), config.redis_ttl)
19
20
  record_stats(stats, pipeline: pipeline)
20
21
  end
21
22
  nil
@@ -58,6 +59,7 @@ module CI
58
59
  return unless stats
59
60
  stats.each do |stat_name, stat_value|
60
61
  pipeline.hset(key(stat_name), config.worker_id, stat_value)
62
+ pipeline.expire(key(stat_name), config.redis_ttl)
61
63
  end
62
64
  end
63
65
  end
@@ -21,17 +21,33 @@ module CI
21
21
 
22
22
  yield if block_given?
23
23
 
24
- time_left = config.timeout
25
- until exhausted? || time_left <= 0 || max_test_failed?
26
- sleep 1
24
+ time_left = config.report_timeout
25
+ time_left_with_no_workers = config.inactive_workers_timeout
26
+ until exhausted? || time_left <= 0 || max_test_failed? || time_left_with_no_workers <= 0
27
27
  time_left -= 1
28
+ sleep 1
29
+
30
+ if active_workers?
31
+ time_left_with_no_workers = config.inactive_workers_timeout
32
+ else
33
+ time_left_with_no_workers -= 1
34
+ end
28
35
 
29
36
  yield if block_given?
30
37
  end
38
+
39
+ puts "Aborting, it seems all workers died." if time_left_with_no_workers <= 0
31
40
  exhausted?
32
41
  rescue CI::Queue::Redis::LostMaster
33
42
  false
34
43
  end
44
+
45
+ private
46
+
47
+ def active_workers?
48
+ # if there are running jobs we assume there are still agents active
49
+ redis.zrangebyscore(key('running'), Time.now.to_f - config.timeout, "+inf", limit: [0,1]).count > 0
50
+ end
35
51
  end
36
52
  end
37
53
  end
@@ -24,6 +24,7 @@ module CI
24
24
  test_time_key(test_name),
25
25
  duration.to_s.force_encoding(Encoding::BINARY),
26
26
  )
27
+ pipeline.expire(test_time_key(test_name), config.redis_ttl)
27
28
  end
28
29
  nil
29
30
  end
@@ -34,6 +35,7 @@ module CI
34
35
  all_test_names_key,
35
36
  test_name.dup.force_encoding(Encoding::BINARY),
36
37
  )
38
+ pipeline.expire(all_test_names_key, config.redis_ttl)
37
39
  end
38
40
  nil
39
41
  end
@@ -53,6 +53,10 @@ module CI
53
53
  sleep 0.05
54
54
  end
55
55
  end
56
+ redis.pipelined do |pipeline|
57
+ pipeline.expire(key('worker', worker_id, 'queue'), config.redis_ttl)
58
+ pipeline.expire(key('processed'), config.redis_ttl)
59
+ end
56
60
  rescue *CONNECTION_ERRORS
57
61
  end
58
62
 
@@ -198,9 +202,16 @@ module CI
198
202
  transaction.lpush(key('queue'), tests) unless tests.empty?
199
203
  transaction.set(key('total'), @total)
200
204
  transaction.set(key('master-status'), 'ready')
205
+ transaction.set(key('master-created-at'), Time.now.to_f)
206
+
207
+ transaction.expire(key('queue'), config.redis_ttl)
208
+ transaction.expire(key('total'), config.redis_ttl)
209
+ transaction.expire(key('master-status'), config.redis_ttl)
210
+ transaction.expire(key('master-created-at'), config.redis_ttl)
201
211
  end
202
212
  end
203
213
  register
214
+ redis.expire(key('workers'), config.redis_ttl)
204
215
  rescue *CONNECTION_ERRORS
205
216
  raise if @master
206
217
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module CI
4
4
  module Queue
5
- VERSION = '0.22.1'
5
+ VERSION = '0.24.0'
6
6
  DEV_SCRIPTS_ROOT = ::File.expand_path('../../../../../redis', __FILE__)
7
7
  RELEASE_SCRIPTS_ROOT = ::File.expand_path('../redis', __FILE__)
8
8
  end
@@ -22,6 +22,9 @@ module Minitest
22
22
  def initialize(argv)
23
23
  @queue_config = CI::Queue::Configuration.from_env(ENV)
24
24
  @command, @argv = parse(argv)
25
+ if Minitest.respond_to?(:seed=)
26
+ Minitest.seed = @queue_config.seed.to_i
27
+ end
25
28
  end
26
29
 
27
30
  def run!
@@ -44,7 +47,10 @@ module Minitest
44
47
  end
45
48
 
46
49
  def run_command
47
- if queue.retrying?
50
+ if queue.retrying? || retry?
51
+ if queue.expired?
52
+ abort! "The test run is too old and can't be retried"
53
+ end
48
54
  reset_counters
49
55
  retry_queue = queue.retry_queue
50
56
  if retry_queue.exhausted?
@@ -364,6 +370,24 @@ module Minitest
364
370
  queue_config.timeout = timeout
365
371
  end
366
372
 
373
+ help = <<~EOS
374
+ Specify a timeout after which the report command will fail if not all tests have been processed.
375
+ Defaults to the value set for --timeout.
376
+ EOS
377
+ opts.separator ""
378
+ opts.on('--report-timeout TIMEOUT', Float, help) do |timeout|
379
+ queue_config.report_timeout = timeout
380
+ end
381
+
382
+ help = <<~EOS
383
+ Specify a timeout after the report will fail if all workers are inactive (e.g. died).
384
+ Defaults to the value set for --timeout.
385
+ EOS
386
+ opts.separator ""
387
+ opts.on('--inactive-workers-timeout TIMEOUT', Float, help) do |timeout|
388
+ queue_config.inactive_workers_timeout = timeout
389
+ end
390
+
367
391
  help = <<~EOS
368
392
  Specify a timeout to elect the leader and populate the queue.
369
393
  Defaults to the value set for --timeout.
@@ -487,6 +511,14 @@ module Minitest
487
511
  end
488
512
  end
489
513
 
514
+ help = <<~EOS
515
+ Defines how long the test report remain after the test run, in seconds.
516
+ Defaults to 28,800 (8 hours)
517
+ EOS
518
+ opts.on("--redis-ttl SECONDS", Integer, help) do |time|
519
+ queue.config.redis_ttl = time
520
+ end
521
+
490
522
  opts.separator ""
491
523
  opts.separator " retry: Replays a previous run in the same order."
492
524
 
@@ -535,6 +567,11 @@ module Minitest
535
567
  puts red(message)
536
568
  exit! 1 # exit! is required to avoid minitest at_exit callback
537
569
  end
570
+
571
+ def retry?
572
+ ENV["BUILDKITE_RETRY_COUNT"].to_i > 0 ||
573
+ ENV["SEMAPHORE_PIPELINE_RERUN"] == "true"
574
+ end
538
575
  end
539
576
  end
540
577
  end
data/lib/rspec/queue.rb CHANGED
@@ -157,6 +157,15 @@ module RSpec
157
157
  queue_config.max_consecutive_failures = Integer(max)
158
158
  end
159
159
 
160
+ help = <<~EOS
161
+ Defines how long the test report remain after the test run, in seconds.
162
+ Defaults to 28,800 (8 hours)
163
+ EOS
164
+ parser.separator ""
165
+ parser.on("--redis-ttl SECONDS", Integer, help) do |time|
166
+ queue.config.redis_ttl = time
167
+ end
168
+
160
169
  parser
161
170
  end
162
171
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ci-queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.1
4
+ version: 0.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-25 00:00:00.000000000 Z
11
+ date: 2022-07-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -171,6 +171,7 @@ files:
171
171
  - dev.yml
172
172
  - exe/minitest-queue
173
173
  - exe/rspec-queue
174
+ - isogun.yml
174
175
  - lib/ci/queue.rb
175
176
  - lib/ci/queue/bisect.rb
176
177
  - lib/ci/queue/build_record.rb
@@ -218,7 +219,6 @@ files:
218
219
  - lib/rspec/queue.rb
219
220
  - lib/rspec/queue/build_status_recorder.rb
220
221
  - lib/rspec/queue/order_recorder.rb
221
- - railgun.yml
222
222
  homepage: https://github.com/Shopify/ci-queue
223
223
  licenses:
224
224
  - MIT
@@ -239,7 +239,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
239
239
  - !ruby/object:Gem::Version
240
240
  version: '0'
241
241
  requirements: []
242
- rubygems_version: 3.2.20
242
+ rubygems_version: 3.3.3
243
243
  signing_key:
244
244
  specification_version: 4
245
245
  summary: Distribute tests over many workers using a queue