ernie 0.3.1
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/.document +5 -0
- data/.gitignore +2 -0
- data/LICENSE +20 -0
- data/README.md +105 -0
- data/Rakefile +65 -0
- data/VERSION.yml +4 -0
- data/bin/ernie +97 -0
- data/ebin/ernie_server_app.app +2 -0
- data/elib/asset_pool.erl +137 -0
- data/elib/asset_pool_sup.erl +15 -0
- data/elib/ernie_server.erl +175 -0
- data/elib/ernie_server_app.erl +15 -0
- data/elib/ernie_server_sup.erl +19 -0
- data/elib/logger.erl +108 -0
- data/elib/logger_sup.erl +13 -0
- data/elib/port_wrapper.erl +67 -0
- data/ernie.gemspec +71 -0
- data/examples/calc.rb +8 -0
- data/ext/Makefile +2 -0
- data/ext/extconf.rb +1 -0
- data/lib/ernie.rb +126 -0
- data/test/ernie_test.rb +18 -0
- data/test/load.rb +14 -0
- data/test/test_helper.rb +12 -0
- metadata +101 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Tom Preston-Werner
|
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,105 @@
|
|
1
|
+
Ernie
|
2
|
+
=====
|
3
|
+
|
4
|
+
By Tom Preston-Werner (tom@mojombo.com)
|
5
|
+
|
6
|
+
WARNING: This software is alpha and should not be used in production without
|
7
|
+
extensive testing. You should not consider this project production ready until
|
8
|
+
it is released as 1.0.
|
9
|
+
|
10
|
+
|
11
|
+
Description
|
12
|
+
-----------
|
13
|
+
|
14
|
+
Ernie is a BERT-RPC server implementation that uses an Erlang server to accept incoming connections, and then delegates the request to a Ruby handler via Erlectricity.
|
15
|
+
|
16
|
+
|
17
|
+
Installation
|
18
|
+
------------
|
19
|
+
|
20
|
+
You must have Erlang installed before installing Ernie.
|
21
|
+
|
22
|
+
gem install mojombo-ernie -s http://gems.github.com
|
23
|
+
|
24
|
+
|
25
|
+
Running
|
26
|
+
-------
|
27
|
+
|
28
|
+
Usage: ernie [command] [options]
|
29
|
+
-h, --handler HANDLER Handler file
|
30
|
+
-p, --port PORT Port
|
31
|
+
-n, --number NUMBER Number of handler instances
|
32
|
+
-d, --detached Run as a daemon
|
33
|
+
-P, --pidfile PIDFILE Location to write pid file.
|
34
|
+
|
35
|
+
Commands:
|
36
|
+
<none> Start an Ernie server.
|
37
|
+
reload-handlers Gracefully reload all of the the ruby handlers
|
38
|
+
and use the new code for all subsequent requests.
|
39
|
+
|
40
|
+
Examples:
|
41
|
+
ernie -d -p 9999 -n 10 -h calc.rb
|
42
|
+
Start the ernie server in the background on port 9999 with ten
|
43
|
+
handlers, using the calc.rb handler file.
|
44
|
+
|
45
|
+
ernie reload-handlers -p 9999
|
46
|
+
Reload the handlers for the ernie server currently running on
|
47
|
+
port 9999.
|
48
|
+
|
49
|
+
Example Handler
|
50
|
+
---------------
|
51
|
+
|
52
|
+
require 'ernie'
|
53
|
+
|
54
|
+
mod(:calc) do
|
55
|
+
fun(:add) do |a, b|
|
56
|
+
a + b
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
Example BERT-RPC call for above example
|
62
|
+
---------------------------------------
|
63
|
+
|
64
|
+
-> {call, calc, add, [1, 2]}
|
65
|
+
|
66
|
+
<- {reply, 3}
|
67
|
+
|
68
|
+
|
69
|
+
Using the BERTRPC gem to make calls to Ernie
|
70
|
+
--------------------------------------------
|
71
|
+
|
72
|
+
You can make BERT-RPC calls from Ruby with the [BERTRPC gem](http://github.com/mojombo/bertrpc):
|
73
|
+
|
74
|
+
require 'bertrpc'
|
75
|
+
|
76
|
+
svc = BERTRPC::Service.new('localhost', 8000)
|
77
|
+
svc.call.calc.add(1, 2)
|
78
|
+
# => 3
|
79
|
+
|
80
|
+
|
81
|
+
Contribute
|
82
|
+
----------
|
83
|
+
|
84
|
+
If you'd like to hack on Ernie, start by forking my repo on GitHub:
|
85
|
+
|
86
|
+
http://github.com/mojombo/ernie
|
87
|
+
|
88
|
+
To get all of the dependencies, install the gem first. The best way to get
|
89
|
+
your changes merged back into core is as follows:
|
90
|
+
|
91
|
+
1. Clone down your fork
|
92
|
+
1. Create a topic branch to contain your change
|
93
|
+
1. Hack away
|
94
|
+
1. Add tests and make sure everything still passes by running `rake`
|
95
|
+
1. If you are adding new functionality, document it in the README.md
|
96
|
+
1. Do not change the version number, I will do that on my end
|
97
|
+
1. If necessary, rebase your commits into logical chunks, without errors
|
98
|
+
1. Push the branch up to GitHub
|
99
|
+
1. Send me (mojombo) a pull request for your branch
|
100
|
+
|
101
|
+
|
102
|
+
Copyright
|
103
|
+
---------
|
104
|
+
|
105
|
+
Copyright (c) 2009 Tom Preston-Werner. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "ernie"
|
8
|
+
gem.summary = %Q{Ernie is a BERT-RPC server implementation.}
|
9
|
+
gem.email = "tom@mojombo.com"
|
10
|
+
gem.homepage = "http://github.com/mojombo/ernie"
|
11
|
+
gem.authors = ["Tom Preston-Werner"]
|
12
|
+
gem.files.include(["ext"])
|
13
|
+
gem.extensions << 'ext/extconf.rb'
|
14
|
+
gem.add_dependency('erlectricity', '>= 1.0.1')
|
15
|
+
gem.add_dependency('bertrpc', '>= 0.2.0')
|
16
|
+
|
17
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
|
+
end
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'rake/testtask'
|
24
|
+
Rake::TestTask.new(:test) do |test|
|
25
|
+
test.libs << 'lib' << 'test'
|
26
|
+
test.pattern = 'test/**/*_test.rb'
|
27
|
+
test.verbose = true
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
require 'rcov/rcovtask'
|
32
|
+
Rcov::RcovTask.new do |test|
|
33
|
+
test.libs << 'test'
|
34
|
+
test.pattern = 'test/**/*_test.rb'
|
35
|
+
test.verbose = true
|
36
|
+
end
|
37
|
+
rescue LoadError
|
38
|
+
task :rcov do
|
39
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
# require 'rake/rdoctask'
|
46
|
+
# Rake::RDocTask.new do |rdoc|
|
47
|
+
# if File.exist?('VERSION.yml')
|
48
|
+
# config = YAML.load(File.read('VERSION.yml'))
|
49
|
+
# version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
50
|
+
# else
|
51
|
+
# version = ""
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# rdoc.rdoc_dir = 'rdoc'
|
55
|
+
# rdoc.title = "ernie #{version}"
|
56
|
+
# rdoc.rdoc_files.include('README*')
|
57
|
+
# rdoc.rdoc_files.include('lib/**/*.rb')
|
58
|
+
# end
|
59
|
+
|
60
|
+
task :ebuild do
|
61
|
+
ERLC_TEST_FLAGS = ""
|
62
|
+
ERLC_FLAGS = "-o ../ebin"
|
63
|
+
cd "elib"
|
64
|
+
sh "erlc #{ERLC_FLAGS} #{ERLC_TEST_FLAGS} #{Dir["**/*.erl"].join(" ")}"
|
65
|
+
end
|
data/VERSION.yml
ADDED
data/bin/ernie
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift(File.join(File.dirname(__FILE__), *%w[.. lib]))
|
4
|
+
ERNIE_ROOT = File.join(File.dirname(__FILE__), *%w[..])
|
5
|
+
|
6
|
+
DEFAULT_ERLANG_CODEPATHS = %w[ebin]
|
7
|
+
DEFAULT_PORT = 8000
|
8
|
+
|
9
|
+
def rel(path)
|
10
|
+
File.join(ERNIE_ROOT, path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def code_paths
|
14
|
+
DEFAULT_ERLANG_CODEPATHS.map {|n| "-pz #{rel(n)}" }.join(" ") + " \\"
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'optparse'
|
18
|
+
require 'pp'
|
19
|
+
|
20
|
+
help = <<HELP
|
21
|
+
Ernie is an Erlang/Ruby BERT-RPC Server.
|
22
|
+
|
23
|
+
Basic Command Line Usage:
|
24
|
+
ernie [options] <path to handler file>
|
25
|
+
|
26
|
+
Options:
|
27
|
+
HELP
|
28
|
+
|
29
|
+
options = {}
|
30
|
+
OptionParser.new do |opts|
|
31
|
+
opts.banner = help
|
32
|
+
|
33
|
+
opts.on("-h HANDLER", "--handler HANDLER", "Handler ruby file") do |x|
|
34
|
+
options[:handler] = x
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on("-p PORT", "--port PORT", "Port") do |x|
|
38
|
+
options[:port] = x
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on("-n NUMBER", "--number NUMBER", "Number of handler instances") do |x|
|
42
|
+
options[:number] = x
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on("-l LOGLEVEL", "--log-level LOGLEVEL", "Log level (0-4)") do |x|
|
46
|
+
options[:log_level] = x
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on("-d", "--detached", "Run as a daemon") do
|
50
|
+
options[:detached] = true
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on("-P", "--pidfile PIDFILE", "Location to write pid file.") do |x|
|
54
|
+
options[:pidfile] = x
|
55
|
+
end
|
56
|
+
end.parse!
|
57
|
+
|
58
|
+
if command = ARGV[0]
|
59
|
+
if !%w{reload-handlers}.include?(command)
|
60
|
+
puts "Invlalid command. Valid commands are:"
|
61
|
+
puts " reload-handlers"
|
62
|
+
exit(1)
|
63
|
+
end
|
64
|
+
|
65
|
+
require 'rubygems'
|
66
|
+
require 'bertrpc'
|
67
|
+
port = options[:port] || DEFAULT_PORT
|
68
|
+
svc = BERTRPC::Service.new('localhost', port)
|
69
|
+
puts svc.call.__admin__.send(command.gsub(/-/, '_'))
|
70
|
+
else
|
71
|
+
if !options[:handler]
|
72
|
+
puts "A handler must be specified: ernie -h /path/to/handler.rb"
|
73
|
+
exit(1)
|
74
|
+
end
|
75
|
+
|
76
|
+
handler = options[:handler]
|
77
|
+
port = options[:port] || DEFAULT_PORT
|
78
|
+
number = options[:number] || 1
|
79
|
+
log_level = options[:log_level] || 2
|
80
|
+
pidfile = options[:pidfile] ? "-ernie_server_app pidfile \"'#{options[:pidfile]}'\"" : ''
|
81
|
+
detached = options[:detached] ? '-detached' : ''
|
82
|
+
|
83
|
+
cmd = %Q{erl -boot start_sasl \
|
84
|
+
#{detached} \
|
85
|
+
+Bc \
|
86
|
+
+K true \
|
87
|
+
-smp enable \
|
88
|
+
#{code_paths}
|
89
|
+
#{pidfile} \
|
90
|
+
-ernie_server_app port #{port} \
|
91
|
+
-ernie_server_app handler '"#{handler}"' \
|
92
|
+
-ernie_server_app number #{number} \
|
93
|
+
-ernie_server_app log_level #{log_level} \
|
94
|
+
-run ernie_server_app boot}.squeeze(' ')
|
95
|
+
puts cmd
|
96
|
+
exec(cmd)
|
97
|
+
end
|
data/elib/asset_pool.erl
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
-module(asset_pool).
|
2
|
+
-behaviour(gen_server).
|
3
|
+
|
4
|
+
%% api
|
5
|
+
-export([start_link/1, start/1, lease/0, return/1, reload_assets/0]).
|
6
|
+
|
7
|
+
%% gen_server callbacks
|
8
|
+
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
9
|
+
terminate/2, code_change/3]).
|
10
|
+
|
11
|
+
-record(state, {assets = undefined,
|
12
|
+
handler = undefined,
|
13
|
+
token = undefined}).
|
14
|
+
|
15
|
+
%%====================================================================
|
16
|
+
%% API
|
17
|
+
%%====================================================================
|
18
|
+
|
19
|
+
start_link(Args) ->
|
20
|
+
gen_server:start_link({global, ?MODULE}, ?MODULE, Args, []).
|
21
|
+
|
22
|
+
start(Args) ->
|
23
|
+
gen_server:start({global, ?MODULE}, ?MODULE, Args, []).
|
24
|
+
|
25
|
+
lease() ->
|
26
|
+
gen_server:call({global, ?MODULE}, {lease}).
|
27
|
+
|
28
|
+
return(Asset) ->
|
29
|
+
gen_server:call({global, ?MODULE}, {return, Asset}).
|
30
|
+
|
31
|
+
reload_assets() ->
|
32
|
+
gen_server:call({global, ?MODULE}, {reload_assets}).
|
33
|
+
|
34
|
+
%%====================================================================
|
35
|
+
%% gen_server callbacks
|
36
|
+
%%====================================================================
|
37
|
+
|
38
|
+
%%--------------------------------------------------------------------
|
39
|
+
%% Function: init(Args) -> {ok, State} |
|
40
|
+
%% {ok, State, Timeout} |
|
41
|
+
%% ignore |
|
42
|
+
%% {stop, Reason}
|
43
|
+
%% Description: Initiates the server
|
44
|
+
%%--------------------------------------------------------------------
|
45
|
+
init([Count, Handler]) ->
|
46
|
+
process_flag(trap_exit, true),
|
47
|
+
error_logger:info_msg("~p starting~n", [?MODULE]),
|
48
|
+
Token = make_ref(),
|
49
|
+
Assets = start_handlers(Count, Handler, Token),
|
50
|
+
{ok, #state{assets = Assets, handler = Handler, token = Token}}.
|
51
|
+
|
52
|
+
%%--------------------------------------------------------------------
|
53
|
+
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
54
|
+
%% {reply, Reply, State, Timeout} |
|
55
|
+
%% {noreply, State} |
|
56
|
+
%% {noreply, State, Timeout} |
|
57
|
+
%% {stop, Reason, Reply, State} |
|
58
|
+
%% {stop, Reason, State}
|
59
|
+
%% Description: Handling call messages
|
60
|
+
%%--------------------------------------------------------------------
|
61
|
+
handle_call({lease}, _From, State) ->
|
62
|
+
Token = State#state.token,
|
63
|
+
case queue:out(State#state.assets) of
|
64
|
+
{{value, Asset}, Assets2} ->
|
65
|
+
{asset, Port, AssetToken} = Asset,
|
66
|
+
case AssetToken =:= Token of
|
67
|
+
false ->
|
68
|
+
port_wrapper:close(Port),
|
69
|
+
Handler = State#state.handler,
|
70
|
+
NewAsset = create_asset(Handler, Token);
|
71
|
+
true ->
|
72
|
+
NewAsset = Asset
|
73
|
+
end,
|
74
|
+
{reply, {ok, NewAsset}, State#state{assets = Assets2}};
|
75
|
+
{empty, _Assets2} ->
|
76
|
+
{reply, empty, State}
|
77
|
+
end;
|
78
|
+
handle_call({return, Asset}, _From, State) ->
|
79
|
+
Token = State#state.token,
|
80
|
+
{asset, Port, AssetToken} = Asset,
|
81
|
+
case AssetToken =:= Token of
|
82
|
+
false ->
|
83
|
+
port_wrapper:close(Port),
|
84
|
+
Handler = State#state.handler,
|
85
|
+
NewAsset = create_asset(Handler, Token);
|
86
|
+
true ->
|
87
|
+
NewAsset = Asset
|
88
|
+
end,
|
89
|
+
Assets2 = queue:in(NewAsset, State#state.assets),
|
90
|
+
{reply, ok, State#state{assets = Assets2}};
|
91
|
+
handle_call({reload_assets}, _From, State) ->
|
92
|
+
Token = make_ref(),
|
93
|
+
{reply, ok, State#state{token = Token}};
|
94
|
+
handle_call(_Request, _From, State) ->
|
95
|
+
{reply, ok, State}.
|
96
|
+
|
97
|
+
%%--------------------------------------------------------------------
|
98
|
+
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
99
|
+
%% {noreply, State, Timeout} |
|
100
|
+
%% {stop, Reason, State}
|
101
|
+
%% Description: Handling cast messages
|
102
|
+
%%--------------------------------------------------------------------
|
103
|
+
handle_cast(_Msg, State) -> {noreply, State}.
|
104
|
+
|
105
|
+
handle_info({'EXIT', _Pid, normal}, State) ->
|
106
|
+
{noreply, State};
|
107
|
+
handle_info({'EXIT', Pid, Error}, State) ->
|
108
|
+
error_logger:error_msg("Port ~p closed with ~p, restarting port...~n", [Pid, Error]),
|
109
|
+
ValidAssets = queue:filter(fun(Item) -> {asset, A, _T} = Item, A =/= Pid end, State#state.assets),
|
110
|
+
Handler = State#state.handler,
|
111
|
+
Token = State#state.token,
|
112
|
+
NewAsset = create_asset(Handler, Token),
|
113
|
+
Assets = queue:in(NewAsset, ValidAssets),
|
114
|
+
{noreply, State#state{assets = Assets}};
|
115
|
+
handle_info(Msg, State) ->
|
116
|
+
error_logger:error_msg("Unexpected message: ~p~n", [Msg]),
|
117
|
+
{noreply, State}.
|
118
|
+
|
119
|
+
terminate(_Reason, _State) -> ok.
|
120
|
+
code_change(_OldVersion, State, _Extra) -> {ok, State}.
|
121
|
+
|
122
|
+
%%====================================================================
|
123
|
+
%% Internal
|
124
|
+
%%====================================================================
|
125
|
+
|
126
|
+
start_handlers(Count, Handler, Token) ->
|
127
|
+
start_handlers(queue:new(), Count, Handler, Token).
|
128
|
+
|
129
|
+
start_handlers(Assets, 0, _Handler, _Token) ->
|
130
|
+
Assets;
|
131
|
+
start_handlers(Assets, Count, Handler, Token) ->
|
132
|
+
Asset = create_asset(Handler, Token),
|
133
|
+
Assets2 = queue:in(Asset, Assets),
|
134
|
+
start_handlers(Assets2, Count - 1, Handler, Token).
|
135
|
+
|
136
|
+
create_asset(Handler, Token) ->
|
137
|
+
{asset, port_wrapper:wrap_link("ruby " ++ Handler), Token}.
|
@@ -0,0 +1,15 @@
|
|
1
|
+
-module(asset_pool_sup).
|
2
|
+
-behaviour(supervisor).
|
3
|
+
-export([start_link/0, init/1]).
|
4
|
+
|
5
|
+
start_link() ->
|
6
|
+
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
7
|
+
|
8
|
+
init([]) ->
|
9
|
+
{ok, Handler} = application:get_env(ernie_server_app, handler),
|
10
|
+
io:format("Using handler ~p~n", [Handler]),
|
11
|
+
{ok, Number} = application:get_env(ernie_server_app, number),
|
12
|
+
io:format("Using ~p handler instances~n", [Number]),
|
13
|
+
{ok, {{one_for_one, 1, 60},
|
14
|
+
[{asset_pool, {asset_pool, start_link, [[Number, Handler]]},
|
15
|
+
permanent, brutal_kill, worker, [asset_pool]}]}}.
|
@@ -0,0 +1,175 @@
|
|
1
|
+
-module(ernie_server).
|
2
|
+
-behaviour(gen_server).
|
3
|
+
|
4
|
+
%% api
|
5
|
+
-export([start_link/1, start/1, process/1, asset_freed/0]).
|
6
|
+
|
7
|
+
%% gen_server callbacks
|
8
|
+
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
9
|
+
terminate/2, code_change/3]).
|
10
|
+
|
11
|
+
-record(state, {lsock = undefined,
|
12
|
+
pending = queue:new()}).
|
13
|
+
|
14
|
+
%%====================================================================
|
15
|
+
%% API
|
16
|
+
%%====================================================================
|
17
|
+
|
18
|
+
start_link(Args) ->
|
19
|
+
gen_server:start_link({global, ?MODULE}, ?MODULE, Args, []).
|
20
|
+
|
21
|
+
start(Args) ->
|
22
|
+
gen_server:start({global, ?MODULE}, ?MODULE, Args, []).
|
23
|
+
|
24
|
+
process(Sock) ->
|
25
|
+
gen_server:cast({global, ?MODULE}, {process, Sock}).
|
26
|
+
|
27
|
+
asset_freed() ->
|
28
|
+
gen_server:cast({global, ?MODULE}, {asset_freed}).
|
29
|
+
|
30
|
+
%%====================================================================
|
31
|
+
%% gen_server callbacks
|
32
|
+
%%====================================================================
|
33
|
+
|
34
|
+
%%--------------------------------------------------------------------
|
35
|
+
%% Function: init(Args) -> {ok, State} |
|
36
|
+
%% {ok, State, Timeout} |
|
37
|
+
%% ignore |
|
38
|
+
%% {stop, Reason}
|
39
|
+
%% Description: Initiates the server
|
40
|
+
%%--------------------------------------------------------------------
|
41
|
+
init([Port]) ->
|
42
|
+
process_flag(trap_exit, true),
|
43
|
+
error_logger:info_msg("~p starting~n", [?MODULE]),
|
44
|
+
{ok, LSock} = try_listen(Port, 500),
|
45
|
+
spawn(fun() -> loop(LSock) end),
|
46
|
+
{ok, #state{lsock = LSock}}.
|
47
|
+
|
48
|
+
%%--------------------------------------------------------------------
|
49
|
+
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
50
|
+
%% {reply, Reply, State, Timeout} |
|
51
|
+
%% {noreply, State} |
|
52
|
+
%% {noreply, State, Timeout} |
|
53
|
+
%% {stop, Reason, Reply, State} |
|
54
|
+
%% {stop, Reason, State}
|
55
|
+
%% Description: Handling call messages
|
56
|
+
%%--------------------------------------------------------------------
|
57
|
+
handle_call(_Request, _From, State) ->
|
58
|
+
{reply, ok, State}.
|
59
|
+
|
60
|
+
%%--------------------------------------------------------------------
|
61
|
+
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
62
|
+
%% {noreply, State, Timeout} |
|
63
|
+
%% {stop, Reason, State}
|
64
|
+
%% Description: Handling cast messages
|
65
|
+
%%--------------------------------------------------------------------
|
66
|
+
handle_cast({process, Sock}, State) ->
|
67
|
+
case gen_tcp:recv(Sock, 0) of
|
68
|
+
{ok, BinaryTerm} ->
|
69
|
+
logger:debug("Got binary term: ~p~n", [BinaryTerm]),
|
70
|
+
Term = binary_to_term(BinaryTerm),
|
71
|
+
logger:info("Got term: ~p~n", [Term]),
|
72
|
+
case Term of
|
73
|
+
{call, '__admin__', Fun, Args} ->
|
74
|
+
State2 = process_admin(Sock, Fun, Args, State);
|
75
|
+
Any ->
|
76
|
+
State2 = process_normal(BinaryTerm, Sock, State)
|
77
|
+
end,
|
78
|
+
{noreply, State2};
|
79
|
+
{error, closed} ->
|
80
|
+
ok = gen_tcp:close(Sock),
|
81
|
+
{noreply, State}
|
82
|
+
end;
|
83
|
+
handle_cast({asset_freed}, State) ->
|
84
|
+
case queue:is_empty(State#state.pending) of
|
85
|
+
false ->
|
86
|
+
case asset_pool:lease() of
|
87
|
+
{ok, Asset} ->
|
88
|
+
{{value, {pending, BinaryTerm, Sock}}, Pending2} = queue:out(State#state.pending),
|
89
|
+
% io:format("d", []),
|
90
|
+
spawn(fun() -> process_now(BinaryTerm, Sock, Asset) end),
|
91
|
+
{noreply, State#state{pending = Pending2}};
|
92
|
+
empty ->
|
93
|
+
% io:format(".", []),
|
94
|
+
{noreply, State}
|
95
|
+
end;
|
96
|
+
true ->
|
97
|
+
{noreply, State}
|
98
|
+
end;
|
99
|
+
handle_cast(_Msg, State) -> {noreply, State}.
|
100
|
+
|
101
|
+
handle_info(Msg, State) ->
|
102
|
+
error_logger:error_msg("Unexpected message: ~p~n", [Msg]),
|
103
|
+
{noreply, State}.
|
104
|
+
|
105
|
+
terminate(_Reason, _State) -> ok.
|
106
|
+
code_change(_OldVersion, State, _Extra) -> {ok, State}.
|
107
|
+
|
108
|
+
%%====================================================================
|
109
|
+
%% Internal
|
110
|
+
%%====================================================================
|
111
|
+
|
112
|
+
try_listen(Port, 0) ->
|
113
|
+
error_logger:error_msg("Could not listen on port ~p~n", [Port]),
|
114
|
+
{error, "Could not listen on port"};
|
115
|
+
try_listen(Port, Times) ->
|
116
|
+
Res = gen_tcp:listen(Port, [binary, {packet, 4}, {active, false}, {reuseaddr, true}]),
|
117
|
+
case Res of
|
118
|
+
{ok, LSock} ->
|
119
|
+
error_logger:info_msg("Listening on port ~p~n", [Port]),
|
120
|
+
{ok, LSock};
|
121
|
+
{error, Reason} ->
|
122
|
+
error_logger:info_msg("Could not listen on port ~p: ~p~n", [Port, Reason]),
|
123
|
+
timer:sleep(5000),
|
124
|
+
try_listen(Port, Times - 1)
|
125
|
+
end.
|
126
|
+
|
127
|
+
loop(LSock) ->
|
128
|
+
{ok, Sock} = gen_tcp:accept(LSock),
|
129
|
+
logger:debug("Accepted socket: ~p~n", [Sock]),
|
130
|
+
ernie_server:process(Sock),
|
131
|
+
loop(LSock).
|
132
|
+
|
133
|
+
process_admin(Sock, reload_handlers, _Args, State) ->
|
134
|
+
asset_pool:reload_assets(),
|
135
|
+
gen_tcp:send(Sock, term_to_binary({reply, <<"Handlers reloaded.">>})),
|
136
|
+
ok = gen_tcp:close(Sock),
|
137
|
+
State;
|
138
|
+
process_admin(Sock, Fun, _Args, State) ->
|
139
|
+
gen_tcp:send(Sock, term_to_binary({reply, <<"Admin function not supported.">>})),
|
140
|
+
ok = gen_tcp:close(Sock),
|
141
|
+
State.
|
142
|
+
|
143
|
+
process_normal(BinaryTerm, Sock, State) ->
|
144
|
+
case queue:is_empty(State#state.pending) of
|
145
|
+
false ->
|
146
|
+
Pending2 = queue:in({pending, BinaryTerm, Sock}, State#state.pending),
|
147
|
+
% io:format("Q", []),
|
148
|
+
State#state{pending = Pending2};
|
149
|
+
true ->
|
150
|
+
try_process_now(BinaryTerm, Sock, State)
|
151
|
+
end.
|
152
|
+
|
153
|
+
try_process_now(BinaryTerm, Sock, State) ->
|
154
|
+
case asset_pool:lease() of
|
155
|
+
{ok, Asset} ->
|
156
|
+
% io:format("i", []),
|
157
|
+
spawn(fun() -> process_now(BinaryTerm, Sock, Asset) end),
|
158
|
+
State;
|
159
|
+
empty ->
|
160
|
+
% io:format("q", []),
|
161
|
+
Pending2 = queue:in({pending, BinaryTerm, Sock}, State#state.pending),
|
162
|
+
State#state{pending = Pending2}
|
163
|
+
end.
|
164
|
+
|
165
|
+
process_now(BinaryTerm, Sock, Asset) ->
|
166
|
+
% io:format(".", []),
|
167
|
+
% error_logger:info_msg("From Internet: ~p~n", [BinaryTerm]),
|
168
|
+
{asset, Port, Token} = Asset,
|
169
|
+
logger:debug("Asset: ~p ~p~n", [Port, Token]),
|
170
|
+
{ok, Data} = port_wrapper:rpc(Port, BinaryTerm),
|
171
|
+
% error_logger:info_msg("From Port: ~p~n", [Data]),
|
172
|
+
asset_pool:return(Asset),
|
173
|
+
ernie_server:asset_freed(),
|
174
|
+
gen_tcp:send(Sock, Data),
|
175
|
+
ok = gen_tcp:close(Sock).
|
@@ -0,0 +1,15 @@
|
|
1
|
+
-module(ernie_server_app).
|
2
|
+
-behaviour(application).
|
3
|
+
|
4
|
+
-export([boot/0, start/2, stop/1]).
|
5
|
+
|
6
|
+
boot() ->
|
7
|
+
application:start(ernie_server_app).
|
8
|
+
|
9
|
+
start(_Type, _Args) ->
|
10
|
+
logger_sup:start_link(),
|
11
|
+
ernie_server_sup:start_link(),
|
12
|
+
asset_pool_sup:start_link().
|
13
|
+
|
14
|
+
stop(_State) ->
|
15
|
+
ok.
|
@@ -0,0 +1,19 @@
|
|
1
|
+
-module(ernie_server_sup).
|
2
|
+
-behaviour(supervisor).
|
3
|
+
-export([start_link/0, init/1]).
|
4
|
+
|
5
|
+
start_link() ->
|
6
|
+
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
7
|
+
|
8
|
+
init([]) ->
|
9
|
+
{ok, Port} = application:get_env(ernie_server_app, port),
|
10
|
+
io:format("Using port ~p~n", [Port]),
|
11
|
+
case application:get_env(ernie_server_app, pidfile) of
|
12
|
+
{ok, Location} ->
|
13
|
+
Pid = os:getpid(),
|
14
|
+
ok = file:write_file(Location, list_to_binary(Pid));
|
15
|
+
undefined -> ok
|
16
|
+
end,
|
17
|
+
{ok, {{one_for_one, 1, 60},
|
18
|
+
[{ernie_server, {ernie_server, start_link, [[Port]]},
|
19
|
+
permanent, brutal_kill, worker, [ernie_server]}]}}.
|
data/elib/logger.erl
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
-module(logger).
|
2
|
+
-behaviour(gen_server).
|
3
|
+
|
4
|
+
%% api
|
5
|
+
-export([start_link/1, start/1, set_log_level/1, debug/2, info/2, warn/2, error/2, fatal/2]).
|
6
|
+
|
7
|
+
%% gen_server callbacks
|
8
|
+
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
9
|
+
terminate/2, code_change/3]).
|
10
|
+
|
11
|
+
-record(state, {log_level = undefined}).
|
12
|
+
|
13
|
+
%%====================================================================
|
14
|
+
%% API
|
15
|
+
%%====================================================================
|
16
|
+
|
17
|
+
start_link(Args) ->
|
18
|
+
gen_server:start_link({global, ?MODULE}, ?MODULE, Args, []).
|
19
|
+
|
20
|
+
start(Args) ->
|
21
|
+
gen_server:start({global, ?MODULE}, ?MODULE, Args, []).
|
22
|
+
|
23
|
+
set_log_level(Level) ->
|
24
|
+
gen_server:call({global, ?MODULE}, {set_log_level, Level}).
|
25
|
+
|
26
|
+
debug(Msg, Args) ->
|
27
|
+
gen_server:cast({global, ?MODULE}, {debug, Msg, Args}).
|
28
|
+
|
29
|
+
info(Msg, Args) ->
|
30
|
+
gen_server:cast({global, ?MODULE}, {info, Msg, Args}).
|
31
|
+
|
32
|
+
warn(Msg, Args) ->
|
33
|
+
gen_server:cast({global, ?MODULE}, {warn, Msg, Args}).
|
34
|
+
|
35
|
+
error(Msg, Args) ->
|
36
|
+
gen_server:cast({global, ?MODULE}, {error, Msg, Args}).
|
37
|
+
|
38
|
+
fatal(Msg, Args) ->
|
39
|
+
gen_server:cast({global, ?MODULE}, {fatal, Msg, Args}).
|
40
|
+
|
41
|
+
%%====================================================================
|
42
|
+
%% gen_server callbacks
|
43
|
+
%%====================================================================
|
44
|
+
|
45
|
+
%%--------------------------------------------------------------------
|
46
|
+
%% Function: init(Args) -> {ok, State} |
|
47
|
+
%% {ok, State, Timeout} |
|
48
|
+
%% ignore |
|
49
|
+
%% {stop, Reason}
|
50
|
+
%% Description: Initiates the server
|
51
|
+
%%--------------------------------------------------------------------
|
52
|
+
init([LogLevel]) ->
|
53
|
+
error_logger:info_msg("~p starting~n", [?MODULE]),
|
54
|
+
{ok, #state{log_level = LogLevel}}.
|
55
|
+
|
56
|
+
%%--------------------------------------------------------------------
|
57
|
+
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
58
|
+
%% {reply, Reply, State, Timeout} |
|
59
|
+
%% {noreply, State} |
|
60
|
+
%% {noreply, State, Timeout} |
|
61
|
+
%% {stop, Reason, Reply, State} |
|
62
|
+
%% {stop, Reason, State}
|
63
|
+
%% Description: Handling call messages
|
64
|
+
%%--------------------------------------------------------------------
|
65
|
+
handle_call({set_log_level, Level}, _From, State) ->
|
66
|
+
{reply, ok, State#state{log_level = Level}};
|
67
|
+
handle_call(_Request, _From, State) ->
|
68
|
+
{reply, ok, State}.
|
69
|
+
|
70
|
+
%%--------------------------------------------------------------------
|
71
|
+
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
72
|
+
%% {noreply, State, Timeout} |
|
73
|
+
%% {stop, Reason, State}
|
74
|
+
%% Description: Handling cast messages
|
75
|
+
%%--------------------------------------------------------------------
|
76
|
+
handle_cast({debug, Msg, Args}, State) ->
|
77
|
+
log(State#state.log_level, 4, Msg, Args),
|
78
|
+
{noreply, State};
|
79
|
+
handle_cast({info, Msg, Args}, State) ->
|
80
|
+
log(State#state.log_level, 3, Msg, Args),
|
81
|
+
{noreply, State};
|
82
|
+
handle_cast({warn, Msg, Args}, State) ->
|
83
|
+
log(State#state.log_level, 2, Msg, Args),
|
84
|
+
{noreply, State};
|
85
|
+
handle_cast({error, Msg, Args}, State) ->
|
86
|
+
log(State#state.log_level, 1, Msg, Args),
|
87
|
+
{noreply, State};
|
88
|
+
handle_cast({fatal, Msg, Args}, State) ->
|
89
|
+
log(State#state.log_level, 0, Msg, Args),
|
90
|
+
{noreply, State};
|
91
|
+
handle_cast(_Msg, State) -> {noreply, State}.
|
92
|
+
|
93
|
+
handle_info(Msg, State) ->
|
94
|
+
error_logger:error_msg("Unexpected message: ~p~n", [Msg]),
|
95
|
+
{noreply, State}.
|
96
|
+
|
97
|
+
terminate(_Reason, _State) -> ok.
|
98
|
+
code_change(_OldVersion, State, _Extra) -> {ok, State}.
|
99
|
+
|
100
|
+
%%====================================================================
|
101
|
+
%% Internal
|
102
|
+
%%====================================================================
|
103
|
+
|
104
|
+
log(SystemLogLevel, MessageLogLevel, Message, Args) ->
|
105
|
+
case SystemLogLevel >= MessageLogLevel of
|
106
|
+
false -> ok;
|
107
|
+
true -> io:format(Message, Args)
|
108
|
+
end.
|
data/elib/logger_sup.erl
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
-module(logger_sup).
|
2
|
+
-behaviour(supervisor).
|
3
|
+
-export([start_link/0, init/1]).
|
4
|
+
|
5
|
+
start_link() ->
|
6
|
+
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
7
|
+
|
8
|
+
init([]) ->
|
9
|
+
{ok, LogLevel} = application:get_env(ernie_server_app, log_level),
|
10
|
+
io:format("Using log level ~p~n", [LogLevel]),
|
11
|
+
{ok, {{one_for_one, 1, 60},
|
12
|
+
[{logger, {logger, start_link, [[LogLevel]]},
|
13
|
+
permanent, brutal_kill, worker, [logger]}]}}.
|
@@ -0,0 +1,67 @@
|
|
1
|
+
-module(port_wrapper).
|
2
|
+
-export([wrap/1, wrap/2, wrap_link/1, wrap_link/2, send/2, shutdown/1, close/1, rpc/2]).
|
3
|
+
|
4
|
+
wrap(Command) ->
|
5
|
+
spawn(fun() -> process_flag(trap_exit, true), Port = create_port(Command), loop(Port, infinity, Command) end).
|
6
|
+
wrap(Command, Timeout) ->
|
7
|
+
spawn(fun() -> process_flag(trap_exit, true), Port = create_port(Command), loop(Port, Timeout, Command) end).
|
8
|
+
|
9
|
+
wrap_link(Command) ->
|
10
|
+
spawn_link(fun() -> process_flag(trap_exit, true), Port = create_port(Command), link(Port), loop(Port, infinity, Command) end).
|
11
|
+
wrap_link(Command, Timeout) ->
|
12
|
+
spawn_link(fun() -> process_flag(trap_exit, true), Port = create_port(Command), link(Port), loop(Port, Timeout, Command) end).
|
13
|
+
|
14
|
+
rpc(WrappedPort, Message) ->
|
15
|
+
send(WrappedPort, Message),
|
16
|
+
receive
|
17
|
+
{WrappedPort, Result} -> {ok, Result}
|
18
|
+
after 15000 ->
|
19
|
+
{error, timed_out, WrappedPort}
|
20
|
+
end.
|
21
|
+
|
22
|
+
send(WrappedPort, Message) ->
|
23
|
+
WrappedPort ! {self(), {command, Message}},
|
24
|
+
WrappedPort.
|
25
|
+
|
26
|
+
shutdown(WrappedPort) ->
|
27
|
+
WrappedPort ! shutdown,
|
28
|
+
true.
|
29
|
+
|
30
|
+
close(WrappedPort) ->
|
31
|
+
WrappedPort ! noose,
|
32
|
+
true.
|
33
|
+
|
34
|
+
create_port(Command) ->
|
35
|
+
open_port({spawn, Command}, [{packet, 4}, nouse_stdio, exit_status, binary]).
|
36
|
+
|
37
|
+
loop(Port, Timeout, Command) ->
|
38
|
+
receive
|
39
|
+
noose ->
|
40
|
+
port_close(Port),
|
41
|
+
noose;
|
42
|
+
shutdown ->
|
43
|
+
port_close(Port),
|
44
|
+
exit(shutdown);
|
45
|
+
{Source, {command, Message}} ->
|
46
|
+
Port ! {self(), {command, Message}},
|
47
|
+
receive
|
48
|
+
{Port, {data, Result}} ->
|
49
|
+
Source ! {self(), Result}
|
50
|
+
after Timeout ->
|
51
|
+
error_logger:error_msg("Port Wrapper ~p timed out in mid operation (~p)!~n", [self(),Message]),
|
52
|
+
% We timed out, which means we need to close and then restart the port
|
53
|
+
port_close(Port), % Should SIGPIPE the child.
|
54
|
+
exit(timed_out)
|
55
|
+
end,
|
56
|
+
loop(Port,Timeout,Command);
|
57
|
+
{Port, {exit_status, _Code}} ->
|
58
|
+
% Hard and Unanticipated Crash
|
59
|
+
error_logger:error_msg( "Port closed! ~p~n", [Port] ),
|
60
|
+
exit({error, _Code});
|
61
|
+
{'EXIT',_Pid,shutdown} ->
|
62
|
+
port_close(Port),
|
63
|
+
exit(shutdown);
|
64
|
+
Any ->
|
65
|
+
error_logger:warning_msg("PortWrapper ~p got unexpected message: ~p~n", [self(), Any]),
|
66
|
+
loop(Port, Timeout, Command)
|
67
|
+
end.
|
data/ernie.gemspec
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{ernie}
|
5
|
+
s.version = "0.3.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Tom Preston-Werner"]
|
9
|
+
s.date = %q{2009-08-14}
|
10
|
+
s.default_executable = %q{ernie}
|
11
|
+
s.email = %q{tom@mojombo.com}
|
12
|
+
s.executables = ["ernie"]
|
13
|
+
s.extensions = ["ext/extconf.rb"]
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"LICENSE",
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".document",
|
20
|
+
".gitignore",
|
21
|
+
"LICENSE",
|
22
|
+
"README.md",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION.yml",
|
25
|
+
"bin/ernie",
|
26
|
+
"ebin/ernie_server_app.app",
|
27
|
+
"elib/asset_pool.erl",
|
28
|
+
"elib/asset_pool_sup.erl",
|
29
|
+
"elib/ernie_server.erl",
|
30
|
+
"elib/ernie_server_app.erl",
|
31
|
+
"elib/ernie_server_sup.erl",
|
32
|
+
"elib/logger.erl",
|
33
|
+
"elib/logger_sup.erl",
|
34
|
+
"elib/port_wrapper.erl",
|
35
|
+
"ernie.gemspec",
|
36
|
+
"examples/calc.rb",
|
37
|
+
"ext/Makefile",
|
38
|
+
"ext/extconf.rb",
|
39
|
+
"lib/ernie.rb",
|
40
|
+
"test/ernie_test.rb",
|
41
|
+
"test/load.rb",
|
42
|
+
"test/test_helper.rb"
|
43
|
+
]
|
44
|
+
s.homepage = %q{http://github.com/mojombo/ernie}
|
45
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
46
|
+
s.require_paths = ["lib"]
|
47
|
+
s.rubygems_version = %q{1.3.5}
|
48
|
+
s.summary = %q{Ernie is a BERT-RPC server implementation.}
|
49
|
+
s.test_files = [
|
50
|
+
"test/ernie_test.rb",
|
51
|
+
"test/load.rb",
|
52
|
+
"test/test_helper.rb",
|
53
|
+
"examples/calc.rb"
|
54
|
+
]
|
55
|
+
|
56
|
+
if s.respond_to? :specification_version then
|
57
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
58
|
+
s.specification_version = 3
|
59
|
+
|
60
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
61
|
+
s.add_runtime_dependency(%q<erlectricity>, [">= 1.0.1"])
|
62
|
+
s.add_runtime_dependency(%q<bertrpc>, [">= 0.2.0"])
|
63
|
+
else
|
64
|
+
s.add_dependency(%q<erlectricity>, [">= 1.0.1"])
|
65
|
+
s.add_dependency(%q<bertrpc>, [">= 0.2.0"])
|
66
|
+
end
|
67
|
+
else
|
68
|
+
s.add_dependency(%q<erlectricity>, [">= 1.0.1"])
|
69
|
+
s.add_dependency(%q<bertrpc>, [">= 0.2.0"])
|
70
|
+
end
|
71
|
+
end
|
data/examples/calc.rb
ADDED
data/ext/Makefile
ADDED
data/ext/extconf.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# does nothing, Makefile is handwritten
|
data/lib/ernie.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'erlectricity'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
class Ernie
|
6
|
+
class << self
|
7
|
+
attr_accessor :mods, :current_mod, :logger
|
8
|
+
end
|
9
|
+
|
10
|
+
self.mods = {}
|
11
|
+
self.current_mod = nil
|
12
|
+
self.logger = nil
|
13
|
+
|
14
|
+
def self.mod(name, block)
|
15
|
+
m = Mod.new(name)
|
16
|
+
self.current_mod = m
|
17
|
+
self.mods[name] = m
|
18
|
+
block.call
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.fun(name, block)
|
22
|
+
self.current_mod.fun(name, block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.logfile(file)
|
26
|
+
self.logger = Logger.new(file)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.log(text)
|
30
|
+
self.logger.info(text) if self.logger
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.convert(item)
|
34
|
+
if item.instance_of?(Hash)
|
35
|
+
a = [:dict]
|
36
|
+
item.each_pair { |k, v| a << [convert(k), convert(v)] }
|
37
|
+
a
|
38
|
+
elsif item.instance_of?(Array)
|
39
|
+
item.map { |x| convert(x) }
|
40
|
+
else
|
41
|
+
item
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.deconvert(item)
|
46
|
+
if item.instance_of?(Array)
|
47
|
+
if item.first == :dict
|
48
|
+
item[1..-1].inject({}) do |acc, x|
|
49
|
+
acc[deconvert(x[0])] = deconvert(x[1]); acc
|
50
|
+
end
|
51
|
+
else
|
52
|
+
item.map { |x| deconvert(x) }
|
53
|
+
end
|
54
|
+
else
|
55
|
+
item
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.dispatch(mod, fun, args)
|
60
|
+
xargs = deconvert(args)
|
61
|
+
self.log("-- " + [mod, fun, xargs].inspect)
|
62
|
+
res = self.mods[mod].funs[fun].call(*xargs)
|
63
|
+
convert(res)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.start
|
67
|
+
self.log("Starting")
|
68
|
+
self.log(self.mods.inspect)
|
69
|
+
receive do |f|
|
70
|
+
f.when([:call, Symbol, Symbol, Array]) do |mod, fun, args|
|
71
|
+
self.log("-> " + [:call, mod, fun, args].inspect)
|
72
|
+
begin
|
73
|
+
res = self.dispatch(mod, fun, args)
|
74
|
+
xres = [:reply, res]
|
75
|
+
self.log("<- " + xres.inspect)
|
76
|
+
f.send!(xres)
|
77
|
+
rescue Object => e
|
78
|
+
xres = [:error, [:user, 0, e.message]]
|
79
|
+
self.log("<- " + xres.inspect)
|
80
|
+
self.log(e.backtrace.join("\n"))
|
81
|
+
f.send!(xres)
|
82
|
+
end
|
83
|
+
f.receive_loop
|
84
|
+
end
|
85
|
+
|
86
|
+
f.when(Any) do |any|
|
87
|
+
self.log("-> " + any.inspect)
|
88
|
+
xres = [:error, [:server, 0, "Invalid request: #{any.inspect}"]]
|
89
|
+
self.log("<- " + xres.inspect)
|
90
|
+
f.send!(xres)
|
91
|
+
f.receive_loop
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Ernie::Mod
|
98
|
+
attr_accessor :name, :funs
|
99
|
+
|
100
|
+
def initialize(name)
|
101
|
+
self.name = name
|
102
|
+
self.funs = {}
|
103
|
+
end
|
104
|
+
|
105
|
+
def fun(name, block)
|
106
|
+
self.funs[name] = block
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Root level calls
|
111
|
+
|
112
|
+
def mod(name, &block)
|
113
|
+
Ernie.mod(name, block)
|
114
|
+
end
|
115
|
+
|
116
|
+
def fun(name, &block)
|
117
|
+
Ernie.fun(name, block)
|
118
|
+
end
|
119
|
+
|
120
|
+
def logfile(name)
|
121
|
+
Ernie.logfile(name)
|
122
|
+
end
|
123
|
+
|
124
|
+
at_exit do
|
125
|
+
Ernie.start unless $test
|
126
|
+
end
|
data/test/ernie_test.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ErnieTest < Test::Unit::TestCase
|
4
|
+
context "mod" do
|
5
|
+
should "add a mod to the mods hash" do
|
6
|
+
mod(:foo) { }
|
7
|
+
assert Ernie.mods[:foo]
|
8
|
+
assert Ernie.mods[:foo].instance_of?(Ernie::Mod)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "fun" do
|
13
|
+
should "add a fun to the funs hash of the mod" do
|
14
|
+
mod(:foo) { fun(:bar) { } }
|
15
|
+
assert Ernie.mods[:foo].funs[:bar]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/test/load.rb
ADDED
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ernie
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Preston-Werner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-08-14 00:00:00 -07:00
|
13
|
+
default_executable: ernie
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: erlectricity
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.0.1
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: bertrpc
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.2.0
|
34
|
+
version:
|
35
|
+
description:
|
36
|
+
email: tom@mojombo.com
|
37
|
+
executables:
|
38
|
+
- ernie
|
39
|
+
extensions:
|
40
|
+
- ext/extconf.rb
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.md
|
44
|
+
files:
|
45
|
+
- .document
|
46
|
+
- .gitignore
|
47
|
+
- LICENSE
|
48
|
+
- README.md
|
49
|
+
- Rakefile
|
50
|
+
- VERSION.yml
|
51
|
+
- bin/ernie
|
52
|
+
- ebin/ernie_server_app.app
|
53
|
+
- elib/asset_pool.erl
|
54
|
+
- elib/asset_pool_sup.erl
|
55
|
+
- elib/ernie_server.erl
|
56
|
+
- elib/ernie_server_app.erl
|
57
|
+
- elib/ernie_server_sup.erl
|
58
|
+
- elib/logger.erl
|
59
|
+
- elib/logger_sup.erl
|
60
|
+
- elib/port_wrapper.erl
|
61
|
+
- ernie.gemspec
|
62
|
+
- examples/calc.rb
|
63
|
+
- ext/Makefile
|
64
|
+
- ext/extconf.rb
|
65
|
+
- lib/ernie.rb
|
66
|
+
- test/ernie_test.rb
|
67
|
+
- test/load.rb
|
68
|
+
- test/test_helper.rb
|
69
|
+
has_rdoc: true
|
70
|
+
homepage: http://github.com/mojombo/ernie
|
71
|
+
licenses: []
|
72
|
+
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options:
|
75
|
+
- --charset=UTF-8
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: "0"
|
83
|
+
version:
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: "0"
|
89
|
+
version:
|
90
|
+
requirements: []
|
91
|
+
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 1.3.5
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: Ernie is a BERT-RPC server implementation.
|
97
|
+
test_files:
|
98
|
+
- test/ernie_test.rb
|
99
|
+
- test/load.rb
|
100
|
+
- test/test_helper.rb
|
101
|
+
- examples/calc.rb
|