Hacker News new | ask | show | jobs
by mapcars 583 days ago
Its always nice to see people experimenting with different technologies.

I'm curious about Erlang server, do you see any advantage or features that Erlang provides, compared to for example if the server was running in Python via multiple instances?

2 comments

We haven't touched the distributed part of the game, but our understanding is that when that time comes, it will be easier to use the BEAM approach given that it was made for this purpose.

Given the experience so far, it seems that using Erlang was the correct choice, not only because of the above, but also because Erlang made the server implementation way easier than we thought.

I see now, you are sending messages directly to Erlang server so you don't have to worry about network sockets.

In my experience the issues with Erlang come with working with data structures, records are not flexible and there is not much one can do to abstract the boilerplate.

Having a potentially untrusted client connect to the erlang node as a c_node (which seems to be what zerl does) is not a good idea generally, as connecting that way essentially allows the client to execute arbitrary code on the server.
Please correct me if I say anything wrong.

As far as I can tell, this is not possible at all; the serialization layer (Zerl) cannot send arbitrary code to another node. Now, assuming we implement this, I also think this is not possible due to how the server is designed; based on supervisors and child processes for user sessions.

We recently became aware that you can indeed send tuples that have fixed effects when using the supervisor behavior, so it may be totally possible and probable that one could exploit this vulnerability to some degree in our server. We plan to investigate more about it as we continue to learn more about OTP and the BEAM.

If you're using zerl on the client and plain dist on the server; the question isn't what Zerl can serialize, but what the server will process.

With stock OTP dist, there is no barrier between nodes. Stock OTP runs an rpc server that you can use to send function calls to run, which can include BEAM code to load (or file I/O to do); and even if that's disabled, you can spawn a process to run a function with arguments on a remote node without needing an rpc server at all.

How can one protect the server then? Do we need some special behavior and/or library?
I think this behavior can be fixed by properly using something like `lib_chan`, but we needed something that worked first for our Func Prog Sweden demo.

Indeed a malicious client can craft an brutal kill message as long as it knows the PID a process (either a worker or a supervisor) for instance.

We are using maps all over the place in the server, and so far nothing has been annoying.

I gotta say though that lack of infix custom operators for the monadic bind is a pain.

Take a look at Erlang's "parse transforms" which would allow you to implement some syntactic sugar. That's what is used to implement qlc, the query language for ETS/Mnesia - that one adds new semantics to list comprehensions, but you can modify any part of the syntax you want.

Also, Elixir supports macros and infix operator overloading - have you considered using it? If you know Erlang's stdlib and BEAM, switching to Elixir (and back) is almost painless. Not sure which monads you needed, but `with` macro is built-in, and it's a monadic bind for Option types (well, more or less). Adapting it for other monads shouldn't be hard.

Thanks, I will for sure take a look!
For reference (for the "parse transform" approach in Erlang): https://github.com/rabbitmq/erlando - it doesn't look maintained, but it's probably still usable; otherwise, you might get some inspiration from the code :) This also (ab)uses list comprehension syntax:

    write_file(Path, Data, Modes) ->
        Modes1 = [binary, write | (Modes -- [binary, write])],
        do([error_m ||
            Bin <- make_binary(Data),
            Hdl <- file:open(Path, Modes1),
            Result <- return(do([error_m ||
                                 file:write(Hdl, Bin),
                                 file:sync(Hdl)])),
            file:close(Hdl),
            Result]).
(if one of the calls returns `{error, Reason}` tuple, the execution terminates and the tuple is returned; otherwise, `{ok, Value}` tuple is unpacked)
Also, Erlang makes it bizarrely simple to have a single process per user (and it's actually what we did for the demo).