schleyfox-ernie 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
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, []}}]}.