ernicorn 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ /.bundle
2
+ /bin
3
+ /vendor
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Tom Preston-Werner, Aman Gupta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ Ernicorn
2
+ ========
3
+
4
+ A BERT-RPC server based on [Ernie](https://github.com/mojombo/ernie)'s Ruby
5
+ interface but that uses [Unicorn](http://unicorn.bogomips.org/) for worker
6
+ process management.
7
+
8
+ Ernicorn supports BERT-RPC `call` and `cast` requests. See the full BERT-RPC
9
+ specification at [bert-rpc.org](http://bert-rpc.org) for more information.
10
+
11
+ Like Ernie before it, Ernicorn was developed at GitHub and is currently in
12
+ production use serving millions of RPC requests every day.
13
+
14
+ Installation
15
+ ------------
16
+
17
+ $ gem install ernicorn
18
+
19
+ Starting The Server
20
+ -------------------
21
+
22
+ Use the ernicorn command to start a new RPC server:
23
+
24
+ [rtomayko@iron:ernicorn]$ ernicorn --help
25
+ Ernicorn is an Ruby BERT-RPC Server based on Unicorn.
26
+
27
+ Basic Command Line Usage:
28
+ ernicorn [options] <handler>
29
+
30
+ -c, --config CONFIG Unicorn style config file
31
+ -p, --port PORT Port
32
+ -l, --log-level LOGLEVEL Log level (0-4)
33
+ -d, --detached Run as a daemon
34
+ -P, --pidfile PIDFILE Location to write pid file.
35
+
36
+ The handler must be given and should be a normal Ruby file that sets up the
37
+ environment and calls `Ernicorn.expose` for any server modules. See the
38
+ [examples/handler.rb][h] file for more info.
39
+
40
+ [h]: https://github.com/github/ernicorn/blob/master/examples/handler.rb
41
+
42
+ Control Commands
43
+ ----------------
44
+
45
+ The `ernicorn-ctrl` command can be used to send various control and
46
+ informational commands to the running server process:
47
+
48
+ $ ernicorn-ctrl --help
49
+ Usage: ernicorn-ctrl [-p <port>] <command>
50
+ Issue a control command to an ernicorn server. The port option is used to
51
+ specify a non-default control port.
52
+
53
+ Commands:
54
+ reload-handlers Gracefully reload all handler processes.
55
+ stats Dump handler stats for the server.
56
+ halt Shut down.
57
+
58
+ Ernicorn servers also support most Unicorn signals for managing worker processes
59
+ and whatnot. See the [SIGNALS](http://unicorn.bogomips.org/SIGNALS.html) file
60
+ for more info.
61
+
62
+ Development
63
+ -----------
64
+
65
+ The `script/bootstrap` command is provided to setup a local gem environment for
66
+ development.
67
+
68
+ $ script/bootstrap
69
+
70
+ It installs all gems under `vendor/gems` and creates binstubs under a `bin`
71
+ directory. This is the best way to get setup for running tests and experimenting
72
+ in a sandbox.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ task :default => :test
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'lib' << 'test'
6
+ test.pattern = 'test/**/test_*.rb'
7
+ test.verbose = true
8
+ end
data/ernicorn.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ Gem::Specification.new do |s|
2
+ s.specification_version = 2 if s.respond_to? :specification_version=
3
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
+ s.rubygems_version = '1.3.6'
5
+
6
+ s.name = 'ernicorn'
7
+ s.version = '1.0.0'
8
+ s.date = '2012-06-23'
9
+
10
+ s.summary = "Ernicorn is a BERT-RPC server implementation based on unicorn."
11
+ s.description = "Ernicorn is a BERT-RPC server packaged as a gem."
12
+
13
+ s.authors = ["Tom Preston-Werner", "tmm1", "rtomayko"]
14
+ s.email = 'tmm1@github.com'
15
+ s.homepage = 'https://github.com/github/ernicorn'
16
+
17
+ s.add_runtime_dependency('bert', ">= 1.1.0")
18
+ s.add_runtime_dependency('bertrpc', ">= 1.0.0")
19
+ s.add_runtime_dependency('unicorn', "~> 4.1.1")
20
+
21
+ s.add_development_dependency('shoulda', "~> 2.11.3")
22
+
23
+ s.files = `git ls-files`.split("\n") - %w[Gemfile Gemfile.lock]
24
+ s.test_files = `git ls-files -- test`.split("\n").select { |f| f =~ /_test.rb$/ }
25
+
26
+ s.bindir = "script"
27
+ s.executables = %w[ernicorn ernicorn-ctrl]
28
+ s.require_paths = %w[lib]
29
+ s.extra_rdoc_files = %w[LICENSE README.md]
30
+ end
@@ -0,0 +1,13 @@
1
+ require 'bertrpc'
2
+ service = BERTRPC::Service.new('localhost', 9777)
3
+
4
+ loop do
5
+ begin
6
+ puts ">> add(10, 32)"
7
+ res = service.call.example.send(:add, 10, 32)
8
+ puts "=> #{res.inspect}"
9
+ rescue => boom
10
+ puts "ERROR: #{boom.class} #{boom}"
11
+ end
12
+ sleep 1
13
+ end
@@ -0,0 +1,36 @@
1
+ # Ernicorn configuration
2
+ #
3
+ # Ernicorn config files typically live at config/ernicorn.rb and support all
4
+ # Unicorn config options:
5
+ #
6
+ # http://unicorn.bogomips.org/Unicorn/Configurator.html
7
+ #
8
+ # Example unicorn config files:
9
+ #
10
+ # http://unicorn.bogomips.org/examples/unicorn.conf.rb
11
+ # http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
12
+ warn "in unicorn config file: #{__FILE__}"
13
+
14
+ # server options
15
+ listen 9777
16
+ worker_processes 2
17
+ working_directory File.dirname(__FILE__)
18
+
19
+ # ernicorn configuration
20
+ Ernicorn.loglevel 1
21
+
22
+ # enable COW where possible
23
+ GC.respond_to?(:copy_on_write_friendly=) &&
24
+ GC.copy_on_write_friendly = true
25
+
26
+ # load server code and expose modules
27
+ require File.expand_path('../handler', __FILE__)
28
+ Ernicorn.expose(:example, Example)
29
+
30
+ # hook into new child immediately after forking
31
+ after_fork do |server, worker|
32
+ end
33
+
34
+ # hook into master immediately before forking a worker
35
+ before_fork do |server, worker|
36
+ end
@@ -0,0 +1,49 @@
1
+ # An example BERTRPC server module. To start the RPC server use:
2
+ #
3
+ # $ bin/ernicorn -p 9675 examples/handler.rb
4
+ #
5
+ # Then connect and issue commands as follows:
6
+ #
7
+ # $ script/console
8
+ # >> require 'bertrpc'
9
+ # >> service = BERTRPC::Service.new('localhost', 9675)
10
+ # => #<BERTRPC::Service:0x1037e9678 @port=9765, @timeout=nil, @host="localhost">
11
+ # >> service.call.example.send(:add, 1, 2)
12
+ # => 3
13
+ # >> service.call.example.send(:fib, 10)
14
+ # => 89
15
+
16
+ module Example
17
+ # Add two numbers together
18
+ def add(a, b)
19
+ a + b
20
+ end
21
+
22
+ def fib(n)
23
+ if n == 0 || n == 1
24
+ 1
25
+ else
26
+ fib(n - 1) + fib(n - 2)
27
+ end
28
+ end
29
+
30
+ def shadow(x)
31
+ "ruby"
32
+ end
33
+
34
+ # Return the given number of bytes
35
+ def bytes(n)
36
+ 'x' * n
37
+ end
38
+
39
+ # Sleep for +sec+ and then return :ok
40
+ def slow(sec)
41
+ sleep(sec)
42
+ :ok
43
+ end
44
+
45
+ # Throw an error
46
+ def error
47
+ raise "abandon hope!"
48
+ end
49
+ end
data/examples/start.sh ADDED
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ # Start the example ernicorn server on port 9777.
3
+ cd "$(dirname "$0")"
4
+ exec ernicorn -p 9777 config.rb
@@ -0,0 +1,39 @@
1
+ require 'unicorn'
2
+
3
+ module Ernicorn
4
+ module AdminRPC
5
+ def stats
6
+ queued = active = 0
7
+
8
+ Raindrops::Linux.tcp_listener_stats(Unicorn.listener_names).each do |addr,stats|
9
+ queued += stats.queued
10
+ active += stats.active
11
+ end if defined?(Raindrops::Linux.tcp_listener_stats)
12
+
13
+ return <<STATS
14
+ connections.total=#{Stats.connections_total}
15
+ connections.completed=#{Stats.connections_completed}
16
+ workers.idle=#{Stats.workers_idle}
17
+ workers.busy=#{active}
18
+ queue.high=#{queued}
19
+ queue.low=0
20
+ STATS
21
+ end
22
+
23
+ def reload_handlers
24
+ Process.kill 'USR2', master_pid
25
+ "Sent USR2 to #{master_pid}"
26
+ end
27
+
28
+ def halt
29
+ Process.kill 'QUIT', master_pid
30
+ "Sent QUIT to #{master_pid}"
31
+ end
32
+
33
+ def master_pid
34
+ $ernicorn.master_pid
35
+ end
36
+ end
37
+ end
38
+
39
+ Ernicorn.expose(:__admin__, Ernicorn::AdminRPC)
@@ -0,0 +1,82 @@
1
+ require 'unicorn'
2
+
3
+ module Ernicorn
4
+ Stats = Raindrops::Struct.new(:connections_total,
5
+ :connections_completed,
6
+ :workers_idle).new
7
+
8
+ class Server < Unicorn::HttpServer
9
+
10
+ # Private HttpServer methods we're overriding to
11
+ # implement BertRPC instead of HTTP.
12
+
13
+ def initialize(app, options={})
14
+ super app, options
15
+ end
16
+
17
+ def build_app!
18
+ end
19
+
20
+ def worker_loop(worker)
21
+ Stats.incr_workers_idle
22
+
23
+ if worker.nr == (self.worker_processes - 1)
24
+ old_pid = "#{self.pid}.oldbin"
25
+ if File.exists?(old_pid) && self.pid != old_pid
26
+ begin
27
+ Process.kill("QUIT", File.read(old_pid).to_i)
28
+ rescue Errno::ENOENT, Errno::ESRCH
29
+ # someone else did our job for us
30
+ end
31
+ end
32
+ end
33
+
34
+ super
35
+ ensure
36
+ Stats.decr_workers_idle
37
+ end
38
+
39
+ def process_client(client)
40
+ Stats.decr_workers_idle
41
+ Stats.incr_connections_total
42
+
43
+ @client = client
44
+ # bail out if client only sent EOF
45
+ return if @client.kgio_trypeek(1).nil?
46
+
47
+ iruby, oruby = Ernicorn.process(self, self)
48
+ rescue EOFError
49
+ logger.error("EOF from #{@client.kgio_addr rescue nil}")
50
+ rescue Object => e
51
+ logger.error(e)
52
+
53
+ begin
54
+ error = t[:error, t[:server, 0, e.class.to_s, e.message, e.backtrace]]
55
+ Ernicorn.write_berp(self, error)
56
+ rescue Object => ex
57
+ logger.error(ex)
58
+ end
59
+ ensure
60
+ @client.close rescue nil
61
+ @client = nil
62
+
63
+ Stats.incr_connections_completed
64
+ Stats.incr_workers_idle
65
+ end
66
+
67
+ # We pass ourselves as both input and output to Ernicorn.process, which
68
+ # calls the following blocking read/write methods.
69
+
70
+ def read(len)
71
+ data = ''
72
+ while data.bytesize < len
73
+ data << @client.kgio_read!(len - data.bytesize)
74
+ end
75
+ data
76
+ end
77
+
78
+ def write(data)
79
+ @client.kgio_write(data)
80
+ end
81
+ end
82
+ end
data/lib/ernicorn.rb ADDED
@@ -0,0 +1,252 @@
1
+ require 'bert'
2
+ require 'logger'
3
+
4
+ module Ernicorn
5
+ VERSION = '1.0.0'
6
+
7
+ class << self
8
+ attr_accessor :mods, :current_mod, :log
9
+ attr_accessor :auto_start
10
+ attr_accessor :count, :virgin_procline
11
+ end
12
+
13
+ self.count = 0
14
+ self.virgin_procline = $0
15
+ self.mods = {}
16
+ self.current_mod = nil
17
+ self.log = Logger.new(STDOUT)
18
+ self.log.level = Logger::FATAL
19
+ self.auto_start = true
20
+
21
+ # Record a module.
22
+ # +name+ is the module Symbol
23
+ # +block+ is the Block containing function definitions
24
+ #
25
+ # Returns nothing
26
+ def self.mod(name, block)
27
+ m = Mod.new(name)
28
+ self.current_mod = m
29
+ self.mods[name] = m
30
+ block.call
31
+ end
32
+
33
+ # Record a function.
34
+ # +name+ is the function Symbol
35
+ # +block+ is the Block to associate
36
+ #
37
+ # Returns nothing
38
+ def self.fun(name, block)
39
+ self.current_mod.fun(name, block)
40
+ end
41
+
42
+ # Expose all public methods in a Ruby module:
43
+ # +name+ is the ernie module Symbol
44
+ # +mixin+ is the ruby module whose public methods are exposed
45
+ #
46
+ # Returns nothing
47
+ def self.expose(name, mixin)
48
+ context = Object.new
49
+ context.extend mixin
50
+ mod(name, lambda {
51
+ mixin.public_instance_methods.each do |meth|
52
+ fun(meth.to_sym, context.method(meth))
53
+ end
54
+ })
55
+ if defined? mixin.dispatched
56
+ self.current_mod.logger = mixin.method(:dispatched)
57
+ end
58
+ context
59
+ end
60
+
61
+ # Set the logfile to given path.
62
+ # +file+ is the String path to the logfile
63
+ #
64
+ # Returns nothing
65
+ def self.logfile(file)
66
+ self.log = Logger.new(file)
67
+ end
68
+
69
+ # Set the log level.
70
+ # +level+ is the Logger level (Logger::WARN, etc)
71
+ #
72
+ # Returns nothing
73
+ def self.loglevel(level)
74
+ self.log.level = level
75
+ end
76
+
77
+ # Dispatch the request to the proper mod:fun.
78
+ # +mod+ is the module Symbol
79
+ # +fun+ is the function Symbol
80
+ # +args+ is the Array of arguments
81
+ #
82
+ # Returns the Ruby object response
83
+ def self.dispatch(mod, fun, args)
84
+ self.mods[mod] || raise(ServerError.new("No such module '#{mod}'"))
85
+ self.mods[mod].funs[fun] || raise(ServerError.new("No such function '#{mod}:#{fun}'"))
86
+
87
+ start = Time.now
88
+ ret = self.mods[mod].funs[fun].call(*args)
89
+
90
+ begin
91
+ self.mods[mod].logger and
92
+ self.mods[mod].logger.call(Time.now-start, [fun, *args], ret)
93
+ rescue Object => e
94
+ self.log.fatal(e)
95
+ end
96
+
97
+ ret
98
+ end
99
+
100
+ # Read the length header from the wire.
101
+ # +input+ is the IO from which to read
102
+ #
103
+ # Returns the size Integer if one was read
104
+ # Returns nil otherwise
105
+ def self.read_4(input)
106
+ raw = input.read(4)
107
+ return nil unless raw
108
+ raw.unpack('N').first
109
+ end
110
+
111
+ # Read a BERP from the wire and decode it to a Ruby object.
112
+ # +input+ is the IO from which to read
113
+ #
114
+ # Returns a Ruby object if one could be read
115
+ # Returns nil otherwise
116
+ def self.read_berp(input)
117
+ packet_size = self.read_4(input)
118
+ return nil unless packet_size
119
+ bert = input.read(packet_size)
120
+ BERT.decode(bert)
121
+ end
122
+
123
+ # Write the given Ruby object to the wire as a BERP.
124
+ # +output+ is the IO on which to write
125
+ # +ruby+ is the Ruby object to encode
126
+ #
127
+ # Returns nothing
128
+ def self.write_berp(output, ruby)
129
+ data = BERT.encode(ruby)
130
+ output.write([data.length].pack("N"))
131
+ output.write(data)
132
+ end
133
+
134
+ # Start the processing loop.
135
+ #
136
+ # Loops forever
137
+ def self.start
138
+ self.procline('starting')
139
+ self.log.info("(#{Process.pid}) Starting") if self.log.level <= Logger::INFO
140
+ self.log.debug(self.mods.inspect) if self.log.level <= Logger::DEBUG
141
+
142
+ input = IO.new(3)
143
+ output = IO.new(4)
144
+ input.sync = true
145
+ output.sync = true
146
+
147
+ loop do
148
+ process(input, output)
149
+ end
150
+ end
151
+
152
+ # Processes a single BertRPC command.
153
+ #
154
+ # input - The IO to #read command BERP from.
155
+ # output - The IO to #write reply BERP to.
156
+ #
157
+ # Returns a [iruby, oruby] tuple of incoming and outgoing BERPs processed.
158
+ def self.process(input, output)
159
+ self.procline('waiting')
160
+ iruby = self.read_berp(input)
161
+ self.count += 1
162
+
163
+ unless iruby
164
+ puts "Could not read BERP length header. Ernicorn server may have gone away. Exiting now."
165
+ if self.log.level <= Logger::INFO
166
+ self.log.info("(#{Process.pid}) Could not read BERP length header. Ernicorn server may have gone away. Exiting now.")
167
+ end
168
+ exit!
169
+ end
170
+
171
+ if iruby.size == 4 && iruby[0] == :call
172
+ mod, fun, args = iruby[1..3]
173
+ self.procline("#{mod}:#{fun}(#{args})")
174
+ self.log.info("-> " + iruby.inspect) if self.log.level <= Logger::INFO
175
+ begin
176
+ res = self.dispatch(mod, fun, args)
177
+ oruby = t[:reply, res]
178
+ self.log.debug("<- " + oruby.inspect) if self.log.level <= Logger::DEBUG
179
+ write_berp(output, oruby)
180
+ rescue ServerError => e
181
+ oruby = t[:error, t[:server, 0, e.class.to_s, e.message, e.backtrace]]
182
+ self.log.error("<- " + oruby.inspect) if self.log.level <= Logger::ERROR
183
+ self.log.error(e.backtrace.join("\n")) if self.log.level <= Logger::ERROR
184
+ write_berp(output, oruby)
185
+ rescue Object => e
186
+ oruby = t[:error, t[:user, 0, e.class.to_s, e.message, e.backtrace]]
187
+ self.log.error("<- " + oruby.inspect) if self.log.level <= Logger::ERROR
188
+ self.log.error(e.backtrace.join("\n")) if self.log.level <= Logger::ERROR
189
+ write_berp(output, oruby)
190
+ end
191
+ elsif iruby.size == 4 && iruby[0] == :cast
192
+ mod, fun, args = iruby[1..3]
193
+ self.procline("#{mod}:#{fun}(#{args})")
194
+ self.log.info("-> " + [:cast, mod, fun, args].inspect) if self.log.level <= Logger::INFO
195
+ oruby = t[:noreply]
196
+ write_berp(output, oruby)
197
+
198
+ begin
199
+ self.dispatch(mod, fun, args)
200
+ rescue Object => e
201
+ # ignore
202
+ end
203
+ else
204
+ self.procline("invalid request")
205
+ self.log.error("-> " + iruby.inspect) if self.log.level <= Logger::ERROR
206
+ oruby = t[:error, t[:server, 0, "Invalid request: #{iruby.inspect}"]]
207
+ self.log.error("<- " + oruby.inspect) if self.log.level <= Logger::ERROR
208
+ write_berp(output, oruby)
209
+ end
210
+
211
+ self.procline('waiting')
212
+ [iruby, oruby]
213
+ end
214
+
215
+ def self.procline(msg)
216
+ $0 = "ernicorn handler #{VERSION} (ruby) - #{self.virgin_procline} - [#{self.count}] #{msg}"[0..159]
217
+ end
218
+
219
+ def self.version
220
+ VERSION
221
+ end
222
+
223
+ class ServerError < StandardError; end
224
+
225
+ class Ernicorn::Mod
226
+ attr_accessor :name, :funs, :logger
227
+
228
+ def initialize(name)
229
+ self.name = name
230
+ self.funs = {}
231
+ self.logger = nil
232
+ end
233
+
234
+ def fun(name, block)
235
+ raise TypeError, "block required" if block.nil?
236
+ self.funs[name] = block
237
+ end
238
+ end
239
+ end
240
+ # Root level calls
241
+
242
+ def logfile(name)
243
+ Ernicorn.logfile(name)
244
+ end
245
+
246
+ def loglevel(level)
247
+ Ernicorn.loglevel(level)
248
+ end
249
+
250
+ at_exit do
251
+ Ernicorn.start if Ernicorn.auto_start && !defined?(Ernicorn)
252
+ end
data/script/bootstrap ADDED
@@ -0,0 +1,11 @@
1
+ #!/bin/sh
2
+ # Usage: script/bootstrap [--local]
3
+ # Prepare the local gem environment.
4
+ set -e
5
+
6
+ cd "$(dirname "$0")/.."
7
+ if bundle check 1>/dev/null 2>&1; then
8
+ echo "Gem environment up-to-date"
9
+ else
10
+ exec bundle install --binstubs --path vendor/gems "$@"
11
+ fi
data/script/cibuild ADDED
@@ -0,0 +1,23 @@
1
+ #!/bin/sh
2
+ # Usage: script/cibuild
3
+ # CI build script.
4
+ # This is tailored for the janky build machines.
5
+ set -e
6
+
7
+ # change into root dir and setup path
8
+ cd $(dirname "$0")/..
9
+ PATH="$(pwd)/bin:$(pwd)/script:/usr/share/rbenv/shims:$PATH"
10
+
11
+ # Write commit we're building at
12
+ git log -n 1 || true
13
+ echo
14
+
15
+ echo "Building under 1.8.7"
16
+ bootstrap
17
+ testsuite
18
+ echo
19
+
20
+ echo "Building under 1.9.3"
21
+ export RBENV_VERSION="${1:-1.9.3-p0}"
22
+ bootstrap
23
+ testsuite
data/script/ernicorn ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ #/ Usage: ernicorn [options] [config file]
3
+ #/ Start a Ruby BERT-RPC Server with the given options and config file.
4
+ #/
5
+ #/ Options
6
+ #/ -h, --host=<host> Server address to listen on; default: 0.0.0.0
7
+ #/ -p, --port=<portno> Server port to listen on; default: 8000
8
+ #/ --log-level=0-4 Set the log level
9
+ #/ -d, --detached Run as a daemon
10
+ #/ -P, --pidfile=<file> Location to write pid file
11
+ #/
12
+ #/ See https://github.com/github/ernicorn for more information.
13
+ require 'optparse'
14
+ require 'ernicorn'
15
+ require 'ernicorn/server'
16
+ require 'ernicorn/adminrpc'
17
+
18
+ Ernicorn.auto_start = false
19
+
20
+ host = '0.0.0.0'
21
+ port = 8000
22
+ daemonize = false
23
+ config = 'config/ernicorn.rb'
24
+ pidfile = nil
25
+
26
+ parser = OptionParser.new do |opts|
27
+ opts.on("-c", "--config=val", String) { |config| }
28
+ opts.on("-h", "--host=val", Integer) { |host| }
29
+ opts.on("-p", "--port=val", Integer) { |port| }
30
+ opts.on( "--log-level=val", Integer) { |val| Ernicorn.loglevel(val) }
31
+ opts.on("-d", "--detached") { |daemonize| }
32
+ opts.on("-P", "--pidfile=val") { |pidfile| }
33
+ opts.on_tail("-h", "--help") { exec "grep ^#/<'#{__FILE__}'|cut -c4-" }
34
+ opts.parse!
35
+ end
36
+
37
+ config = ARGV[0] || config
38
+
39
+ options = {
40
+ :pid => pidfile,
41
+ :listeners => ["#{host}:#{port}"],
42
+ :config_file => File.expand_path(config)
43
+ }
44
+
45
+ if daemonize
46
+ require 'unicorn/launcher'
47
+ Unicorn::Launcher.daemonize!(options)
48
+ end
49
+
50
+ $ernicorn = Ernicorn::Server.new(nil, options)
51
+ $ernicorn.start.join
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ #/ Usage: ernicorn-ctrl [-p <port>] <command>
3
+ #/ Issue a control command to an ernicorn server. The port option is used to
4
+ #/ specify a non-default control port.
5
+ #/
6
+ #/ Commands:
7
+ #/ reload-handlers Gracefully reload all handler processes.
8
+ #/ stats Dump handler stats for the server.
9
+ #/ halt Shut down.
10
+ require 'optparse'
11
+ require 'bertrpc'
12
+
13
+ port = 8000
14
+
15
+ def usage(status=0)
16
+ exec "grep ^#/<'#{__FILE__}'|cut -c4-; exit #{status}"
17
+ end
18
+
19
+ # parse arguments
20
+ ARGV.options do |opts|
21
+ opts.on("-p", "--port=val", Integer) { |port| }
22
+ opts.on_tail("-h", "--help") { usage }
23
+ opts.parse!
24
+ end
25
+
26
+ # the control command
27
+ command = ARGV[0]
28
+
29
+ # bail out with usage if the command wasn't specified or is invalid
30
+ if !%w[reload-handlers stats halt].include?(command)
31
+ usage(1)
32
+ end
33
+
34
+ # connect to the adminrpc handler and send the command
35
+ require 'bertrpc'
36
+ service = BERTRPC::Service.new('localhost', port)
37
+ puts service.call.__admin__.send(command.gsub(/-/, '_'))
data/script/testsuite ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+ set -e
3
+ cd "$(dirname "$0")/.."
4
+
5
+ # run entire test suite
6
+ ruby --version 1>&2
7
+ bundle exec ruby -I "$(pwd)" -e "ARGV.each { |f| require(f) }" -- $(find test -name 'test_*.rb')
data/test/helper.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'test/unit'
2
+ require 'shoulda'
3
+ require 'ernicorn'
4
+ Ernicorn.auto_start = false
@@ -0,0 +1,69 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class ErnicornTest < Test::Unit::TestCase
4
+ module TestExposingModule
5
+ def foo
6
+ end
7
+
8
+ def bar(a, b, c=nil)
9
+ [a, b, c]
10
+ end
11
+
12
+ protected
13
+ def baz
14
+ end
15
+
16
+ private
17
+ def bling
18
+ end
19
+ end
20
+
21
+ context "expose" do
22
+ setup { Ernicorn.expose :expo, TestExposingModule }
23
+ teardown { Ernicorn.mods.clear }
24
+
25
+ should "add all public methods from the module" do
26
+ assert_not_nil Ernicorn.mods[:expo].funs[:foo]
27
+ assert_not_nil Ernicorn.mods[:expo].funs[:bar]
28
+ end
29
+
30
+ should "not expose protected methods" do
31
+ assert_nil Ernicorn.mods[:expo].funs[:baz]
32
+ end
33
+
34
+ should "not expose private methods" do
35
+ assert_nil Ernicorn.mods[:expo].funs[:bling]
36
+ end
37
+
38
+ should "dispatch to module methods properly" do
39
+ res = Ernicorn.dispatch(:expo, :bar, ['a', :b, { :fiz => 42 }])
40
+ assert_equal ['a', :b, { :fiz => 42 }], res
41
+ end
42
+ end
43
+
44
+ module TestLoggingModule
45
+ def self.logs() @logs ||= [] end
46
+
47
+ def self.dispatched(*args)
48
+ logs << args
49
+ end
50
+
51
+ def sleepy(time)
52
+ sleep(time)
53
+ time
54
+ end
55
+ end
56
+
57
+ context "logging" do
58
+ setup { Ernicorn.expose :logging, TestLoggingModule; TestLoggingModule.logs.clear }
59
+ teardown { Ernicorn.mods.clear }
60
+
61
+ should "call logger method with timing" do
62
+ ret = Ernicorn.dispatch(:logging, :sleepy, 0.1)
63
+ assert_equal 0.1, ret
64
+
65
+ assert_equal 1, TestLoggingModule.logs.size
66
+ assert_in_delta 0.1, TestLoggingModule.logs.first[0], 0.05
67
+ end
68
+ end
69
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ernicorn
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Tom Preston-Werner
13
+ - tmm1
14
+ - rtomayko
15
+ autorequire:
16
+ bindir: script
17
+ cert_chain: []
18
+
19
+ date: 2012-06-23 00:00:00 -07:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: bert
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ segments:
30
+ - 1
31
+ - 1
32
+ - 0
33
+ version: 1.1.0
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: bertrpc
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 1
45
+ - 0
46
+ - 0
47
+ version: 1.0.0
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: unicorn
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 4
59
+ - 1
60
+ - 1
61
+ version: 4.1.1
62
+ type: :runtime
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ name: shoulda
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ~>
70
+ - !ruby/object:Gem::Version
71
+ segments:
72
+ - 2
73
+ - 11
74
+ - 3
75
+ version: 2.11.3
76
+ type: :development
77
+ version_requirements: *id004
78
+ description: Ernicorn is a BERT-RPC server packaged as a gem.
79
+ email: tmm1@github.com
80
+ executables:
81
+ - ernicorn
82
+ - ernicorn-ctrl
83
+ extensions: []
84
+
85
+ extra_rdoc_files:
86
+ - LICENSE
87
+ - README.md
88
+ files:
89
+ - .gitignore
90
+ - LICENSE
91
+ - README.md
92
+ - Rakefile
93
+ - ernicorn.gemspec
94
+ - examples/client.rb
95
+ - examples/config.rb
96
+ - examples/handler.rb
97
+ - examples/start.sh
98
+ - lib/ernicorn.rb
99
+ - lib/ernicorn/adminrpc.rb
100
+ - lib/ernicorn/server.rb
101
+ - script/bootstrap
102
+ - script/cibuild
103
+ - script/ernicorn
104
+ - script/ernicorn-ctrl
105
+ - script/testsuite
106
+ - test/helper.rb
107
+ - test/test_ernicorn.rb
108
+ has_rdoc: true
109
+ homepage: https://github.com/github/ernicorn
110
+ licenses: []
111
+
112
+ post_install_message:
113
+ rdoc_options: []
114
+
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ segments:
122
+ - 0
123
+ version: "0"
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ segments:
129
+ - 0
130
+ version: "0"
131
+ requirements: []
132
+
133
+ rubyforge_project:
134
+ rubygems_version: 1.3.6
135
+ signing_key:
136
+ specification_version: 2
137
+ summary: Ernicorn is a BERT-RPC server implementation based on unicorn.
138
+ test_files: []
139
+