Class: Cohere::Transcribe::State::OutputSetLock

Inherits:
Object
  • Object
show all
Defined in:
lib/cohere/transcribe/state/locking.rb

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(target, handles, keys, roles: nil) ⇒ OutputSetLock

Returns a new instance of OutputSetLock.



144
145
146
147
148
149
150
# File 'lib/cohere/transcribe/state/locking.rb', line 144

def initialize(target, handles, keys, roles: nil)
  @target = target
  @handles = handles.freeze
  @keys = keys.freeze
  @roles = (roles || Array.new(keys.length, :primary)).freeze
  @released = false
end

Class Attribute Details

.activeObject (readonly)

Returns the value of attribute active.



33
34
35
# File 'lib/cohere/transcribe/state/locking.rb', line 33

def active
  @active
end

.guardObject (readonly)

Returns the value of attribute guard.



33
34
35
# File 'lib/cohere/transcribe/state/locking.rb', line 33

def guard
  @guard
end

Instance Attribute Details

#targetObject (readonly)

Returns the value of attribute target.



142
143
144
# File 'lib/cohere/transcribe/state/locking.rb', line 142

def target
  @target
end

Class Method Details

.acquire(target, blocking: false, operation_hook: nil, ownership_hook: nil) ⇒ Object



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
# File 'lib/cohere/transcribe/state/locking.rb', line 35

def acquire(target, blocking: false, operation_hook: nil, ownership_hook: nil)
  State.ensure_lock_acquisition_allowed!(target)
  completed = false
  failed = false
  guard.synchronize do
    handles = []
    lock = nil
    lock_paths = State.lock_path_specs_for_target(target)
    paths = lock_paths.map(&:path)
    keys = paths.map(&:to_s)
    if keys.any? { |key| active.key?(key) }
      raise TranscriptionRuntimeError,
            "Another transcription job owns output set #{target.identity}"
    end

    lock_paths.each_with_index do |lock_path, index|
      path = lock_path.path
      handle = nil
      Thread.handle_interrupt(Object => :never) do
        handle = State.open_lock_file(path, shared: lock_path.canonical?)
        handles << handle
      end
      operation = File::LOCK_EX
      operation |= File::LOCK_NB unless blocking
      unless State.acquire_exclusive_file_lock(handle, operation, target: target, path: path)
        raise TranscriptionRuntimeError,
              "Another transcription process owns output set #{target.identity} (lock #{path})"
      end
      operation_hook&.call(:after_flock, target) if index.zero?
      State.verify_lock_identity!(
        path,
        handle,
        message: "Output lock changed while acquiring #{target.identity}"
      )
      State.refresh_legacy_lock(handle) if lock_path.legacy?
    end

    lock = new(target, handles, keys, roles: lock_paths.map(&:role))
    keys.each do |key|
      active[key] = lock
    end
    ownership_hook&.call(lock)
    completed = true
    lock
  rescue Errno::EWOULDBLOCK, Errno::EAGAIN
    failed = true
    raise TranscriptionRuntimeError,
          "Another transcription process owns output set #{target.identity}"
  rescue SystemCallError => e
    failed = true
    raise TranscriptionRuntimeError,
          "Cannot acquire output lock for #{target.identity}: #{e.message}",
          cause: e
  rescue Exception # rubocop:disable Lint/RescueException -- record only this acquisition's unwind
    failed = true
    raise
  ensure
    unless completed && !failed && !State.lock_acquisition_unwinding?
      Thread.handle_interrupt(Object => :never) do
        delete_active_keys(lock, keys || [])
        close_lock_handles(handles || [])
        lock&.mark_released!
      end
    end
  end
end

.close_lock_handles(handles) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/cohere/transcribe/state/locking.rb', line 106

def close_lock_handles(handles)
  first_error = nil
  Thread.handle_interrupt(Object => :never) do
    handles.reverse_each do |handle|
      begin
        next if handle.closed?
      rescue Exception => e # rubocop:disable Lint/RescueException
        first_error ||= e
      end

      begin
        handle.flock(File::LOCK_UN)
      rescue Exception => e # rubocop:disable Lint/RescueException
        first_error ||= e
      end
      begin
        handle.close
      rescue Exception => e # rubocop:disable Lint/RescueException
        first_error ||= e
      end
    end
  end
  first_error
end

.delete_active_keys(lock, keys) ⇒ Object



131
132
133
134
135
136
137
138
139
# File 'lib/cohere/transcribe/state/locking.rb', line 131

def delete_active_keys(lock, keys)
  first_error = nil
  keys.reverse_each do |key|
    active.delete(key) if active[key].equal?(lock)
  rescue Exception => e # rubocop:disable Lint/RescueException
    first_error ||= e
  end
  first_error
end

.release_allObject



102
103
104
# File 'lib/cohere/transcribe/state/locking.rb', line 102

def release_all
  guard.synchronize { active.values.uniq }.each(&:release)
end

Instance Method Details

#mark_released!Object



199
200
201
# File 'lib/cohere/transcribe/state/locking.rb', line 199

def mark_released!
  @released = true
end

#releaseObject



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/cohere/transcribe/state/locking.rb', line 175

def release
  cleanup_error = nil
  Thread.handle_interrupt(Object => :never) do
    self.class.guard.synchronize do
      return if @released

      begin
        handle_error = self.class.close_lock_handles(@handles)
        registration_error = self.class.delete_active_keys(self, @keys)
        cleanup_error = handle_error || registration_error
      ensure
        @released = true
      end
    end
  end
  raise cleanup_error if cleanup_error

  nil
end

#released?Boolean

Returns:

  • (Boolean)


195
196
197
# File 'lib/cohere/transcribe/state/locking.rb', line 195

def released?
  @released
end

#verify!Object



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/cohere/transcribe/state/locking.rb', line 152

def verify!
  self.class.guard.synchronize do
    if @released
      raise TranscriptionRuntimeError,
            "Output lock was released before committing #{target.identity}"
    end

    canonical_held = @roles.include?(:canonical)
    @keys.zip(@handles, @roles).each do |path, handle, role|
      State.verify_lock_identity!(
        path,
        handle,
        message: "Output lock changed while held for #{target.identity}"
      )
    rescue TranscriptionRuntimeError => e
      # The output-adjacent lock is authoritative. A tmp cleaner may
      # remove the 0.1.x compatibility path; replacement is still fatal.
      raise unless canonical_held && role == :legacy && State.missing_lock_path_error?(e)
    end
  end
  nil
end