Python – Why does select() in the parent process make accept() in the child process unusable?

Why does select() in the parent process make accept() in the child process unusable?… here is a solution to the problem.

Why does select() in the parent process make accept() in the child process unusable?

I have a parent process that creates 2 server sockets and calls select() on them to wait for a new connection. When the connection arrives, a message is sent to the child process (created with fork(), after the server socket is created, so they are shared).

In this child, calling accept() on the server socket does not work. I GET AN EAGAIN ERROR (NON-BLOCKING SOCKET). Calling accept() in the main process works well.

Of course, I didn’t call accept() in the main process at all, I just tested if it worked and it did.

Why can’t I call accept() in the child process after select() in the parent process?

EDIT: The goal here is to create a fixed number of workers (say 8) to handle client connections, just like in the prefork model. These connections will be persistent connections, unlike HTTP. The goal is to load balance connections between workers.

To do this, I used a shared memory variable that contains the number of clients the worker is currently connected to. I want to “require” the worker with the fewest number of clients to handle new connections.

That’s why I do select() in the parent process and then send a message to the child process, because I want to “choose” which process will handle the new connection.

The server listens for multiple sockets (one for SSL, one not), which is why I use select() in the child process instead of using accept() directly, because I can’t accept() on multiple sockets of my child worker.

Solution

Actually, the problem is not what I thought at first. Here’s a review of some of the basic load balancing work I’ve done to implement connections between worker processes.

  • A master process (parent process) creates 2 server sockets, bind() and listen() (e.g. with and without SSL).
  • I created 8 child processes with fork(), so they inherit the socket of the parent process
  • The main process runs select() in an infinite loop
  • When one of its two sockets is available, it sends a message through the pipe to the child. Because the shared memory value determines the child process, the value contains the current number of clients in the “child process”. Select the process that currently processes the fewest number of clients.
  • This child process then calls accept() on the server socket (the socket used between the two is passed in the pipe, so child knows on which to call accept()).

The problem is that my parent process tells the child process to accept the socket and re-enter the loop immediately afterward, it runs select() again. But if child hasn’t accepted a socket yet, select() returns again for the same connection. That’s why I’m getting the EAGAIN error, I actually called accept() twice (or more depending on speedinterprocess race condition)!

The solution is to wait for child to answer “Hey, I accepted the connection, it’s okay!” on the pipe, and then go back to select(). Circulate.

This works just fine. For the curious, the implementation in Python can be used here: https://github.com/thibautd/Kiwi !

Related Problems and Solutions