How to call (and sleep) on gen_tcp:accept and still process system messages at the same time?

StackOverflow https://stackoverflow.com/questions/10638776

  •  09-06-2021
  •  | 
  •  

Question

I'm still kind of new to the erlang/otp world, so I guess this is a pretty basic question. Nevertheless I'd like to know what's the correct way of doing the following.

Currently, I have an application with a top supervisor. The latter will supervise workers that call gen_tcp:accept (sleeping on it) and then spawn a process for each accepted connection. Note: To this question, it is irrelevant where the listen() is done.

My question is about the correct way of making these workers (the ones that sleep on gen_tcp:accept) respect the otp design principles, in such a way that they can handle system messages (to handle shutdown, trace, etc), according to what I've read here: http://www.erlang.org/doc/design_principles/spec_proc.html

So,

  • Is it possible to use one of the available behaviors like gen_fsm or gen_server for this? My guess would be no, because of the blocking call to gen_tcp:accept/1. Is it still possible to do it by specifying an accept timeout? If so, where should I put the accept() call?
  • Or should I code it from scratch (i.e: not using an existant behavior) like the examples in the above link? In this case, I thought about a main loop that calls gen_tcp:accept/2 instead of gen_tcp:accept/1 (i.e: specifying a timeout), and immediately afterwards code a receive block, so I can process the system messages. Is this correct/acceptable?

Thanks in advance :)

Was it helpful?

Solution 2

I've actually found the answer in another question: Non-blocking TCP server using OTP principles and here http://20bits.com/article/erlang-a-generalized-tcp-server

EDIT: The specific answer that was helpful to me was: https://stackoverflow.com/a/6513913/727142

OTHER TIPS

As Erlang is event driven, it is awkward to deal with code that blocks as accept/{1,2} does.

Personally, I would have a supervisor which has a gen_server for the listener, and another supervisor for the accept workers. Handroll an accept worker to timeout (gen_tcp:accept/2), effectively polling, (the awkward part) rather than receiving an message for status.

This way, if a worker dies, it gets restarted by the supervisor above it. If the listener dies, it restarts, but not before restarting the worker tree and supervisor that depended on that listener. Of course, if the top supervisor dies, it gets restarted. However, if you supervisor:terminate_child/2 on the tree, then you can effectively disable the listener and all acceptors for that socket. Later, supervisor:restart_child/2 can restart the whole listener+acceptor worker pool.

If you want an app to manage this for you, cowboy implements the above. Although http oriented, it easily supports a custom handler for whatever protocol to be used instead.

You can make it as a gen_server similar to this one: https://github.com/alinpopa/qerl/blob/master/src/qerl_conn_listener.erl.

As you can see, this process is doing tcp accept and processing other messages (e.g. stop(Pid) -> gen_server:cast(Pid,{close}).)

HTH, Alin

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top