9 ms·
universal_server() -> receive {become, F} -> F() end. Honestly I don't fully appreciate the power of this universal server. Can any
by refactorworks 3y ago
universal_server() ->
receive
{become, F} ->
F()
end.
Honestly I don't fully appreciate the power of this universal server. Can anyone help?
- brokencode 3y agoNormally you need to know ahead of time what your server is going to be used for and write the code in advance. But with Erlang, you can have a distributed network of Erlang servers where the server is a generic computing resource that can do anything the client wants. The code actually comes from the client. No need to get your system administrators to install some binary on all the machines. You simply pass the function along and the remote machine calls it.
- refactorworks 3y agoThanks! I get it — no need to install binaries of specific servers.
- rramadass 3y agohttps://news.ycombinator.com/item?id=37415159 https://news.ycombinator.com/item?id=37415159
- nesarkvechnep 3y agoDouble check whether you think of the correct abstraction level. The BEAM is more like the JVM than Kubernetes.
- zbentley 3y agoOne of the most interesting characteristics of the BEAM, in my opinion, is that it's similar to both. As a memory-managed bytecode runtime it's similar to the JVM, and as a distributed process orchestrator+discovery and RPC system, it's similar to Kubernetes. I often wonder what would have happened if the "BEAM renaissance" (driven largely by the birth of Elixir and associated tools) had happened a decade earlier, before Kubernetes became the de-facto standard for ad-hoc distributed computing in web software.
- zaphirplane 3y agoA few people here are liking the idea, isn’t this the exact definition of arbitrary code execution (exploit) It’s possible people are showing the capability of BEAM thou
- coldtea 3y agoIt's the definition of any distributed system (or even single server) where you can deploy code, whether it's Erlang or a kubernetes cluster you setup. Is pushing new code for your server to run "arbitrary code execution"? I guess we can call it that. Is it an exploit? Depends if the code comes from some random person on the internet from mechanisms that you don't intend for pushing new code to run (e.g. through a buffer overflow on your server or XSS), or if it comes from yourself through your official mechanisms.
- jerf 3y ago"Arbitrary code execution", yes, "exploit", no. You need to be "inside" the BEAM cluster and a member of the BEAM cluster to do this. That is not something you hand off to end users, just as you do not normally hand end users direct access to your database socket or other such resources. In Raymond Chen's terminology [1], if you're sending Erlang terms to the BEAM cluster, you're already on the privileged side of the airtight hatchway. [1]: https://devblogs.microsoft.com/oldnewthing/20060508-22/?p=31283 https://devblogs.microsoft.com/oldnewthing/20060508-22/?p=31...
- brokencode 3y agoOnly if you allow untrusted clients to join to the cluster. To really make use of something like this, you’d have to control the clients rather than opening it up to anybody on the public internet.
- macintux 3y agoI may be answering the wrong question, but I’ll give it a shot. Erlang’s architecture is unusual; both the virtual machine and the language are built around the idea of tiny processes operating concurrently, each process running in an infinite loop waiting for incoming messages to interpret. This allows a process to become whatever code you send it. If you need a process to control a microwave, and then run some quantum computations, and then predict the winner of tomorrow’s football game, you just send it the code it needs for each operation and it happily does so.
- notRobot 3y agoAh, so TL;DR is that each process evals the supplied code and returns the output to the caller?
- macintux 3y agoSlightly different in the low-level details, but conceptually accurate.
- Jtsummers 3y agoThe universal server may or may not return anything. It just executes whatever is passed to it. In Erlang there are two ways of "calling" (in quotes for a reason). There's conventional function calling which is the regular synchronous style that we all know and love: f(10). This will produce a result and return it to its caller. The other isn't really a call, it's "sending": Pid ! 10. Some process id has been sent the value 10. It may be on this same node, it may be on another node, I don't have to care (sometimes I do though). This is asynchronous. Once a send is done the sending process will continue on (perhaps even terminating). At the other end of the send is a receive (hopefully, otherwise somebody's queue is getting filled up...): receive N -> ... % do something with this value end. In the case of `universal_server` we don't know what it will become, it's just going to execute whatever 0-ary function is passed. That function may or may not include a "return" (sending a value back to the origin). It could also just terminate the universal server. Or it could temporarily convert the universal server into something else and then become a universal server again.
- Jtsummers 3y agoYou execute that on any node in your system and send it a message `{become, fun some_function/0}`. Once it receives that conforming message (a tuple of two items, the atom `become` and a 0-ary function) and that node will stop being a "universal server" and become whatever process "some_function" describes. And in his case he had access to some 9000 computers. If each was running at least one Erlang node and each node was running a universal server, then with a very simple program he could write a function, serialize the function, and distribute the function to his 9k+ running universal servers and turn them into 9k+ specialized servers.
- softirq 3y agoIf you are familiar with Go, it's similar to a goroutine that waits for someone to send it an anonymous function to start executing. In Erlang, you solve problems by spawning lots of processes, and most processes are waiting to accept messages. One critical difference is that in Erlang, processes can run on remote machines, seamlessly. This process accepts a message which is a tuple containing the atom become, which is basically just an enum, and a function. Another process, such as the Erlang shell, can send this tuple message with the function to this process at any time so that it "becomes" that function. What he is saying is that you can swap out the logic of this waiting loop with whatever protocol logic you want. His example was that he had a fleet of machines that were running this loop, and he sent all of them a function that implemented a gossip protocol. But he could easily send them all another message that turns them all into BitTorrent clients. Joe was an absolute genius and an extremely kind person. I had the honor of meeting him once. Erlang is still one of the most beautiful technical creations I've ever encountered. It really does make you see concurrency in a whole new way.
- thakoppno 3y ago> One critical difference is that in Erlang, processes can run on remote machines, seamlessly. Is there a concise way to explain how Erlang achieves this property?
- deleted 3y ago[deleted]
- querulous 3y agoit's not as mysterious as it sounds. every data structure (including modules and anonymous functions) has a binary serialization and every erlang vm is also an rpc server that can receive arbitrary data -- including whole programs -- and execute them. your vm of course needs to know about the remote vms to do so but that's where the rudimentary clustering mechanism in erlang comes into play
- thomasfortes 3y ago
- agundy 3y agoThis universal server is a process listening for a message with a function and then it executes that function here the function is just a different infinite server turning a running process into something new. I think it shows off the power of erlang processes and the ability to pass functions to replace running processes with new behavior without changing pids.
- h0l0cube 3y agoTo explain further, the `receive` keyword is a bit like a switch statement (but actually a pattern matcher) for incoming messages. Here they made a new server that takes an return process (From) and a number (N). The exclamation point sends the result back to the return process. factorial_server() -> receive {From, N} -> From ! factorial(N), factorial_server() end. factorial(0) -> 1; factorial(N) -> N * factorial(N-1). This code then spawns the server, sends a message to that server to become a factorial server, then tells that server to send it back a message with the factorial of 50. It then specifies it's own message listener that takes whatever it receives and returns it. test() -> Pid = spawn(fun universal_server/0), Pid ! {become, fun factorial_server/0}, Pid ! {self(), 50}, receive X -> X end. A couple of the major advantages of Erlang its distributed parallel nature, and also hot code update. Which happens in `Pid ! {become, fun factorial_server/0}` where it overides the receive loop of universal_server with that of the factorial_server. Though I think proper hot code update doesn't work like this
- coldtea 3y agoAnd /0 is the local vm?
- chrisoverzero 3y ago`/0` refers to the arity of the function.
- codemonkey-zeta 3y agoIt's the arity of the function. In Erlang there are no variadic functions, but functions with different arities can have the same name, so universal_server/0 takes no arguments, fib/1 takes 1 argument, fib/2 takes 2 arguments, the second arg may be the accumulator for a recursive Fibonacci, for example, and fib/1 may call fib/2 as an implementation detail.
- jay-barronville 3y agoBasically, the idea is that it can become any type of server you’d like. The actual function to run the server is passed by the client and the Erlang process effectively morphs into that server after constructing it using the provided function. When you consider Erlang’s hot reloading abilities, this simple architecture becomes even more powerful. Another way to look at it is that the Erlang process is just compute waiting for work to do and the work is to run full-blown servers. Pretty neat.
- jay-barronville 3y agoI was typing this answer on my phone and I didn’t realize several folks already responded. Move along, nothing to see here. Haha.
- xvilka 3y agoIs the same trick possible with other BEAM languages? E.g. Elixir?
- hmmokidk 3y agoYeah. I don’t see why not.
- qohen 3y agoYes. Here[0] is a gist showing it done in Elixir -- as you'll see, it looks very similar to the Erlang code: [0] https://gist.github.com/mndvns/80b00cf67d418e8359fb5566b80aeb4c https://gist.github.com/mndvns/80b00cf67d418e8359fb5566b80ae...
- ungamedplayer 3y agoHere is an aggressive worm/virus that exploits this exact mechanism. https://github.com/wmealing/Elixir-virus https://github.com/wmealing/Elixir-virus
- rramadass 3y agoYes; it is a property of the "Erlang Run Time System"(ERTS)/"VM"(BEAM) - https://news.ycombinator.com/item?id=37415159 https://news.ycombinator.com/item?id=37415159
- javajosh 3y agoYou can achieve the same goal, if not so elegantly, if you define a node process that processes HTTP POSTs by evaling the body of the request to replace the previous handling function. In practice you'd quickly want to post a function that behaves normally. However you could also define a function that does something "normal" but has a code path for continually redefining the function. As exotic as this sounds, this is very similar to what web-browsers do with script src tags, especially from 3rd parties. The page is saying "Hey let me eval a function that can do whatever it wants in this context. I trust you!" Most webdevs don't consider this a threat vector!
- octacat 3y agoIt is a nice example, which would be rarely used in the real systems. Could be used for understanding how hot-code reloading could be implemented. Real usage could be if we have some big state and wanna apply some operations on it, we could send "{execute, F}" into the server and pass code (i.e. a simple reference) instead of data.