DidactMacros

DidactMacros

Servers Exiting vs Terminating in Programming Elixir Book

I have a question regarding server termination vs server exit.

I would appreciate that this question be considered in light of the below code and excerpt.

defmodule Duper.Worker do
    use GenServer, restart: :transient

    def start_link(_) do
        GenServer.start_link(__MODULE__, :no_args)
    end

    def init(:no_args) do
        Process.send_after(self(), :do_one_file, 0)
        { :ok, nil }
    end

    def handle_info(:do_one_file, _) do
        Duper.PathFinder.next_path()
        |> add_result()
    end

    defp add_result(nil) do
        Duper.Gatherer.done()
        {:stop, :normal, nil}
    end

    defp add_result(path) do
        Duper.Gatherer.result(path, hash_of_file_at(path))
        send(self(), :do_one_file)
        { :noreply, nil }
    end

    defp hash_of_file_at(path) do
        File.stream!(path, [], 1024*1024)
        |> Enum.reduce(
        :crypto.hash_init(:md5),
        fn (block, hash) ->
            :crypto.hash_update(hash, block)
        end)
        |> :crypto.hash_final()
    end

end

Why Can’t We Just Write a Looping Function?

We can implement a loop in Elixir using a recursive function call.
But the worker server doesn’t do this. Instead, it sends itself a
message and then exits after processing each file.

The reason is that the Elixir runtime won’t let any one invocation of
a server hog the CPU forever. Instead it sets a timeout on each call
or cast into a GenServer (by default 5 seconds). If the call or cast
handler has not returned in that time, the runtime assumes
something has gone wrong and terminates the server.
Processing a million files in a loop will take more than 5 seconds.
So we instead just process one file per entry into the server, and then
queue up another message to process the next on a fresh entry. The
result: no timeouts.

My question is regarding the bolded and italicised statement. My understanding was that a Gen server exited by returning :stop, :terminate, or calling System.halt(num), however the explanation suggests that the server exits implicitly, so I wonder if exiting and terminating are different.

Also just prior to when server is said to exit, it sends itself the :do_one_file atom, but the init of the server already does this, so the server can not be terminating and reinitialising since the sending of such a message when would not be necessary at termination as it also takes place at initialisation.

There definitely seems to be a difference between exiting and termianting, I would appreciate some clarity on the difference(s).

Marked As Solved

al2o3cr

al2o3cr

The use of “exits” here is confusing - I believe the author means “returns” (as in “returns to the GenServer loop that called handle_whatever”) because as you’ve noted “exit” has a very specific meaning.

The “or cast” of this sentence is flatly wrong; a cast can’t timeout - you can even cast to processes that aren’t alive without getting an error.

I’m also unsure what this has to do with anything, since the posted code doesn’t define a handle_call. There’s a different timeout from the supervisor inside start_link - IIRC it will eventually fail if init takes too long to return - but that’s not really the same thing.

Also Liked

lud

lud

Instead, it sends itself a message and then exits after processing each file.

The description is wrong. That server does not exit after processing each file, but only when Duper.PathFinder.next_path() returns nil, I assume when there is no more file.

By returning a stop tuple, the implementation tells GenServer to terminate the current server, which will in the end either call exit/1 or just stop looping forever awaiting messages (I don’t remember how it is done in gen_server). (spawn(fn -> :ok end) and spawn(fn -> :ok; exit(:normal) end) are equivalent.)

but the init of the server already does this, so the server can not be terminating and reinitialising since the sending of such a message when not be necessary at termination when it is also takes place at initialisation.

Rember that if the server restarts, it is a new process, so sending to self will not forward the messages to the new instance.

lud

lud

Where does that material comes from? It seems that the author does not fully understand the very basics of generic servers. Which is quite surprising if the book is “Programming Elixir” by Dave Thomas. Is it some other book?

In your case, send or send_after makes no difference. The generic server init callback is called, and once it is finished the gen_server module will start to pull messages from the process mailbox and dispatch them to handle_info (or handle_call/handle_cast).

During the execution of init, the process can receive many messages from other processes, if those processes know the pid of your process (likely not, your process would have to tell them first in init) or use the registered name of your process. When calling send(self(), message) during init, you just add the message to the mailbox of the process, but that message will only be read after init is complete. So using send_after is slightly different but achieves the same thing.

(Note that a Task would be more appropriate with the current set of features of your code, as the server does not actually handle other types of messages that could be interleaved with the processing of files).

I imagine it would be the same, since the only delaying factor is readiness to receive the message.

So yes, the readiness is just “when init is done, and returned {:ok, ...

I guess send_after must mean send to be received after the block (or process?), there seems be be more internally going on than the explanation elucidates, for me at least.

If you’d use send_after with 5000 for instance, that gives time to other processes to send you messages. But once the loop would be started, as add_result uses send, it would have the same behaviour. Only the start of the loop would be delayed by five seconds.

lud

lud

As I have said:

So the send_after allows for the initialisation (invocation?) to complete

Not really. If in init you call a function that does a loop over files, your init function would not complete until that is done too. But it will complete when there are no more files. As said by @al2o3cr , “it will eventually fail if init takes too long to return - but that’s not really the same thing”.

If the call or cast handler has not returned in that time, the runtime assumes something has gone wrong and terminates the server. Processing a million files in a loop will take more than 5 seconds. So we instead just process one file per entry into the server, and then queue up another message to process the next on a fresh entry. The result: no timeouts.

The timeout in question here is the default timeout of 5 seconds of GenServer.call. The problem with this example is that is does not demonstrate how a call would timeout, because there is no GenServer.call involved.

Where Next?

Popular in Questions Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
JulienCorb
I am trying to implement my new.html.eex file to create new posts on my website. new.html.eex: <h1>Create Post</h1> <%= ...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New

Other popular topics Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 42158 114
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New

We're in Beta

About us Mission Statement