💾 Archived View for ser1.net › post › running-a-code-server-in-erlang.gmi captured on 2023-05-24 at 17:55:19. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2022-07-16)

➡️ Next capture (2024-03-21)

-=-=-=-=-=-=-

For as old as Erlang is, there's a distinct lack of documentation about some aspects of it.  I've had a hard time finding information about how to run a code server, so I'm going to write down what I've learned here.

First, there are a couple of things I should note:

1. While you **can** use the erlang runtime as a hack-as-you-go platform, it's really better to follow the OTP principles; you get a lot of things for free that you otherwise end up writing yourself.  So, go ahead and use gen_servers, and write your `.app` and `.rel` scripts.  I know, it feels like a lot of metadata, and *I* **hate** systems that require a bunch of metadata files scattered around.  This is because a profusion of metadata files is a common feature of some of the *worst* software systems -- Hibernate, for example -- but in this case, it's a necessary evil.  You* *can get pretty far without the files, but in the end, you'll end up needing them anyway to take full advantage of the OTP, so go ahead and write them.

2. Erlang has a really wicked (as in, good) distribution mechanism, so if you can use it, consider it.  When used, it'll build a tar-ball containing everything you need, including a stripped-down runtime, to run  a worker node; copy the tar-ball to the destination machine, unpack it, and run your app.

3. What I describe here is probably not Erlang an "best practice," but it satisfies a need, and with a small tweak, it produces #3 above.

This whole thing hinges entirely on the boot script.  I have been unable to figure out a way to run a code server without a boot script; the Erlang documentation says this, indirectly, in several places by noting that the boot script tells the runtime how (and from where) to load code.  Although you might think, from reading the erl manpage, that you can inform the runtime about where to get code purely with `-loader inet -hosts IP -id Name`, you can't -- at least, I've not been able to coerce it so.  A boot script depends on an `.app`, and while it can be written by hand, it's easier to generate it from a `.rel`.

This is what I believe to be the bare minimum to get a client to load code from a boot server:

And if anybody knows of a way to use a code server with less crap, let me know.  If you want to start up services on the worker, that's easy enough -- just fire them off from within the supervisor.

I'm starting entirely from scratch; you should be able to copy/paste and get up and running:

44)~% mkdir myapp ; cd myapp
45)~/myapp % mkdir ebin src 
46)~/myapp % cat > src/myapp.erl
-module(myapp).
-behaviour(application).
-export([start/2,stop/1]).
 
start(_,_) -> myapp_sup:start_link().
stop(_) -> ok. 
47)~/myapp % cat > src/myapp_sup.erl
-module(myapp_sup).
-behaviour(supervisor).
-export([start_link/0, init/1]).
start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init(_) -> {ok, {{one_for_one, 1, 60}, [] }}. 
48)~/myapp % cat > ebin/myapp.app
{application, myapp,
  [{description, "MyApp"},
   {id, "MyApp"},
   {vsn, "0.0.1"},
   {modules, [ myapp, myapp_sup ]}, 
   {applications, [kernel,stdlib]},
   {maxT, infinity},
   {registered, [myapp, myapp_sup]},
   {mod, {myapp,[]}}
  ]
}.
49)~/myapp % cat > ebin/myapp.rel
{release, {"MyApp", "0.0.1"}, {erts, "5.8.2"},
  [{kernel, "2.14.2"}
  ,{stdlib, "1.17.2"}
  ,{sasl, "2.1.9.2"}
  ,{crypto, "2.0.2"}
  ,{myapp, "0.0.1"}
  ]
}.
50)~/myapp % erlc -o ebin src/*.erl
51)~/myapp % erl -id bootserver -name bootserver -setcookie cookie -run erl_boot_server start 192.168.0.6
Erlang R14B01 (erts-5.8.2) [source] [smp:2:2] [rq:2
async-threads:0] [hipe] [kernel-poll:false]
 
Eshell V5.8.2  (abort with ^G) 
(bootserver@Sean-Russells-MacBook-Pro.local)1> systools:make_script("ebin/myapp", {path,["./ebin"]},local]).
ok
(bootserver@Sean-Russells-MacBook-Pro.local)2> ls("ebin").
myapp.app          myapp.beam         myapp.boot         myapp.rel
myapp.script       myapp_sup.beam
ok
(bootserver@Sean-Russells-MacBook-Pro.local)3> 
User switch command
 --> q
52)~/myapp %

  You don't **need** to run erl with all of those args if you're just making the boot script; they're needed to run the boot server.  Here's the client -- I've pared it down, because SASL dumps a bunch of debugging information that isn't relevant for this tutorial:

53)~ % erl -id slave -name slave -setcookie cookie -loader inet -hosts 192.168.0.6 -boot /Users/seanrussell/myapp/ebin/myapp
Erlang R14B01 (erts-5.8.2) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
 
 
=PROGRESS REPORT==== 22-Jan-2011::08:40:58 === 
    supervisor: {local,sasl_safe_sup}
       started: [{pid,<0.40.0>},
           {name,alarm_handler},
           {mfargs,{alarm_handler,start_link,[]}}, {restart_type,permanent},
           {shutdown,2000},
           {child_type,worker}]
=PROGRESS REPORT==== 22-Jan-2011::08:40:58 ===
   application: myapp
    started_at: 'slave@Sean-Russells-MacBook-Pro.local'
Eshell V5.8.2  (abort with ^G)
(slave@Sean-Russells-MacBook-Pro.local)1> pwd().
/Users/seanrussell
ok
(slave@Sean-Russells-MacBook-Pro.local)2> code:which(myapp).
"/Users/seanrussell/myapp/ebin/myapp.beam"
(slave@Sean-Russells-MacBook-Pro.local)3>
User switch command
 --> q
54)~ % 

Because I did this all on one machine, you might think that the slave is getting the code directly from the filesystem, but it isn't; this works just as well from a different machine.

It's instructive to take a look at the `myapp.script` file that `systools` creates, paying special attention to the paths.  If you remove the `local` argument from `make_script`, then `systools` will change the absolute paths to `$ROOT`, which it expands to the value of that variable on the server, which is in turn set by the `erl` command.  You can also add an argument {variable,[MYAPP,"."]}, and systools will replace the full path with the variable $MYAPP, which you can then define on the slave by adding the argument `-env MYAPP /absolute/path/to/myapp/parent` to the `erl` command.

Hopefully, this will help somebody else; it took me about three full days to figure this out.  Erlang is well documented, but having good documentation doesn't always provide enough information for how to use a system, and Erlang OTP is horrible in that respect.  Once you get it up and running, though, it's a thing of beauty.