ci-queue 0.22.1 → 0.24.0

Sign up to get free protection for your applications and to get access to all the features.
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