starling 0.9.3 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,49 @@
1
+ == 0.9.8
2
+ * add fix to enable relative paths <david@motorator.com>
3
+ * fix tests so they don't run 10 times due to a stupid bug with how the server is forked <seth@mojodna.net>
4
+ * fix some other tests <romeda@gmail.com>
5
+ * fix some error messages <romeda@gmail.com>
6
+ * probably some other things <romeda@gmail.com>
7
+
8
+ == 0.9.7.9
9
+ * properly complain if the spool directory isn't writable <seth@mojodna.net>
10
+ * assume group and user privileges in a working order <seth@mojodna.net>
11
+ * support string user / group names in addition to uid/gids <seth@mojodna.net>
12
+
13
+ == 0.9.7.7
14
+ * added init.d scripts for redhat and ubuntu by Mike Perham <mperham@gmail.com>
15
+ * fixed dependencies for SyslogLogger, eventmachine and memcache-client by Mike Perham <mperham@gmail.com>
16
+ * added starling_top script to monitor starling server by Mike Perham <mperham@gmail.com>
17
+ * fixed starling_top to use 22122 as port by Abdul-Rahman Advany <abdulrahman@advany.com>
18
+
19
+ == 0.9.7.6 2008-06-24
20
+ * removed client code (will be added to different project) by Abdul-Rahman Advany <abdulrahman@advany.com>
21
+
22
+ == 0.9.7.5 2008-05-04
23
+ * added worker class, using starling client you can now run them in background by Abdul-Rahman Advany <abdulrahman@advany.com>
24
+ - handles creation of threadpool
25
+ - handles fetching of messages and passing these to the threads
26
+ - handles pushing of processed messages to starling again if needed
27
+
28
+ == 0.9.7 2008-05-03
29
+ * merged branch of AnotherBritt and Glenn Rempe by Abdul-Rahman Advany <abdulrahman@advany.com>
30
+ * rspeced tests by Abdul-Rahman Advany <abdulrahman@advany.com>
31
+
32
+ == 0.9.6 2008-04-30
33
+ * logging of message lifecycle as :age by AnotherBritt <?>
34
+ * added some extra logging options by AnotherBritt <?>
35
+ * added some test for epoll by AnotherBritt <?>
36
+
37
+ == 0.9.5.4 2008-04-28
38
+
39
+ * Bumped version number by Glenn Rempe <glenn.rempe@gmail.com>
40
+ * Purged all old RubyForge config. Now GitHub friendly by Glenn Rempe <glenn.rempe@gmail.com>
41
+ * New gemspec for GitHub gem auto-build-serve by Glenn Rempe <glenn.rempe@gmail.com>
42
+
43
+ == 0.9.4 2008-01-31 ==
44
+ * Evented code added using EventMachine by Chris Wanstrath <chris@ozmm.org>
45
+
46
+ == 2007-11-02
47
+
48
+ * Initial release
49
+
@@ -1,4 +1,4 @@
1
- Copyright (c) 2007 Blaine Cook, Twitter, Inc.
1
+ Copyright (c) 2007 FIXME full name
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc ADDED
@@ -0,0 +1,106 @@
1
+ = Name
2
+
3
+ Starling - a light weight server for reliable distributed message passing.
4
+
5
+ = Description
6
+
7
+ Starling is a powerful but simple messaging server that enables reliable
8
+ distributed queuing with an absolutely minimal overhead. It speaks the
9
+ MemCache protocol for maximum cross-platform compatibility. Any language
10
+ that speaks MemCache can take advantage of Starling's queue facilities.
11
+
12
+ = Installation
13
+
14
+ This fork of the Starling source is hosted at GitHub and can be found at:
15
+
16
+ http://github.com/starling/starling/tree/master
17
+
18
+ The original source was to be found at RubyForge but no longer exists there.
19
+
20
+ GitHub serves gems prefixed by a username to differentiate different forks.
21
+ This project can be installed with:
22
+
23
+ # THIS COMMAND ONE TIME ONLY
24
+ gem sources -a http://gems.github.com/
25
+
26
+ # As often as you like
27
+ sudo gem install starling-starling
28
+
29
+ See http://gems.github.com/ if you want more info about GitHub and gems.
30
+
31
+ = Quick Start Usage
32
+
33
+ # View the Starling help and usage message
34
+ starling --help
35
+
36
+ # In a console window start the Starling server. By default
37
+ # it runs verbosely in the foreground, listening on 127.0.0.1:22122
38
+ # and stores its files under /tmp/starling:
39
+ starling
40
+
41
+ # In a new console test the put and get of messages on a queue:
42
+
43
+ irb
44
+ >> require 'starling'
45
+ => true
46
+ >> starling = Starling.new('127.0.0.1:22122')
47
+ => MemCache: 1 servers, 1 buckets, ns: nil, ro: false
48
+ >> starling.set('my_queue', 12345)
49
+ => nil
50
+ >> starling.get('my_queue')
51
+ => 12345
52
+
53
+ # You can do a simple loop over a queue with something like:
54
+ >> loop { puts starling.get('my_queue'); sleep 1 }
55
+ 12345
56
+ nil
57
+ nil
58
+ ...
59
+
60
+ For more information run the following in a new console:
61
+
62
+ 'gem server'
63
+
64
+ This will start a gem server on http://localhost:8808/ which you can view in your
65
+ browser to see the RDocs for the gem. Or generate rdocs by running the following
66
+ in a new console:
67
+
68
+ 'rdoc'
69
+
70
+ = Using fiveruns memcache-client
71
+
72
+ memcache-client from fiveruns has a couple of fixed added like supporting failover and retry on failure.
73
+
74
+ This fork of the memcache-client source is hosted at GitHub and can be found at:
75
+
76
+ http://github.com/fiveruns/memcache-client/tree/master
77
+
78
+ It can be installed using:
79
+
80
+ # THIS COMMAND ONE TIME ONLY
81
+ gem sources -a http://gems.github.com/
82
+
83
+ # As often as you like
84
+ sudo gem install fiveruns-memcache-client
85
+
86
+ = Known Issues
87
+
88
+ * Starling is "slow" as far as messaging systems are concerned. In practice,
89
+ it's fast enough.
90
+
91
+ == TODO
92
+
93
+ * Implement memcached instead of memcache as a client interface (to make it faster)
94
+
95
+ = Authors
96
+
97
+ * Blaine Cook <romeda@gmail.com>
98
+ * Chris Wanstrath <chris@ozmm.org>
99
+ * AnotherBritt <?>
100
+ * Glenn Rempe <?>
101
+ * Abdul-Rahman Advany <abdulrahman@advany.com>
102
+
103
+ = Copyright
104
+
105
+ Starling - a light-weight server for reliable distributed message passing.
106
+ Copyright 2007-2008 Blaine Cook <blaine@twitter.com>, Twitter Inc.
data/Rakefile CHANGED
@@ -1,4 +1,22 @@
1
- require 'config/requirements'
2
- require 'config/hoe' # setup Hoe + all gem configuration
3
-
4
- Dir['tasks/**/*.rake'].each { |rake| load rake }
1
+ require 'rubygems'
2
+ require 'rake/rdoctask'
3
+ require 'spec/rake/spectask'
4
+
5
+ task :default => :spec
6
+
7
+ task :install do
8
+ sh %{gem build starling.gemspec}
9
+ sh %{sudo gem install starling-*.gem}
10
+ end
11
+
12
+ Spec::Rake::SpecTask.new do |t|
13
+ t.ruby_opts = ['-rtest/unit']
14
+ t.spec_files = FileList['spec/*_spec.rb']
15
+ t.fail_on_error = true
16
+ end
17
+
18
+ Rake::RDocTask.new do |rd|
19
+ rd.main = "README.rdoc"
20
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
21
+ rd.rdoc_dir = 'doc'
22
+ end
data/bin/starling CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'starling/runner'
3
+ require 'starling/server_runner'
4
+
4
5
  StarlingServer::Runner.run
6
+
data/bin/starling_top ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'ostruct'
5
+ require 'socket'
6
+
7
+ @options = OpenStruct.new
8
+ @options.hostname = 'localhost'
9
+ @options.port = 22122
10
+
11
+ op = OptionParser.new do |opts|
12
+ opts.banner = "STARLING TOP\nUsage: startop [options]"
13
+ opts.separator "General Options:"
14
+ opts.on("-h HOSTNAME", "--hostname=HOSTNAME", "Hostname [default: localhost]") do |h|
15
+ @options.hostname = h
16
+ end
17
+ opts.on("-p PORT", "--port=PORT", Integer, "Port [default: 22122]") do |p|
18
+ @options.port = p
19
+ end
20
+ opts.on_tail("--help", "Show this message") do
21
+ puts opts
22
+ exit
23
+ end
24
+ end
25
+ op.parse!
26
+
27
+
28
+ def stats_data
29
+ data = ''
30
+ sock = TCPSocket.new(@options.hostname, @options.port)
31
+ sock.print("stats\r\n")
32
+ sock.flush
33
+ # memcached does not close the socket once it is done writing
34
+ # the stats data. We need to read line by line until we detect
35
+ # the END line and then stop/close on our side.
36
+ stats = sock.gets
37
+ while true
38
+ data += stats
39
+ break if stats.strip == 'END'
40
+ stats = sock.gets
41
+ end
42
+ sock.close
43
+ data
44
+ end
45
+
46
+ def parse(stats_data)
47
+ stats = []
48
+ stats_data.each_line do |line|
49
+ stats << "#{$1}: #{$2}" if line =~ /STAT (\w+) (\S+)/
50
+ end
51
+ stats.sort
52
+ end
53
+
54
+ stats = parse(stats_data)
55
+ stats.each do |stat|
56
+ puts stat
57
+ end
@@ -0,0 +1,9 @@
1
+ starling:
2
+ port: 22122
3
+ pid_file: /tmp/starling/starling.pid
4
+ queue_path: /tmp/starling/spool
5
+ timeout: 0
6
+ syslog_channel: starling-tampopo
7
+ log_level: DEBUG
8
+ daemonize: true
9
+
@@ -0,0 +1,63 @@
1
+ #!/bin/bash
2
+ #
3
+ # starling This shell script takes care of starting and stopping
4
+ # the starling server
5
+ # chkconfig: 345 98 98
6
+ # description: The starling queue server
7
+
8
+ #determine where the 'pidof' executable is located
9
+ if [ -e /bin/pidof ]; then
10
+ PIDOF="/bin/pidof"
11
+ elif [ -e /sbin/pidof ]; then
12
+ PIDOF="/sbin/pidof"
13
+ elif [ -e /usr/local/bin/pidof ]; then
14
+ PIDOF="/usr/local/bin/pidof"
15
+ elif [ -e /bin/pgrep ]; then
16
+ PIDOF="/bin/pgrep"
17
+ elif [ -e /usr/bin/pgrep ]; then
18
+ PIDOF="/usr/bin/pgrep"
19
+ elif [ -e /usr/local/bin/pgrep ]; then
20
+ PIDOF="/usr/local/bin/pgrep"
21
+ else
22
+ echo "Could not find pidof or pgrep"
23
+ fi
24
+
25
+ PROGDIR="/usr/bin"
26
+ PROGNAME="starling"
27
+ OPTIONS="-u nobody -g nobody -L /var/log/starling.log -q /var/spool/starling"
28
+
29
+ start() {
30
+ pid=`$PIDOF $PROGNAME`
31
+ if [ "$pid" != "" ]; then
32
+ echo "$PROGDIR$PROGNAME already running: $pid"
33
+ else
34
+ echo "Starting $PROGDIR$PROGNAME"
35
+ cd $PROGDIR
36
+ nohup $PROGDIR$PROGNAME $OPTIONS &
37
+ fi
38
+ }
39
+
40
+ stop() {
41
+ pid=`$PIDOF $PROGNAME`
42
+ if [ "$pid" != "" ]; then
43
+ echo "Stopping $PROGDIR$PROGNAME: $pid"
44
+ kill $pid
45
+ else
46
+ echo "$PROGDIR$PROGNAME not running"
47
+ fi
48
+ }
49
+
50
+ case "$1" in
51
+ start)
52
+ start
53
+ ;;
54
+ stop)
55
+ stop
56
+ ;;
57
+ restart)
58
+ stop
59
+ sleep 3
60
+ start
61
+ ;;
62
+
63
+ esac
@@ -0,0 +1,71 @@
1
+ #! /bin/sh
2
+ ### BEGIN INIT INFO
3
+ # Provides: starling
4
+ # Required-Start: $local_fs $remote_fs
5
+ # Required-Stop: $local_fs $remote_fs
6
+ # Default-Start: 2 3 4 5
7
+ # Default-Stop: S 0 1 6
8
+ # Short-Description: Starling queue server
9
+ # Description: The Starling distributed, transactional queue server
10
+ ### END INIT INFO
11
+ # Author: Twitter
12
+ # Version: 0.9.7.7
13
+
14
+ set -e
15
+
16
+ DUSER=starling
17
+ DGROUP=starling
18
+ LOGFILE=/var/log/starling/starling.log
19
+ SPOOLDIR=/var/spool/starling
20
+ PORT=22122
21
+ LISTEN=0.0.0.0
22
+ PIDFILE=/var/run/starling/starling.pid
23
+
24
+ PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
25
+ NAME=starling
26
+ DESC="Starling"
27
+ INSTALL_DIR=/usr/local/bin
28
+ DAEMON=$INSTALL_DIR/$NAME
29
+ SCRIPTNAME=/etc/init.d/$NAME
30
+ OPTS="-h $LISTEN -p $PORT -d -q $SPOOLDIR -P $PIDFILE -L $LOGFILE"
31
+
32
+ . /lib/lsb/init-functions
33
+
34
+
35
+ # Gracefully exit if the package has been removed.
36
+ test -x $DAEMON || exit 0
37
+
38
+ d_start() {
39
+ log_begin_msg "Starting Starling Server..."
40
+ start-stop-daemon -c $DUSER:$DGROUP --start --quiet --pidfile $PIDFILE --exec $DAEMON \
41
+ -- $OPTS || log_end_msg 1
42
+ log_end_msg 0
43
+ }
44
+
45
+ d_stop() {
46
+ log_begin_msg "Stopping Starling Server..."
47
+ start-stop-daemon -c $DUSER:$DGROUP --stop --quiet --pidfile $PIDFILE \
48
+ || log_end_msg 1
49
+ log_end_msg 0
50
+ }
51
+
52
+ case "$1" in
53
+ start)
54
+ d_start
55
+ ;;
56
+ stop)
57
+ d_stop
58
+ ;;
59
+ restart|force-reload|reload)
60
+ d_stop
61
+ sleep 2
62
+ d_start
63
+ ;;
64
+ *)
65
+ echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
66
+ exit 3
67
+ ;;
68
+ esac
69
+
70
+ exit 0
71
+
data/lib/starling.rb CHANGED
@@ -6,7 +6,7 @@ class Starling < MemCache
6
6
 
7
7
  ##
8
8
  # fetch an item from a queue.
9
-
9
+
10
10
  def get(*args)
11
11
  loop do
12
12
  response = super(*args)
@@ -16,13 +16,13 @@ class Starling < MemCache
16
16
  end
17
17
 
18
18
  ##
19
- # insert +value+ into +queue+.
19
+ # insert +value+ into +queue+.
20
20
  #
21
21
  # +expiry+ is expressed as a UNIX timestamp
22
- #
22
+ #
23
23
  # If +raw+ is true, +value+ will not be Marshalled. If +raw+ = :yaml, +value+
24
24
  # will be serialized with YAML, instead.
25
-
25
+
26
26
  def set(queue, value, expiry = 0, raw = false)
27
27
  retries = 0
28
28
  begin
@@ -4,8 +4,8 @@ module StarlingServer
4
4
  # This is an internal class that's used by Starling::Server to handle the
5
5
  # MemCache protocol and act as an interface between the Server and the
6
6
  # QueueCollection.
7
-
8
- class Handler
7
+
8
+ class Handler < EventMachine::Connection
9
9
 
10
10
  DATA_PACK_FMT = "Ia*".freeze
11
11
 
@@ -13,18 +13,18 @@ module StarlingServer
13
13
  ERR_UNKNOWN_COMMAND = "CLIENT_ERROR bad command line format\r\n".freeze
14
14
 
15
15
  # GET Responses
16
- GET_COMMAND = /^get (.{1,250})\r\n$/
16
+ GET_COMMAND = /\Aget (.{1,250})\s*\r\n/m
17
17
  GET_RESPONSE = "VALUE %s %s %s\r\n%s\r\nEND\r\n".freeze
18
18
  GET_RESPONSE_EMPTY = "END\r\n".freeze
19
19
 
20
20
  # SET Responses
21
- SET_COMMAND = /^set (.{1,250}) ([0-9]+) ([0-9]+) ([0-9]+)\r\n$/
21
+ SET_COMMAND = /\Aset (.{1,250}) ([0-9]+) ([0-9]+) ([0-9]+)\r\n/m
22
22
  SET_RESPONSE_SUCCESS = "STORED\r\n".freeze
23
23
  SET_RESPONSE_FAILURE = "NOT STORED\r\n".freeze
24
24
  SET_CLIENT_DATA_ERROR = "CLIENT_ERROR bad data chunk\r\nERROR\r\n".freeze
25
25
 
26
26
  # STAT Response
27
- STATS_COMMAND = /stats\r\n$/
27
+ STATS_COMMAND = /\Astats\r\n/m
28
28
  STATS_RESPONSE = "STAT pid %d
29
29
  STAT uptime %d
30
30
  STAT time %d
@@ -47,77 +47,118 @@ STAT limit_maxbytes %d
47
47
  QUEUE_STATS_RESPONSE = "STAT queue_%s_items %d
48
48
  STAT queue_%s_total_items %d
49
49
  STAT queue_%s_logsize %d
50
- STAT queue_%s_expired_items %d\n".freeze
50
+ STAT queue_%s_expired_items %d
51
+ STAT queue_%s_age %d\r\n".freeze
52
+
53
+ SHUTDOWN_COMMAND = /\Ashutdown\r\n/m
54
+
55
+
56
+ @@next_session_id = 1
51
57
 
52
58
  ##
53
59
  # Creates a new handler for the MemCache protocol that communicates with a
54
60
  # given client.
55
61
 
56
- def initialize(client, server, queue_collection)
57
- @client = client
58
- @server = server
59
- @queue_collection = queue_collection
60
- @expiry_stats = Hash.new(0)
62
+ def initialize(options = {})
63
+ @opts = options
61
64
  end
62
65
 
63
66
  ##
64
67
  # Process incoming commands from the attached client.
65
68
 
66
- def run
67
- while running? do
68
- process_command(@client.readline)
69
- Thread.current[:last_activity] = Time.now
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)
70
94
  end
95
+
96
+ send_data response if response
71
97
  end
72
98
 
73
- private
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
74
133
 
75
- def running?
76
- !Thread.current[:shutdown]
134
+ def unbind
135
+ #@logger.debug "(#{@session_id}) connection ends"
77
136
  end
78
137
 
138
+ private
79
139
  def respond(str, *args)
80
140
  response = sprintf(str, *args)
81
141
  @server.stats[:bytes_written] += response.length
82
- @client.write response
142
+ response
83
143
  end
84
144
 
85
- def process_command(command)
86
- begin
87
- @server.stats[:bytes_read] += command.length
88
- case command
89
- when SET_COMMAND
90
- @server.stats[:set_requests] += 1
91
- set($1, $2, $3, $4.to_i)
92
- when GET_COMMAND
93
- @server.stats[:get_requests] += 1
94
- get($1)
95
- when STATS_COMMAND
96
- stats
97
- else
98
- logger.warn "Unknown command: #{command[0,4]}.\nFull command was #{command}."
99
- respond ERR_UNKNOWN_COMMAND
100
- end
101
- rescue => e
102
- logger.error "Error handling request: #{e}."
103
- logger.debug e.backtrace.join("\n")
104
- respond GET_RESPONSE_EMPTY
105
- end
145
+ def set(key, flags, expiry, len)
146
+ @expected_length = len + 2
147
+ @stash = [ key, flags, expiry ]
148
+ nil
106
149
  end
107
150
 
108
- def set(key, flags, expiry, len)
109
- data = @client.read(len)
110
- data_end = @client.read(2)
111
- @server.stats[:bytes_read] += len + 2
112
- if data_end == "\r\n" && data.size == len
113
- internal_data = [expiry.to_i, data].pack(DATA_PACK_FMT)
114
- if @queue_collection.put(key, internal_data)
115
- respond SET_RESPONSE_SUCCESS
116
- else
117
- respond SET_RESPONSE_FAILURE
118
- end
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
119
160
  else
120
- respond SET_CLIENT_DATA_ERROR
161
+ respond SET_RESPONSE_FAILURE
121
162
  end
122
163
  end
123
164
 
@@ -141,7 +182,7 @@ STAT queue_%s_expired_items %d\n".freeze
141
182
  end
142
183
 
143
184
  def stats
144
- respond STATS_RESPONSE,
185
+ respond STATS_RESPONSE,
145
186
  Process.pid, # pid
146
187
  Time.now - @server.stats(:start_time), # uptime
147
188
  Time.now.to_i, # time
@@ -169,13 +210,13 @@ STAT queue_%s_expired_items %d\n".freeze
169
210
  k, v.length,
170
211
  k, v.total_items,
171
212
  k, v.logsize,
172
- k, @expiry_stats[k])
213
+ k, @expiry_stats[k],
214
+ k, v.current_age)
173
215
  end
174
216
  end
175
217
 
176
218
  def logger
177
- @server.logger
219
+ @logger
178
220
  end
179
-
180
221
  end
181
222
  end