summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb
blob: f74fbf1206fcf5d95cd742b8c8663f2ea71b7f25 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::ExclusiveLeaseHelpers::SleepingLock, :clean_gitlab_redis_shared_state do
  include ::ExclusiveLeaseHelpers

  let(:timeout) { 1.second }
  let(:delay) { 0.1.seconds }
  let(:key) { SecureRandom.hex(10) }

  subject { described_class.new(key, timeout: timeout, delay: delay) }

  describe '#retried?' do
    before do
      stub_exclusive_lease(key, 'uuid')
    end

    context 'we have not made any attempts' do
      it { is_expected.not_to be_retried }
    end

    context 'we just made a single (initial) attempt' do
      it 'is not considered a retry' do
        subject.send(:try_obtain)

        is_expected.not_to be_retried
      end
    end

    context 'made multiple attempts' do
      it 'is considered a retry' do
        2.times { subject.send(:try_obtain) }

        is_expected.to be_retried
      end
    end
  end

  describe '#obtain' do
    context 'when the lease is not held' do
      before do
        stub_exclusive_lease(key, 'uuid')
      end

      it 'obtains the lease on the first attempt, without sleeping' do
        expect(subject).not_to receive(:sleep)

        subject.obtain(10)

        expect(subject).not_to be_retried
      end
    end

    context 'when the lease is held elsewhere' do
      let!(:lease) { stub_exclusive_lease_taken(key) }
      let(:max_attempts) { 7 }

      it 'retries to obtain a lease and raises an error' do
        expect(subject).to receive(:sleep).with(delay).exactly(max_attempts - 1).times
        expect(lease).to receive(:try_obtain).exactly(max_attempts).times

        expect { subject.obtain(max_attempts) }.to raise_error('Failed to obtain a lock')
      end

      context 'when the delay is computed from the attempt number' do
        let(:delay) { ->(n) { 3 * n } }

        it 'uses the computation to determine the sleep length' do
          expect(subject).to receive(:sleep).with(3).once
          expect(subject).to receive(:sleep).with(6).once
          expect(subject).to receive(:sleep).with(9).once
          expect(lease).to receive(:try_obtain).exactly(4).times

          expect { subject.obtain(4) }.to raise_error('Failed to obtain a lock')
        end
      end

      context 'when lease is granted after retry' do
        it 'knows that it retried' do
          expect(subject).to receive(:sleep).with(delay).exactly(3).times
          expect(lease).to receive(:try_obtain).exactly(3).times { nil }
          expect(lease).to receive(:try_obtain).once { 'obtained' }

          subject.obtain(max_attempts)

          expect(subject).to be_retried
        end
      end
    end

    describe 'cancel' do
      let!(:lease) { stub_exclusive_lease(key, 'uuid') }

      it 'cancels the lease' do
        expect(lease).to receive(:cancel)

        subject.cancel
      end
    end
  end
end