class Redispot::Server

Public Class Methods

new(config: { }, timeout: 3, tmpdir: nil, &block) click to toggle source

Create a new instance, and start redis-server if block given.

@example

Redispot::Server.new do |connect_info|
  redis = Redis.new(connect_info)
  assert_equal('PONG', redis.ping)
end

@param config [Hash] This is a `redis.conf` key value pair. You can use any key-value pair(s) that redis-server supports. @param timeout [Fixnum] Timeout seconds for detecting if redis-server is awake or not. @param tmpdir [String] Temporal directory, where redis config will be stored. @yield [connect_info] Connection info for client library to connect this redis-server instance. @yieldparam connect_info [String] This parameter is designed to pass directly to Redis module.

# File lib/redispot/server.rb, line 20
def initialize (config: { }, timeout: 3, tmpdir: nil, &block)
  @executable = 'redis-server'
  @owner_pid  = Process.pid
  @pid        = nil
  @config     = config.symbolize_keys
  @timeout    = timeout
  @workdir    = WorkingDirectory.new(tmpdir)

  if @config[:port].nil? && @config[:unixsocket].nil?
    @config[:unixsocket] = "#{@workdir}/redis.sock"
    @config[:port]       = 0
  end

  if @config[:dir].nil?
    @config[:dir] = @workdir
  end

  if @config[:loglevel].to_s == 'warning'
    $stderr.puts 'Redispot::Server does not support "loglevel warning", using "notice" instead.'
    @config[:loglevel] = 'notice'
  end

  start(&block) if block
end

Public Instance Methods

connect_info() click to toggle source

Return connection info for client library to connect this redis-server instance.

@example

redispot = Redispot::Server.new
redis    = Redis.new(redispot.connect_info)

@return [String] This parameter is designed to pass directly to Redis module.

# File lib/redispot/server.rb, line 107
def connect_info
  host = @config[:bind].presence || '0.0.0.0'
  port = @config[:port]

  if port.is_a?(Fixnum) && port > 0
    { url: "redis://#{host}:#{port}/" }
  else
    { path: @config[:unixsocket] }
  end
end
start() { |connect_info| ... } click to toggle source

Start redis-server instance manually. If block given, the redis instance is available only within a block. Users must call Redispot::Server#stop they have called Redispot::Server#start without block.

@example

redispot.start do |connect_info|
  redis = Redis.new(connect_info)
  assert_equal('PONG', redis.ping)
end

# or

connect_info = redispot.start

@overload start { … }

@yield [connect_info] Connection info for client library to connect this redis-server instance.
@yieldparam connect_info [Hash] This parameter is designed to pass directly to Redis module.

@overload start

@return connect_info [Hash] This parameter is designed to pass directly to Redis module.
# File lib/redispot/server.rb, line 64
def start
  return if @pid
  start_process

  if block_given?
    begin
      yield connect_info
    ensure
      stop
    end
  else
    connect_info
  end
end
stop() click to toggle source

Stop redis-server. This method is automatically called from object destructor.

# File lib/redispot/server.rb, line 82
def stop
  return unless @pid

  signals = [:TERM, :INT, :KILL]

  begin
    Process.kill(signals.shift, @pid)
    Timeout.timeout(@timeout) { Process.waitpid(@pid) }
  rescue Timeout::Error => error
    retry unless signals.empty?
    raise error
  end

  @pid = nil

  ObjectSpace.undefine_finalizer(self)
end

Private Instance Methods

config_string() click to toggle source

@return [String]

# File lib/redispot/server.rb, line 166
def config_string
  @config.each_with_object(String.new) do |(key, value), memo|
    next if value.to_s.empty?
    memo << "#{key} #{value}\n"
  end
end
execute_redis_server(logfile) click to toggle source

@param logfile [Pathname]

# File lib/redispot/server.rb, line 130
def execute_redis_server (logfile)
  File.open(logfile, 'a') do |fh|
    @pid = Process.fork do
      configfile = "#{@workdir}/redis.conf"
      File.write(configfile, config_string)

      begin
        Kernel.exec(@executable, configfile, out: fh, err: fh)
      rescue SystemCallError => error
        $stderr.puts "exec failed: #{error}"
        exit error.errno
      end
    end
  end
rescue SystemCallError => error
  $stderr.puts "failed to create log file: #{error}"
end
start_process() click to toggle source
# File lib/redispot/server.rb, line 120
def start_process
  logfile = "#{@workdir}/redis-server.log"

  execute_redis_server(logfile)
  wait_redis_server(logfile)
  ObjectSpace.define_finalizer(self, Killer.new(@pid, @timeout))
end
wait_redis_server(logfile) click to toggle source

@param logfile [Pathname]

# File lib/redispot/server.rb, line 150
def wait_redis_server (logfile)
  Timeout.timeout(@timeout) do
    while Process.waitpid(@pid, Process::WNOHANG).nil?
      return if File.read(logfile) =~ /the server is now ready to accept connections/i
      sleep 0.1
    end

    @pid = nil
    raise Timeout::Error
  end
rescue Timeout::Error
  raise RuntimeError, "failed to launch redis-server\n#{File.read(logfile)}"
end