fiveruns-starling 0.9.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +27 -0
- data/LICENSE +20 -0
- data/README.rdoc +93 -0
- data/Rakefile +21 -0
- data/bin/starling +5 -0
- data/bin/starling_client +6 -0
- data/bin/starling_top +57 -0
- data/etc/starling.redhat +63 -0
- data/etc/starling.ubuntu +71 -0
- data/lib/starling.rb +104 -0
- data/lib/starling/client.rb +151 -0
- data/lib/starling/client_runner.rb +272 -0
- data/lib/starling/handler.rb +222 -0
- data/lib/starling/persistent_queue.rb +151 -0
- data/lib/starling/queue_collection.rb +141 -0
- data/lib/starling/server.rb +113 -0
- data/lib/starling/server_runner.rb +292 -0
- data/lib/starling/thread_pool.rb +62 -0
- data/lib/starling/worker.rb +239 -0
- data/test/test_starling_client.rb +100 -0
- data/test/test_starling_server.rb +205 -0
- data/test/test_starling_worker.rb +237 -0
- metadata +115 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'starling'
|
|
3
|
+
require 'starling/worker'
|
|
4
|
+
require 'logger'
|
|
5
|
+
require 'eventmachine'
|
|
6
|
+
require 'analyzer_tools/syslog_logger'
|
|
7
|
+
|
|
8
|
+
module StarlingClient
|
|
9
|
+
|
|
10
|
+
VERSION = "0.9.7.5"
|
|
11
|
+
|
|
12
|
+
class Base
|
|
13
|
+
attr_reader :logger
|
|
14
|
+
|
|
15
|
+
DEFAULT_HOST = "localhost"
|
|
16
|
+
DEFAULT_PORT = "22122"
|
|
17
|
+
DEFAULT_TEMPLATES_PATH = File.join("tmp", "starling", "templates")
|
|
18
|
+
DEFAULT_WORKERS_PATH = File.join("tmp", "starling", "workers")
|
|
19
|
+
DEFAULT_TIMEOUT = 60
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Initialize a new Starling client and immediately start processing
|
|
23
|
+
# requests.
|
|
24
|
+
#
|
|
25
|
+
# +opts+ is an optional hash, whose valid options are:
|
|
26
|
+
#
|
|
27
|
+
# [:host] Host on which to listen (default is 127.0.0.1).
|
|
28
|
+
# [:port] Port on which to listen (default is 22122).
|
|
29
|
+
# [:path] Path to Starling queue logs. Default is /tmp/starling/
|
|
30
|
+
# [:timeout] Time in seconds to wait before closing connections.
|
|
31
|
+
# [:logger] A Logger object, an IO handle, or a path to the log.
|
|
32
|
+
# [:loglevel] Logger verbosity. Default is Logger::ERROR.
|
|
33
|
+
#
|
|
34
|
+
# Other options are ignored.
|
|
35
|
+
|
|
36
|
+
def self.start(opts = {})
|
|
37
|
+
server = self.new(opts)
|
|
38
|
+
server.run
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# Initialize a new Starling client, but do not start with working
|
|
43
|
+
#
|
|
44
|
+
# +opts+ is as for +start+
|
|
45
|
+
|
|
46
|
+
def initialize(opts = {})
|
|
47
|
+
@opts = {
|
|
48
|
+
:host => DEFAULT_HOST,
|
|
49
|
+
:port => DEFAULT_PORT,
|
|
50
|
+
:templates_path => DEFAULT_TEMPLATES_PATH,
|
|
51
|
+
:workers_path => DEFAULT_WORKERS_PATH,
|
|
52
|
+
:timeout => DEFAULT_TIMEOUT,
|
|
53
|
+
:continues_processing => true
|
|
54
|
+
}.merge(opts)
|
|
55
|
+
|
|
56
|
+
@stats = Hash.new(0)
|
|
57
|
+
|
|
58
|
+
FileUtils.mkdir_p(@opts[:templates_path])
|
|
59
|
+
FileUtils.mkdir_p(@opts[:workers_path])
|
|
60
|
+
|
|
61
|
+
@client = Starling.new("#{@opts[:host]}:#{@opts[:port]}", :multithread => true)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
##
|
|
65
|
+
# Start listening and processing requests.
|
|
66
|
+
|
|
67
|
+
def run
|
|
68
|
+
@stats[:start_time] = Time.now
|
|
69
|
+
|
|
70
|
+
@@logger = case @opts[:logger]
|
|
71
|
+
when IO, String; Logger.new(@opts[:logger])
|
|
72
|
+
when Logger; @opts[:logger]
|
|
73
|
+
else; Logger.new(STDERR)
|
|
74
|
+
end
|
|
75
|
+
@@logger = SyslogLogger.new(@opts[:syslog_channel]) if @opts[:syslog_channel]
|
|
76
|
+
|
|
77
|
+
@@logger.level = @opts[:log_level] || Logger::ERROR
|
|
78
|
+
|
|
79
|
+
@@logger.info "Starling Client STARTUP"
|
|
80
|
+
|
|
81
|
+
load_templates
|
|
82
|
+
|
|
83
|
+
@pids = []
|
|
84
|
+
|
|
85
|
+
load_workers.each do |worker|
|
|
86
|
+
@pids << fork {
|
|
87
|
+
worker = StarlingWorker.const_get(worker).new(:host => @opts[:host],
|
|
88
|
+
:port => @opts[:port]).run
|
|
89
|
+
@@logger.info "Starling Client STARTUP"
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if load_workers.length == 0
|
|
94
|
+
@@logger.error "no workers found"
|
|
95
|
+
@shutdown = true
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
loop {
|
|
99
|
+
sleep 60
|
|
100
|
+
} if @opts[:continues_processing]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def stop
|
|
104
|
+
@pids.each do |pid|
|
|
105
|
+
Process.kill(0, pid)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
exit(0)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def load_templates
|
|
112
|
+
templates = []
|
|
113
|
+
Dir.glob("#{@opts[:templates_path]}/*.rb").each do |file|
|
|
114
|
+
unless [".", ".."].include?(file)
|
|
115
|
+
load(file)
|
|
116
|
+
templates << File.basename(file, ".rb").split('_').map{|w| w.capitalize}.join
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
return templates
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def load_workers
|
|
124
|
+
workers = []
|
|
125
|
+
Dir.glob("#{@opts[:workers_path]}/*.rb").each do |file|
|
|
126
|
+
unless [".", ".."].include?(file)
|
|
127
|
+
load(file)
|
|
128
|
+
workers << File.basename(file, ".rb").split('_').map{|w| w.capitalize}.join
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
return workers
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def starling
|
|
136
|
+
return @client
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def self.logger
|
|
140
|
+
@@logger
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def stats(stat = nil) #:nodoc:
|
|
144
|
+
case stat
|
|
145
|
+
when nil; @stats
|
|
146
|
+
when :connections; 1
|
|
147
|
+
else; @stats[stat]
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'client')
|
|
2
|
+
require 'optparse'
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module StarlingClient
|
|
6
|
+
class Runner
|
|
7
|
+
|
|
8
|
+
attr_accessor :options
|
|
9
|
+
private :options, :options=
|
|
10
|
+
|
|
11
|
+
def self.run
|
|
12
|
+
new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.shutdown
|
|
16
|
+
@@instance.shutdown
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
@@instance = self
|
|
21
|
+
parse_options
|
|
22
|
+
|
|
23
|
+
@process = ProcessHelper.new(options[:logger], options[:pid_file], options[:user], options[:group])
|
|
24
|
+
|
|
25
|
+
pid = @process.running?
|
|
26
|
+
if pid
|
|
27
|
+
STDERR.puts "There is already a Starling Client process running (pid #{pid}), exiting."
|
|
28
|
+
exit(1)
|
|
29
|
+
elsif pid.nil?
|
|
30
|
+
STDERR.puts "Cleaning up stale pidfile at #{options[:pid_file]}."
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
start
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def load_config_file(filename)
|
|
37
|
+
YAML.load(File.open(filename))['starling'].each do |key, value|
|
|
38
|
+
# alias some keys
|
|
39
|
+
case key
|
|
40
|
+
when "queue_path" then key = "path"
|
|
41
|
+
when "log_file" then key = "logger"
|
|
42
|
+
end
|
|
43
|
+
options[key.to_sym] = value
|
|
44
|
+
|
|
45
|
+
if options[:log_level].instance_of?(String)
|
|
46
|
+
options[:log_level] = Logger.const_get(options[:log_level])
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parse_options
|
|
52
|
+
self.options = { :path => File.join(%w( / tmp workers )),
|
|
53
|
+
:log_level => Logger::INFO,
|
|
54
|
+
:daemonize => false,
|
|
55
|
+
:timeout => 0,
|
|
56
|
+
:pid_file => File.join(%w( / tmp starling starling_client.pid )) }
|
|
57
|
+
|
|
58
|
+
OptionParser.new do |opts|
|
|
59
|
+
opts.summary_width = 25
|
|
60
|
+
|
|
61
|
+
opts.banner = "Starling (#{StarlingClient::VERSION})\n\n",
|
|
62
|
+
"usage: starling_client [options...]\n",
|
|
63
|
+
" starling_client --help\n",
|
|
64
|
+
" starling_client --version\n"
|
|
65
|
+
|
|
66
|
+
opts.separator ""
|
|
67
|
+
opts.separator "Configuration:"
|
|
68
|
+
|
|
69
|
+
opts.on("-f", "--config FILENAME",
|
|
70
|
+
"Config file (yaml) to load") do |filename|
|
|
71
|
+
load_config_file(filename)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
opts.on("-w", "--workers_path PATH",
|
|
75
|
+
:REQUIRED,
|
|
76
|
+
"Path to workers") do |queue_path|
|
|
77
|
+
options[:workers_path] = queue_path
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
opts.on("-t", "--templates_path PATH",
|
|
81
|
+
:REQUIRED,
|
|
82
|
+
"Path to templates") do |queue_path|
|
|
83
|
+
options[:templates_path] = queue_path
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
opts.separator ""; opts.separator "Process:"
|
|
87
|
+
|
|
88
|
+
opts.on("-d", "Run as a daemon.") do
|
|
89
|
+
options[:daemonize] = true
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
opts.on("-PFILE", "--pid FILENAME", "save PID in FILENAME when using -d option.", "(default: #{options[:pid_file]})") do |pid_file|
|
|
93
|
+
options[:pid_file] = pid_file
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
opts.separator ""; opts.separator "Logging:"
|
|
97
|
+
|
|
98
|
+
opts.on("-L", "--log [FILE]", "Path to print debugging information.") do |log_path|
|
|
99
|
+
options[:logger] = log_path
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
opts.on("-l", "--syslog CHANNEL", "Write logs to the syslog instead of a log file.") do |channel|
|
|
103
|
+
options[:syslog_channel] = channel
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
opts.on("-v", "Increase logging verbosity (may be used multiple times).") do
|
|
107
|
+
options[:log_level] -= 1
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
opts.separator ""; opts.separator "Miscellaneous:"
|
|
111
|
+
|
|
112
|
+
opts.on_tail("-?", "--help", "Display this usage information.") do
|
|
113
|
+
puts "#{opts}\n"
|
|
114
|
+
exit
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
opts.on_tail("-V", "--version", "Print version number and exit.") do
|
|
118
|
+
puts "Starling Client #{StarlingClient::VERSION}\n\n"
|
|
119
|
+
exit
|
|
120
|
+
end
|
|
121
|
+
end.parse!
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def start
|
|
125
|
+
drop_privileges
|
|
126
|
+
|
|
127
|
+
@process.daemonize if options[:daemonize]
|
|
128
|
+
|
|
129
|
+
setup_signal_traps
|
|
130
|
+
@process.write_pid_file
|
|
131
|
+
|
|
132
|
+
STDOUT.puts "Starting."
|
|
133
|
+
@client = StarlingClient::Base.new(options)
|
|
134
|
+
@client.run
|
|
135
|
+
|
|
136
|
+
@process.remove_pid_file
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def drop_privileges
|
|
140
|
+
Process.euid = options[:user] if options[:user]
|
|
141
|
+
Process.egid = options[:group] if options[:group]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def shutdown
|
|
145
|
+
begin
|
|
146
|
+
STDOUT.puts "Shutting down."
|
|
147
|
+
StarlingClient::Base.logger.info "Shutting down."
|
|
148
|
+
@client.stop
|
|
149
|
+
rescue Object => e
|
|
150
|
+
STDERR.puts "There was an error shutting down: #{e}"
|
|
151
|
+
exit(70)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def setup_signal_traps
|
|
156
|
+
Signal.trap("INT") { shutdown }
|
|
157
|
+
Signal.trap("TERM") { shutdown }
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
class ProcessHelper
|
|
162
|
+
|
|
163
|
+
def initialize(log_file = nil, pid_file = nil, user = nil, group = nil)
|
|
164
|
+
@log_file = log_file
|
|
165
|
+
@pid_file = pid_file
|
|
166
|
+
@user = user
|
|
167
|
+
@group = group
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def safefork
|
|
171
|
+
begin
|
|
172
|
+
if pid = fork
|
|
173
|
+
return pid
|
|
174
|
+
end
|
|
175
|
+
rescue Errno::EWOULDBLOCK
|
|
176
|
+
sleep 5
|
|
177
|
+
retry
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def daemonize
|
|
182
|
+
sess_id = detach_from_terminal
|
|
183
|
+
exit if pid = safefork
|
|
184
|
+
|
|
185
|
+
Dir.chdir("/")
|
|
186
|
+
File.umask 0000
|
|
187
|
+
|
|
188
|
+
close_io_handles
|
|
189
|
+
redirect_io
|
|
190
|
+
|
|
191
|
+
return sess_id
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def detach_from_terminal
|
|
195
|
+
srand
|
|
196
|
+
safefork and exit
|
|
197
|
+
|
|
198
|
+
unless sess_id = Process.setsid
|
|
199
|
+
raise "Couldn't detache from controlling terminal."
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
trap 'SIGHUP', 'IGNORE'
|
|
203
|
+
|
|
204
|
+
sess_id
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def close_io_handles
|
|
208
|
+
ObjectSpace.each_object(IO) do |io|
|
|
209
|
+
unless [STDIN, STDOUT, STDERR].include?(io)
|
|
210
|
+
begin
|
|
211
|
+
io.close unless io.closed?
|
|
212
|
+
rescue Exception
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def redirect_io
|
|
219
|
+
begin; STDIN.reopen('/dev/null'); rescue Exception; end
|
|
220
|
+
|
|
221
|
+
if @log_file
|
|
222
|
+
begin
|
|
223
|
+
STDOUT.reopen(@log_file, "a")
|
|
224
|
+
STDOUT.sync = true
|
|
225
|
+
rescue Exception
|
|
226
|
+
begin; STDOUT.reopen('/dev/null'); rescue Exception; end
|
|
227
|
+
end
|
|
228
|
+
else
|
|
229
|
+
begin; STDOUT.reopen('/dev/null'); rescue Exception; end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
begin; STDERR.reopen(STDOUT); rescue Exception; end
|
|
233
|
+
STDERR.sync = true
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def rescue_exception
|
|
237
|
+
begin
|
|
238
|
+
yield
|
|
239
|
+
rescue Exception
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def write_pid_file
|
|
244
|
+
return unless @pid_file
|
|
245
|
+
FileUtils.mkdir_p(File.dirname(@pid_file))
|
|
246
|
+
File.open(@pid_file, "w") { |f| f.write(Process.pid) }
|
|
247
|
+
File.chmod(0644, @pid_file)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def remove_pid_file
|
|
251
|
+
return unless @pid_file
|
|
252
|
+
File.unlink(@pid_file) if File.exists?(@pid_file)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def running?
|
|
256
|
+
return false unless @pid_file
|
|
257
|
+
|
|
258
|
+
pid = File.read(@pid_file).chomp.to_i rescue nil
|
|
259
|
+
pid = nil if pid == 0
|
|
260
|
+
return false unless pid
|
|
261
|
+
|
|
262
|
+
begin
|
|
263
|
+
Process.kill(0, pid)
|
|
264
|
+
return pid
|
|
265
|
+
rescue Errno::ESRCH
|
|
266
|
+
return nil
|
|
267
|
+
rescue Errno::EPERM
|
|
268
|
+
return pid
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
module StarlingServer
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# This is an internal class that's used by Starling::Server to handle the
|
|
5
|
+
# MemCache protocol and act as an interface between the Server and the
|
|
6
|
+
# QueueCollection.
|
|
7
|
+
|
|
8
|
+
class Handler < EventMachine::Connection
|
|
9
|
+
|
|
10
|
+
DATA_PACK_FMT = "Ia*".freeze
|
|
11
|
+
|
|
12
|
+
# ERROR responses
|
|
13
|
+
ERR_UNKNOWN_COMMAND = "CLIENT_ERROR bad command line format\r\n".freeze
|
|
14
|
+
|
|
15
|
+
# GET Responses
|
|
16
|
+
GET_COMMAND = /\Aget (.{1,250})\s*\r\n/m
|
|
17
|
+
GET_RESPONSE = "VALUE %s %s %s\r\n%s\r\nEND\r\n".freeze
|
|
18
|
+
GET_RESPONSE_EMPTY = "END\r\n".freeze
|
|
19
|
+
|
|
20
|
+
# SET Responses
|
|
21
|
+
SET_COMMAND = /\Aset (.{1,250}) ([0-9]+) ([0-9]+) ([0-9]+)\r\n/m
|
|
22
|
+
SET_RESPONSE_SUCCESS = "STORED\r\n".freeze
|
|
23
|
+
SET_RESPONSE_FAILURE = "NOT STORED\r\n".freeze
|
|
24
|
+
SET_CLIENT_DATA_ERROR = "CLIENT_ERROR bad data chunk\r\nERROR\r\n".freeze
|
|
25
|
+
|
|
26
|
+
# STAT Response
|
|
27
|
+
STATS_COMMAND = /\Astats\r\n/m
|
|
28
|
+
STATS_RESPONSE = "STAT pid %d
|
|
29
|
+
STAT uptime %d
|
|
30
|
+
STAT time %d
|
|
31
|
+
STAT version %s
|
|
32
|
+
STAT rusage_user %0.6f
|
|
33
|
+
STAT rusage_system %0.6f
|
|
34
|
+
STAT curr_items %d
|
|
35
|
+
STAT total_items %d
|
|
36
|
+
STAT bytes %d
|
|
37
|
+
STAT curr_connections %d
|
|
38
|
+
STAT total_connections %d
|
|
39
|
+
STAT cmd_get %d
|
|
40
|
+
STAT cmd_set %d
|
|
41
|
+
STAT get_hits %d
|
|
42
|
+
STAT get_misses %d
|
|
43
|
+
STAT bytes_read %d
|
|
44
|
+
STAT bytes_written %d
|
|
45
|
+
STAT limit_maxbytes %d
|
|
46
|
+
%sEND\r\n".freeze
|
|
47
|
+
QUEUE_STATS_RESPONSE = "STAT queue_%s_items %d
|
|
48
|
+
STAT queue_%s_total_items %d
|
|
49
|
+
STAT queue_%s_logsize %d
|
|
50
|
+
STAT queue_%s_expired_items %d
|
|
51
|
+
STAT queue_%s_age %d\n".freeze
|
|
52
|
+
|
|
53
|
+
SHUTDOWN_COMMAND = /\Ashutdown\r\n/m
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@@next_session_id = 1
|
|
57
|
+
|
|
58
|
+
##
|
|
59
|
+
# Creates a new handler for the MemCache protocol that communicates with a
|
|
60
|
+
# given client.
|
|
61
|
+
|
|
62
|
+
def initialize(options = {})
|
|
63
|
+
@opts = options
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# Process incoming commands from the attached client.
|
|
68
|
+
|
|
69
|
+
def post_init
|
|
70
|
+
@stash = []
|
|
71
|
+
@data = ""
|
|
72
|
+
@data_buf = ""
|
|
73
|
+
@server = @opts[:server]
|
|
74
|
+
@logger = StarlingServer::Base.logger
|
|
75
|
+
@expiry_stats = Hash.new(0)
|
|
76
|
+
@expected_length = nil
|
|
77
|
+
@server.stats[:total_connections] += 1
|
|
78
|
+
set_comm_inactivity_timeout @opts[:timeout]
|
|
79
|
+
@queue_collection = @opts[:queue]
|
|
80
|
+
|
|
81
|
+
@session_id = @@next_session_id
|
|
82
|
+
@@next_session_id += 1
|
|
83
|
+
|
|
84
|
+
peer = Socket.unpack_sockaddr_in(get_peername)
|
|
85
|
+
#@logger.debug "(#{@session_id}) New session from #{peer[1]}:#{peer[0]}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def receive_data(incoming)
|
|
89
|
+
@server.stats[:bytes_read] += incoming.size
|
|
90
|
+
@data << incoming
|
|
91
|
+
|
|
92
|
+
while data = @data.slice!(/.*?\r\n/m)
|
|
93
|
+
response = process(data)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
send_data response if response
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def process(data)
|
|
100
|
+
data = @data_buf + data if @data_buf.size > 0
|
|
101
|
+
# our only non-normal state is consuming an object's data
|
|
102
|
+
# when @expected_length is present
|
|
103
|
+
if @expected_length && data.size == @expected_length
|
|
104
|
+
response = set_data(data)
|
|
105
|
+
@data_buf = ""
|
|
106
|
+
return response
|
|
107
|
+
elsif @expected_length
|
|
108
|
+
@data_buf = data
|
|
109
|
+
return
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
case data
|
|
113
|
+
when SET_COMMAND
|
|
114
|
+
@server.stats[:set_requests] += 1
|
|
115
|
+
set($1, $2, $3, $4.to_i)
|
|
116
|
+
when GET_COMMAND
|
|
117
|
+
@server.stats[:get_requests] += 1
|
|
118
|
+
get($1)
|
|
119
|
+
when STATS_COMMAND
|
|
120
|
+
stats
|
|
121
|
+
when SHUTDOWN_COMMAND
|
|
122
|
+
# no point in responding, they'll never get it.
|
|
123
|
+
Runner::shutdown
|
|
124
|
+
else
|
|
125
|
+
logger.warn "Unknown command: #{data}."
|
|
126
|
+
respond ERR_UNKNOWN_COMMAND
|
|
127
|
+
end
|
|
128
|
+
rescue => e
|
|
129
|
+
logger.error "Error handling request: #{e}."
|
|
130
|
+
logger.debug e.backtrace.join("\n")
|
|
131
|
+
respond GET_RESPONSE_EMPTY
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def unbind
|
|
135
|
+
#@logger.debug "(#{@session_id}) connection ends"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private
|
|
139
|
+
def respond(str, *args)
|
|
140
|
+
response = sprintf(str, *args)
|
|
141
|
+
@server.stats[:bytes_written] += response.length
|
|
142
|
+
response
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def set(key, flags, expiry, len)
|
|
146
|
+
@expected_length = len + 2
|
|
147
|
+
@stash = [ key, flags, expiry ]
|
|
148
|
+
nil
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def set_data(incoming)
|
|
152
|
+
key, flags, expiry = @stash
|
|
153
|
+
data = incoming.slice(0...@expected_length-2)
|
|
154
|
+
@stash = []
|
|
155
|
+
@expected_length = nil
|
|
156
|
+
|
|
157
|
+
internal_data = [expiry.to_i, data].pack(DATA_PACK_FMT)
|
|
158
|
+
if @queue_collection.put(key, internal_data)
|
|
159
|
+
respond SET_RESPONSE_SUCCESS
|
|
160
|
+
else
|
|
161
|
+
respond SET_RESPONSE_FAILURE
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def get(key)
|
|
166
|
+
now = Time.now.to_i
|
|
167
|
+
|
|
168
|
+
while response = @queue_collection.take(key)
|
|
169
|
+
expiry, data = response.unpack(DATA_PACK_FMT)
|
|
170
|
+
|
|
171
|
+
break if expiry == 0 || expiry >= now
|
|
172
|
+
|
|
173
|
+
@expiry_stats[key] += 1
|
|
174
|
+
expiry, data = nil
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
if data
|
|
178
|
+
respond GET_RESPONSE, key, 0, data.size, data
|
|
179
|
+
else
|
|
180
|
+
respond GET_RESPONSE_EMPTY
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def stats
|
|
185
|
+
respond STATS_RESPONSE,
|
|
186
|
+
Process.pid, # pid
|
|
187
|
+
Time.now - @server.stats(:start_time), # uptime
|
|
188
|
+
Time.now.to_i, # time
|
|
189
|
+
StarlingServer::VERSION, # version
|
|
190
|
+
Process.times.utime, # rusage_user
|
|
191
|
+
Process.times.stime, # rusage_system
|
|
192
|
+
@queue_collection.stats(:current_size), # curr_items
|
|
193
|
+
@queue_collection.stats(:total_items), # total_items
|
|
194
|
+
@queue_collection.stats(:current_bytes), # bytes
|
|
195
|
+
@server.stats(:connections), # curr_connections
|
|
196
|
+
@server.stats(:total_connections), # total_connections
|
|
197
|
+
@server.stats(:get_requests), # get count
|
|
198
|
+
@server.stats(:set_requests), # set count
|
|
199
|
+
@queue_collection.stats(:get_hits),
|
|
200
|
+
@queue_collection.stats(:get_misses),
|
|
201
|
+
@server.stats(:bytes_read), # total bytes read
|
|
202
|
+
@server.stats(:bytes_written), # total bytes written
|
|
203
|
+
0, # limit_maxbytes
|
|
204
|
+
queue_stats
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def queue_stats
|
|
208
|
+
@queue_collection.queues.inject("") do |m,(k,v)|
|
|
209
|
+
m + sprintf(QUEUE_STATS_RESPONSE,
|
|
210
|
+
k, v.length,
|
|
211
|
+
k, v.total_items,
|
|
212
|
+
k, v.logsize,
|
|
213
|
+
k, @expiry_stats[k],
|
|
214
|
+
k, v.current_age)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def logger
|
|
219
|
+
@logger
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|