class TrustedSandbox::UidPool

Offers intra-server inter-process pool of Uids. In other words:

- Every server has its own pool. Since Docker containers live within a server, this is what we want.
- Processes within the same server share the pool.

Usage:

The following will behave the same when different processes try to perform #lock and #release.

pool = UidPool.new 100, 101
pool.lock
# => 100

pool.lock
# => 101

pool.lock
# => RuntimeError: No available UIDs in the pool. Please try again later.

pool.release(100)
# => 100

pool.lock
# => 100

pool.release_all

Attributes

delay[R]
lock_dir[R]
lower[R]
master_lock_file[R]
retries[R]
timeout[R]
upper[R]

Public Class Methods

new(lock_dir, lower, upper, options={}) click to toggle source

@param lower [Integer] lower bound of the pool @param upper [Integer] upper bound of the pool @option timeout [Integer] number of seconds to wait for the lock @option retries [Integer] number of attempts to retry to acquire a uid @option delay [Float] delay between retries

# File lib/trusted_sandbox/uid_pool.rb, line 37
def initialize(lock_dir, lower, upper, options={})
  @lock_dir = lock_dir
  FileUtils.mkdir_p(lock_dir)

  @master_lock_file = lock_file_path_for('master')
  @lower = lower
  @upper = upper
  @timeout = options[:timeout] || options['timeout'] || 3
  @retries = options[:retries] || options['retries'] || 5
  @delay = options[:delay] || options['delay'] || 0.5
end

Public Instance Methods

available() click to toggle source

@return [Integer] number of availabld UIDs

# File lib/trusted_sandbox/uid_pool.rb, line 94
def available
  available_uids.length
end
available_uids() click to toggle source

@return [Array<Integer>] all non taken uids

# File lib/trusted_sandbox/uid_pool.rb, line 105
def available_uids
  all_uids - used_uids
end
inspect() click to toggle source
# File lib/trusted_sandbox/uid_pool.rb, line 49
def inspect
  "#<TrustedSandbox::UidPool used: #{used}, available: #{available}, used_uids: #{used_uids}>"
end
lock() click to toggle source

Locks one UID from the pool, in a cross-process atomic manner @return [Integer] @raise [PoolTimeoutError] if no ID is available after retries

# File lib/trusted_sandbox/uid_pool.rb, line 56
def lock
  retries.times do
    atomically(timeout) do
      uid = available_uid
      if uid
        lock_uid uid
        return uid.to_i
      end
    end
    sleep(delay)
  end
  raise PoolTimeoutError.new('No available UIDs in the pool. Please try again later.')
end
release(uid) click to toggle source

Releases one UID @param uid [Integer] @return [Integer] UID removed

# File lib/trusted_sandbox/uid_pool.rb, line 82
def release(uid)
  atomically(timeout) do
    release_uid uid
  end
end
release_all() click to toggle source

Releases all UIDs @return [UidPool] self

# File lib/trusted_sandbox/uid_pool.rb, line 72
def release_all
  all_uids.each do |uid|
    release uid
  end
  self
end
used() click to toggle source

@return [Integer] number of used UIDs

# File lib/trusted_sandbox/uid_pool.rb, line 89
def used
  used_uids.length
end
used_uids() click to toggle source

@return [Array<Integer>] all taken uids

# File lib/trusted_sandbox/uid_pool.rb, line 99
def used_uids
  uids = Dir.entries(lock_dir) - %w(. .. master)
  uids.map(&:to_i)
end

Private Instance Methods

all_uids() click to toggle source

@return [Array<Integer>] all uids in range

# File lib/trusted_sandbox/uid_pool.rb, line 112
def all_uids
  [*lower..upper]
end
atomically(timeout) { || ... } click to toggle source

@param timeout [Integer] @return yield return value

# File lib/trusted_sandbox/uid_pool.rb, line 142
def atomically(timeout)
  Timeout.timeout(timeout) do
    File.open(master_lock_file, File::RDWR|File::CREAT, 0644) do |f|
      f.flock File::LOCK_EX
      yield
    end
  end
end
available_uid() click to toggle source

@return [Integer, nil] one available uid or nil if none is available

# File lib/trusted_sandbox/uid_pool.rb, line 152
def available_uid
  available_uids.first
end
lock_file_path_for(uid) click to toggle source

@param uid [Integer] @return [String] full path for the UID lock file

# File lib/trusted_sandbox/uid_pool.rb, line 118
def lock_file_path_for(uid)
  File.join lock_dir, uid.to_s
end
lock_uid(uid) click to toggle source

Creates a UID lock file in the lock_dir

@param uid [Integer] @return [Integer] the UID locked

# File lib/trusted_sandbox/uid_pool.rb, line 126
def lock_uid(uid)
  File.open lock_file_path_for(uid), 'w'
  uid
end
release_uid(uid) click to toggle source

Removes a UID lock file from the lock_dir

@param uid [Integer] @return [Integer] the UID removed

# File lib/trusted_sandbox/uid_pool.rb, line 135
def release_uid(uid)
  FileUtils.rm lock_file_path_for(uid), force: true
  uid
end