class GCSLock::Mutex

Public Class Methods

new(bucket, object, client: nil, uuid: nil, min_backoff: nil, max_backoff: nil) click to toggle source
# File lib/gcslock/mutex.rb, line 9
def initialize(bucket, object, client: nil, uuid: nil, min_backoff: nil, max_backoff: nil)
  @client = client || Google::Cloud::Storage.new
  @bucket = @client.bucket(bucket, skip_lookup: true)
  @object = @bucket.file(object, skip_lookup: true)

  @uuid = uuid || SecureRandom.uuid
  @min_backoff = min_backoff || 0.01
  @max_backoff = max_backoff || 5.0
end

Public Instance Methods

lock(timeout: nil) click to toggle source

Attempts to grab the lock and waits if it isn't available.

@param timeout [Integer] the duration to wait before cancelling the operation

if the lock was not obtained (unlimited if _nil_).

@return [Boolean] `true` if the lock was obtained.

@raise [LockAlreadyOwnedError] if the lock is already owned by the current instance. @raise [LockTimeoutError] if the lock was not obtained before reaching the timeout.

# File lib/gcslock/mutex.rb, line 28
def lock(timeout: nil)
  raise LockAlreadyOwnedError, "Mutex for #{@object.name} is already owned by this process" if owned?

  begin
    Utils.backoff(min_backoff: @min_backoff, max_backoff: @max_backoff, timeout: timeout) do
      try_lock
    end
  rescue LockTimeoutError
    raise LockTimeoutError, "Unable to get mutex for #{@object.name} before timeout"
  end
end
locked?() click to toggle source

Verifies if the lock is already taken.

@return [Boolean] `true` if this lock is currently held.

# File lib/gcslock/mutex.rb, line 43
def locked?
  @object.reload!
  @object.exists?
rescue Google::Cloud::NotFoundError
  false
end
owned?() click to toggle source

Verifies if the lock is already owned by this instance.

@return [Boolean] `true` if this lock is currently held by this instance.

# File lib/gcslock/mutex.rb, line 53
def owned?
  locked? && @object.size == @uuid.size && @object.download.read == @uuid
end
synchronize(timeout: nil) { || ... } click to toggle source

Obtains a lock, runs the block, and releases the lock when the block completes.

@param timeout [Integer] the duration to wait before cancelling the operation

if the lock was not obtained (unlimited if _nil_).

@return [Object] what the called block returned.

@raise [LockAlreadyOwnedError] if the lock is already owned by the current instance. @raise [LockTimeoutError] if the lock was not obtained before reaching the timeout.

# File lib/gcslock/mutex.rb, line 66
def synchronize(timeout: nil)
  lock(timeout: timeout)
  begin
    block = yield
  ensure
    unlock
  end

  block
end
try_lock() click to toggle source

Attempts to obtain the lock and returns immediately.

@return [Boolean] `true` if the lock was granted.

# File lib/gcslock/mutex.rb, line 80
def try_lock
  @client.service.service.insert_object(
    @bucket.name,
    name: @object.name,
    if_generation_match: 0,
    upload_source: StringIO.new(@uuid),
  )

  true
rescue Google::Apis::ClientError => e
  raise unless e.status_code == 412 && e.message.start_with?('conditionNotMet:')

  false
end
unlock() click to toggle source

Releases the lock.

@return nil

@raise [LockNotOwnedError] if the lock is not owned by the current instance.

# File lib/gcslock/mutex.rb, line 100
def unlock
  raise LockNotOwnedError, "Mutex for #{@object.name} is not owned by this process" unless owned?
  @object.delete

  nil
end
unlock!() click to toggle source

Releases the lock even if not owned by this instance.

@return nil

@raise [LockNotFoundError] if the lock is not held by anyone.

# File lib/gcslock/mutex.rb, line 112
def unlock!
  @object.delete

  nil
rescue Google::Cloud::NotFoundError => e
  raise LockNotFoundError, "Mutex for #{@object.name} not found"
end