File: //lib/ruby/vendor_ruby/moneta/server.rb
require 'socket'
module Moneta
# Moneta server to be used together with Moneta::Adapters::Client
# @api public
class Server
# @param [Hash] options
# @option options [Integer] :port (9000) TCP port
# @option options [String] :socket Alternative Unix socket file name
def initialize(store, options = {})
@store = store
@server = start(options)
@ios = [@server]
@clients = {}
@running = false
end
# Is the server running
#
# @return [Boolean] true if the server is running
def running?
@running
end
# Run the server
#
# @note This method blocks!
def run
raise 'Already running' if @running
@stop = false
@running = true
begin
until @stop
mainloop
end
ensure
File.unlink(@socket) if @socket
end
end
# Stop the server
def stop
raise 'Not running' unless @running
@stop = true
@server.close
@server = nil
end
private
TIMEOUT = 1
MAXSIZE = 0x100000
def mainloop
if ios = IO.select(@ios, nil, @ios, TIMEOUT)
ios[2].each do |io|
io.close
delete_client(io)
end
ios[0].each do |io|
if io == @server
if client = @server.accept
@ios << client
@clients[client] = ''
end
elsif io.closed? || io.eof?
delete_client(io)
else
handle(io, @clients[io] << io.readpartial(0xFFFF))
end
end
end
rescue SignalException => ex
warn "Moneta::Server - #{ex.message}"
raise if ex.signo == 15 || ex.signo == 2 # SIGTERM or SIGINT
rescue Exception => ex
warn "Moneta::Server - #{ex.message}"
end
def delete_client(io)
@ios.delete(io)
@clients.delete(io)
end
def pack(o)
s = Marshal.dump(o)
[s.bytesize].pack('N') << s
end
def handle(io, buffer)
buffer = @clients[io]
return if buffer.bytesize < 8 # At least 4 bytes for the marshalled array
size = buffer[0,4].unpack('N').first
if size > MAXSIZE
delete_client(io)
return
end
return if buffer.bytesize < 4 + size
buffer.slice!(0, 4)
method, *args = Marshal.load(buffer.slice!(0, size))
case method
when :key?, :load, :delete, :increment, :create, :features
io.write(pack @store.send(method, *args))
when :store, :clear
@store.send(method, *args)
io.write(@nil ||= pack(nil))
else
raise 'Invalid method call'
end
rescue IOError => ex
warn "Moneta::Server - #{ex.message}" unless ex.message =~ /closed/
delete_client(io)
rescue Exception => ex
warn "Moneta::Server - #{ex.message}"
io.write(pack Exception.new(ex.message))
end
def start(options)
if @socket = options[:socket]
begin
UNIXServer.open(@socket)
rescue Errno::EADDRINUSE
if client = (UNIXSocket.open(@socket) rescue nil)
client.close
raise
end
File.unlink(@socket)
tries ||= 0
(tries += 1) < 3 ? retry : raise
end
else
TCPServer.open(options[:host] || '127.0.0.1', options[:port] || 9000)
end
end
end
end