schleyfox-ernie 2.2.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 ADDED
@@ -0,0 +1,5 @@
1
+ README.md
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *.beam
2
+ pkg
data/History.txt ADDED
@@ -0,0 +1,40 @@
1
+ = 2.2.0 / 2010-03-12
2
+ * Minor Additions
3
+ * Set procline for external Ruby handlers
4
+
5
+ = 2.1.0 / 2010-02-20
6
+ * Major Additions
7
+ * Add access logging
8
+
9
+ = 2.0.0 / 2010-02-16
10
+ * Major Changes
11
+ * Use configuration file for defining handlers
12
+ * Add Native Erlang modules
13
+ * Abstract handler logic to support handlers in any language
14
+ * Add High/Low connection queues
15
+ * Remove Ruby DSL (must use Ernie.expose now)
16
+
17
+ = 1.3.0 / 2009-11-30
18
+ * API Additions
19
+ * Add loglevel for setting log level
20
+ * Add Ernie.auto_start bool
21
+ * Major changes
22
+ * Better logging granularity
23
+
24
+ = 1.2.0 / 2009-11-23
25
+ * API Additions
26
+ * Add Ernie.expose
27
+ * Internal Changes
28
+ * Remove 15s internal timeout
29
+
30
+ = 1.1.0 / 2009-10-28
31
+ * Major changes
32
+ * Remove dependency on Erlectricity
33
+ * Simplify processing loop
34
+
35
+ = 1.0.0 / 2009-10-19
36
+ * No Changes. Production ready!
37
+
38
+ = 0.4.0 / 2009-10-08
39
+ * Major changes
40
+ * Convert to use BERT gem.
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,306 @@
1
+ Ernie
2
+ =====
3
+
4
+ By Tom Preston-Werner (tom@mojombo.com)
5
+
6
+ Ernie is a BERT-RPC server implementation that uses an Erlang server to accept
7
+ incoming connections, and then delegates the request to custom modules that
8
+ you can write in any language (currently only Ruby and Erlang support is
9
+ included).
10
+
11
+ Modules that are written in Ruby or any non-Erlang language are known as
12
+ "external" modules and you must specify how many workers of each module should
13
+ be spawned. Requests against these modules are balanced between the workers.
14
+ Modules that are written in Erlang are known as "native" modules and run
15
+ within the Erlang server's runtime. Since these are spawned as lightweight
16
+ processes, there is no balancing necessary and much less communication
17
+ overhead when compared to external modules.
18
+
19
+ Ernie supports multiple heterogenous modules. For instance, you can have an
20
+ external Ruby module running 10 workers *and* a native Erlang module running
21
+ simultaneously. Ernie keeps track of sending requests to the proper module.
22
+ Using a technique called "shadowing," you can selectively optimize certain
23
+ external module functions with native code and Ernie will handle selecting the
24
+ correct function.
25
+
26
+ See the full BERT-RPC specification at [bert-rpc.org](http://bert-rpc.org).
27
+
28
+ Ernie currently supports the following BERT-RPC features:
29
+
30
+ * `call` requests
31
+ * `cast` requests
32
+
33
+ Ernie was developed for GitHub and is currently in production use serving
34
+ millions of RPC requests every day. The stability and performance have been
35
+ exemplary.
36
+
37
+ Ernie follows [Semantic Versioning](http://semver.org/) for release
38
+ versioning.
39
+
40
+ Installation
41
+ ------------
42
+
43
+ Step 1: Install Erlang (R13B or higher).
44
+
45
+ http://www.erlang.org/download.html
46
+
47
+ Step 2: Install Ernie:
48
+
49
+ $ [sudo] gem install ernie
50
+
51
+
52
+ Running
53
+ -------
54
+
55
+ Usage: ernie [command] [options]
56
+ -c, --config CONFIG Config file.
57
+ -p, --port PORT Port.
58
+ -l, --log-level Log level (0-4).
59
+ -a, --access-log LOGFILE Access log file
60
+ -d, --detached Run as a daemon.
61
+ -P, --pidfile PIDFILE Location to write pid file.
62
+
63
+ Commands:
64
+ <none> Start an Ernie server.
65
+ reload-handlers Gracefully reload all of the external handlers
66
+ and use the new code for all subsequent requests.
67
+ stats Print a list of connection and handler statistics.
68
+
69
+ Examples:
70
+ ernie -d -p 9999 -c example.cfg
71
+ Start the ernie server in the background on port 9999 using the
72
+ example.cfg configuration file.
73
+
74
+ ernie reload-handlers -p 9999
75
+ Reload the handlers for the ernie server currently running on
76
+ port 9999.
77
+
78
+
79
+ Configuration File
80
+ ------------------
81
+
82
+ Ernie configuration files are written as a series of dotted Erlang terms. Each
83
+ term is a list of 2-tuples that specify options for a module.
84
+
85
+ ### Native Modules
86
+
87
+ The form for native modules is:
88
+
89
+ [{module, Module},
90
+ {type, native},
91
+ {codepaths, CodePaths}].
92
+
93
+ Where Module is an atom corresponding to the module name and CodePaths is a
94
+ list of strings representing the file paths that should be added to the
95
+ runtime's code path. These paths will be prepended to the code path and must
96
+ include the native module's directory and the directories of any dependencies.
97
+
98
+ ### External Modules
99
+
100
+ The form for external modules is:
101
+
102
+ [{module, Module},
103
+ {type, external},
104
+ {command, Command},
105
+ {count, Count}].
106
+
107
+ Where Module is an atom corresponding to the module name, Command is a string
108
+ specifying the command to be executed in order to start a worker, and Count is
109
+ the number of workers to spawn.
110
+
111
+ ### Shadowing
112
+
113
+ If you specify a native module and an external module of the same name (and in
114
+ that order), Ernie will inspect the native module to see if it has the
115
+ requested function exported and use that if it does. If it does not, then it
116
+ will fall back on the external module. This can be used to selectively
117
+ optimize certain functions in a module without any modifications to your
118
+ client code.
119
+
120
+ ### Predicate Shadowing
121
+
122
+ In some circumstances it can be nice to conditionally shadow a function in an
123
+ external module based on the nature of the arguments. For example, you might
124
+ want requests for `math:fib(X)` to be routed to the external module when X is
125
+ less than 10, but to be handled by the native module when X is 10 or greater.
126
+ This can be accomplished by implementing a function `math:fib_pred(X)` in the
127
+ native module. Notice the `_pred` appended to the normal function name (pred
128
+ is short for predicate). If a function like this is present, Ernie will call
129
+ it with the requested arguments and if the return value is `true` the native
130
+ module will be used. If the return value is `false` the external module will
131
+ be used.
132
+
133
+
134
+ Example Configuration File
135
+ --------------------------
136
+
137
+ The following example config file informs Ernie of two modules. The first term
138
+ identifies a native module 'nat' that resides in the nat.beam file under the
139
+ '/path/to/app/ebin' directory. The second term specifies an external module
140
+ 'ext' that will have 2 workers started with the command 'ruby
141
+ /path/to/app/ernie/ext.rb'.
142
+
143
+ [{module, nat},
144
+ {type, native},
145
+ {codepaths, ["/path/to/app/ebin"]}].
146
+
147
+ [{module, ext},
148
+ {type, external},
149
+ {command, "ruby /path/to/app/ernie/ext.rb"},
150
+ {count, 2}].
151
+
152
+
153
+ Access Log
154
+ ----------
155
+
156
+ If you have requested that an access log be written (using the -a or
157
+ --access-log option) then all requests will be logged to that file. Each
158
+ request is printed on a single line. The elements of the log line are as
159
+ follows (with comments on the right side):
160
+
161
+ ACC type of message [ ACC | ERR ]
162
+ [2010-02-20T11:42:25.259750] time the connection was accepted
163
+ 0.000053 seconds from connection to processing start
164
+ 0.000237 seconds from processing start to finish
165
+ - delimiter
166
+ 0 size of high queue at connect time
167
+ 0 size of low queue at connect time
168
+ nat type of handler [ nat | ext ]
169
+ high priority [ high | low ]
170
+ - delimiter
171
+ {call,nat,add,[1,2]} message
172
+
173
+
174
+ Log lines are written when the request completes so they may appear out of
175
+ order with respect to connection time. To facilitate log rotation, Ernie will
176
+ create a new access log file if the current log file is moved or deleted.
177
+
178
+
179
+ Native (Erlang) Handler
180
+ -----------------------
181
+
182
+ Native handlers are written as normal Erlang modules. The exported functions
183
+ will become available to BERT-RPC clients.
184
+
185
+ ### Example
186
+
187
+ -module(nat).
188
+ -export([add/2]).
189
+
190
+ add(A, B) ->
191
+ A + B.
192
+
193
+ ### BERT-RPC Sequence Example
194
+
195
+ -> {call, nat, add, [1, 2]}
196
+ <- {reply, 3}
197
+
198
+
199
+ External (Ruby) Handler
200
+ -----------------------
201
+
202
+ Included in this gem is a library called `ernie` that makes it easy to write
203
+ Ernie handlers in Ruby. All you have to do is write a standard Ruby module and
204
+ expose it to Ernie and the functions of that module will become available to
205
+ BERT-RPC clients.
206
+
207
+ ### Example
208
+
209
+ Using a Ruby module and Ernie.expose:
210
+
211
+ require 'rubygems'
212
+ require 'ernie'
213
+
214
+ module Ext
215
+ def add(a, b)
216
+ a + b
217
+ end
218
+ end
219
+
220
+ Ernie.expose(:ext, Ext)
221
+
222
+ ### BERT-RPC Sequence Example
223
+
224
+ -> {call, nat, add, [1, 2]}
225
+ <- {reply, 3}
226
+
227
+ ### Logging
228
+
229
+ You can have logging sent to a file by adding these lines to your handler:
230
+
231
+ logfile('/var/log/ernie.log')
232
+ loglevel(Logger::INFO)
233
+
234
+ This will log startup info, requests, and error messages to the log. Choosing
235
+ Logger::DEBUG will include the response (be careful, doing this can generate
236
+ very large log files).
237
+
238
+ ### Autostart
239
+
240
+ Normally Ruby Ernie handlers will become active after the file has been loaded
241
+ in. you can disable this behavior by setting:
242
+
243
+ Ernie.auto_start = false
244
+
245
+
246
+ Selecting Queue Priority
247
+ ------------------------
248
+
249
+ Ernie maintains High and Low priority queues for incoming connections. If
250
+ there are any connections in the High priority queue, they will always be
251
+ processed first. If the High priority queue is empty, connections will be
252
+ processed from the Low priority queue. By default, connections go into the
253
+ High priority queue. To select a queue, an info BERP of the following form
254
+ must be sent preceding the call.
255
+
256
+ -- {info, priority, Priority}
257
+
258
+ Where `Priority` is either the `high` or `low` atom. An example sequence where
259
+ the low priority queue is being selected would look like the following.
260
+
261
+ -> {info, priority, low}
262
+ -> {call, nat, add, [1, 2]}
263
+ <- {reply, 3}
264
+
265
+
266
+ Using the BERTRPC gem to make calls to Ernie
267
+ --------------------------------------------
268
+
269
+ You can make BERT-RPC calls from Ruby with the [BERTRPC gem](http://github.com/mojombo/bertrpc):
270
+
271
+ require 'bertrpc'
272
+
273
+ svc = BERTRPC::Service.new('localhost', 8000)
274
+ svc.call.ext.add(1, 2)
275
+ # => 3
276
+
277
+
278
+ Contribute
279
+ ----------
280
+
281
+ If you'd like to hack on Ernie, start by forking my repo on GitHub:
282
+
283
+ http://github.com/mojombo/ernie
284
+
285
+ To get all of the dependencies, install the gem first. To run ernie from
286
+ source, you must first build the Erlang code:
287
+
288
+ rake ebuild
289
+
290
+ The best way to get your changes merged back into core is as follows:
291
+
292
+ 1. Clone down your fork
293
+ 1. Create a topic branch to contain your change
294
+ 1. Hack away
295
+ 1. Add tests and make sure everything still passes by running `rake`
296
+ 1. If you are adding new functionality, document it in the README.md
297
+ 1. Do not change the version number, I will do that on my end
298
+ 1. If necessary, rebase your commits into logical chunks, without errors
299
+ 1. Push the branch up to GitHub
300
+ 1. Send me (mojombo) a pull request for your branch
301
+
302
+
303
+ Copyright
304
+ ---------
305
+
306
+ Copyright (c) 2009 Tom Preston-Werner. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,67 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "ernie"
8
+ gem.rubyforge_project = "ernie"
9
+ gem.summary = %Q{Ernie is a BERT-RPC server implementation.}
10
+ gem.description = %Q{Ernie is an Erlang/Ruby hybrid BERT-RPC server implementation packaged as a gem.}
11
+ gem.email = "tom@mojombo.com"
12
+ gem.homepage = "http://github.com/mojombo/ernie"
13
+ gem.authors = ["Tom Preston-Werner"]
14
+ gem.files.include(["ext"])
15
+ gem.extensions << 'ext/extconf.rb'
16
+ gem.add_dependency('bert', '>= 1.1.0')
17
+ gem.add_dependency('bertrpc', '>= 1.0.0')
18
+
19
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
20
+ end
21
+ rescue LoadError
22
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
23
+ end
24
+
25
+ require 'rake/testtask'
26
+ Rake::TestTask.new(:test) do |test|
27
+ test.libs << 'lib' << 'test'
28
+ test.pattern = 'test/**/*_test.rb'
29
+ test.verbose = true
30
+ end
31
+
32
+ begin
33
+ require 'rcov/rcovtask'
34
+ Rcov::RcovTask.new do |test|
35
+ test.libs << 'test'
36
+ test.pattern = 'test/**/*_test.rb'
37
+ test.verbose = true
38
+ end
39
+ rescue LoadError
40
+ task :rcov do
41
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
42
+ end
43
+ end
44
+
45
+ task :default => :test
46
+
47
+ # require 'rake/rdoctask'
48
+ # Rake::RDocTask.new do |rdoc|
49
+ # if File.exist?('VERSION.yml')
50
+ # config = YAML.load(File.read('VERSION.yml'))
51
+ # version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
52
+ # else
53
+ # version = ""
54
+ # end
55
+ #
56
+ # rdoc.rdoc_dir = 'rdoc'
57
+ # rdoc.title = "ernie #{version}"
58
+ # rdoc.rdoc_files.include('README*')
59
+ # rdoc.rdoc_files.include('lib/**/*.rb')
60
+ # end
61
+
62
+ task :ebuild do
63
+ ERLC_TEST_FLAGS = ""
64
+ ERLC_FLAGS = "-o ../ebin"
65
+ cd "elib"
66
+ sh "erlc #{ERLC_FLAGS} #{ERLC_TEST_FLAGS} #{Dir["**/*.erl"].join(" ")}"
67
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :major: 2
3
+ :minor: 2
4
+ :patch: 0
5
+ :build:
data/bin/ernie ADDED
@@ -0,0 +1,116 @@
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
+ def version
18
+ yml = YAML.load(File.read(File.join(File.dirname(__FILE__), *%w[.. VERSION.yml])))
19
+ "#{yml[:major]}.#{yml[:minor]}.#{yml[:patch]}"
20
+ end
21
+
22
+ require 'optparse'
23
+ require 'pp'
24
+ require 'yaml'
25
+
26
+ help = <<HELP
27
+ Ernie is an Erlang/Ruby BERT-RPC Server.
28
+
29
+ Basic Command Line Usage:
30
+ ernie [command] [options]
31
+
32
+ Commands:
33
+ <none> Start an Ernie server.
34
+ reload-handlers Gracefully reload all of the the ruby handlers
35
+ and use the new code for all subsequent requests.
36
+
37
+ Options:
38
+ HELP
39
+
40
+ options = {}
41
+ OptionParser.new do |opts|
42
+ opts.banner = help
43
+ opts.version = version
44
+
45
+ opts.on("-c CONFIG", "--config CONFIG", "Config file") do |x|
46
+ options[:config] = x
47
+ end
48
+
49
+ opts.on("-p PORT", "--port PORT", "Port") do |x|
50
+ options[:port] = x
51
+ end
52
+
53
+ opts.on("-l LOGLEVEL", "--log-level LOGLEVEL", "Log level (0-4)") do |x|
54
+ options[:log_level] = x
55
+ end
56
+
57
+ opts.on("-a LOGFILE", "--access-log LOGFILE", "Access log file") do |x|
58
+ options[:access_log] = x
59
+ end
60
+
61
+ opts.on("-d", "--detached", "Run as a daemon") do
62
+ options[:detached] = true
63
+ end
64
+
65
+ opts.on("-P", "--pidfile PIDFILE", "Location to write pid file.") do |x|
66
+ options[:pidfile] = x
67
+ end
68
+
69
+ opts.on("--name NAME", "Erlang node name") do |x|
70
+ options[:name] = x
71
+ end
72
+ end.parse!
73
+
74
+ if command = ARGV[0]
75
+ if !%w{reload-handlers stats}.include?(command)
76
+ puts "Invlalid command. Valid commands are:"
77
+ puts " reload-handlers"
78
+ puts " stats"
79
+ exit(1)
80
+ end
81
+
82
+ require 'rubygems'
83
+ require 'bertrpc'
84
+ port = options[:port] || DEFAULT_PORT
85
+ svc = BERTRPC::Service.new('localhost', port)
86
+ puts svc.call.__admin__.send(command.gsub(/-/, '_'))
87
+ else
88
+ if !options[:config]
89
+ puts "A config file must be specified: ernie -c /path/to/config.yml"
90
+ exit(1)
91
+ end
92
+
93
+ config = options[:config]
94
+ port = options[:port] || DEFAULT_PORT
95
+ log_level = options[:log_level] || 2
96
+ pidfile = options[:pidfile] ? "-ernie_server_app pidfile \"'#{options[:pidfile]}'\"" : ''
97
+ detached = options[:detached] ? '-detached' : ''
98
+ access_log = options[:access_log] ? "-ernie_server_app access_log '\"#{options[:access_log]}\"'" : ''
99
+ name = options[:name] ? "-name '#{options[:name]}'" : ''
100
+
101
+ cmd = %Q{erl -boot start_sasl \
102
+ #{detached} \
103
+ +Bc \
104
+ +K true \
105
+ -smp enable \
106
+ #{code_paths}
107
+ #{pidfile} \
108
+ #{access_log} \
109
+ #{name} \
110
+ -ernie_server_app port #{port} \
111
+ -ernie_server_app config '"#{config}"' \
112
+ -ernie_server_app log_level #{log_level} \
113
+ -run ernie_server_app boot}.squeeze(' ')
114
+ puts cmd
115
+ exec(cmd)
116
+ end
@@ -0,0 +1,76 @@
1
+ % erlc *.erl && erl ebench.beam -run ebench start 10000 20 ext add
2
+
3
+ -module(ebench).
4
+ -export([start/1]).
5
+
6
+ start([Ni, Ci, Modi, Funi]) ->
7
+ Nt = list_to_integer(Ni),
8
+ C = list_to_integer(Ci),
9
+ Mod = list_to_atom(Modi),
10
+ Fun = list_to_atom(Funi),
11
+ N = round(Nt / C),
12
+ T0 = erlang:now(),
13
+ Waiter = spawn(fun() -> wait(T0, N * C) end),
14
+ spawner(Waiter, N, C, Mod, Fun).
15
+
16
+ spawner(_Waiter, _N, 0, _Mod, _Fun) ->
17
+ ok;
18
+ spawner(Waiter, N, C, Mod, Fun) ->
19
+ spawn(fun() -> loop(Waiter, N, Mod, Fun) end),
20
+ spawner(Waiter, N, C - 1, Mod, Fun).
21
+
22
+ % X is the total number of responses to wait for
23
+ wait(T0, XTotal, 0) ->
24
+ T1 = erlang:now(),
25
+ Diff = timer:now_diff(T1, T0),
26
+ Mean = Diff / XTotal,
27
+ io:format("~p requests completed in ~.2fs~n", [XTotal, Diff / 1000000]),
28
+ io:format("Mean request time: ~.2fms (~.2f r/s)~n", [Mean / 1000, XTotal / (Diff / 1000000)]),
29
+ init:stop();
30
+ wait(T0, XTotal, X) ->
31
+ receive
32
+ done -> wait(T0, XTotal, X - 1)
33
+ end.
34
+
35
+ wait(T0, X) ->
36
+ wait(T0, X, X).
37
+
38
+ loop(_Waiter, 0, _Mod, _Fun) ->
39
+ ok;
40
+ loop(Waiter, N, Mod, Fun) ->
41
+ hit(Waiter, Mod, Fun),
42
+ loop(Waiter, N - 1, Mod, Fun).
43
+
44
+ hit(Waiter, Mod, Fun) ->
45
+ % io:format("outgoing!~n", []),
46
+ Host = "localhost",
47
+ case gen_tcp:connect(Host, 8000, [binary, {packet, 4}]) of
48
+ {ok, Sock} -> process(Waiter, Mod, Fun, Sock);
49
+ Any ->
50
+ io:format("Unable to establish connection: ~p~n", [Any]),
51
+ Waiter ! done
52
+ end.
53
+
54
+ process(Waiter, Mod, Fun, Sock) ->
55
+ % Info = term_to_binary({info, priority, [low]}),
56
+ % ok = gen_tcp:send(Sock, Info),
57
+ Request = term_to_binary({call, Mod, Fun, args(Fun)}),
58
+ ok = gen_tcp:send(Sock, Request),
59
+ receive
60
+ {tcp, _Port, Reply} ->
61
+ % io:format("~p~n", [Reply]),
62
+ Res = res(Fun),
63
+ {reply, Res} = binary_to_term(Reply);
64
+ {tcp_closed, Port} ->
65
+ io:format("Connection closed after sending data: ~p~n", [Port]);
66
+ Any ->
67
+ io:format("Unexpected message: ~p~n", [Any])
68
+ end,
69
+ Waiter ! done,
70
+ ok = gen_tcp:close(Sock).
71
+
72
+ args(add) -> [1, 2];
73
+ args(fib) -> [20].
74
+
75
+ res(add) -> 3;
76
+ res(fib) -> 10946.
@@ -0,0 +1,2 @@
1
+ {application, ernie_server_app,
2
+ [{mod, {ernie_server_app, []}}]}.