11

I have a thread that listens for new connections

new_fd = accept(Listen_fd, (struct sockaddr *) & their_addr, &sin_size);

and another thread that closes Listen_fd when when it's time to close the program. After Listen_fd is closed however, it still blocks. When I use GDB to try and debug accept() doesn't block. I thought that it could be a problem with SO_LINGER, but it shouldn't be on by default, and shouldn't change when using GDB. Any idea whats going on, or any other suggestion to closing the listing socket?

BrandonToner
  • 111
  • 1
  • 1
  • 3

5 Answers5

7

Use: sock.shutdown (socket.SHUT_RD)

Then accept will return EINVAL. No ugly cross thread signals required!

From the Python documentation: "Note close() releases the resource associated with a connection but does not necessarily close the connection immediately. If you want to close the connection in a timely fashion, call shutdown() before close()."

http://docs.python.org/3/library/socket.html#socket.socket.close

I ran into this problem years ago, while programming in C. But I only found the solution today, after running into the same problem in Python, AND pondering using signals (yuck!), AND THEN remembering the note about shutdown!

As for the comments that say you should not close/use sockets across threads... in CPython the global interpreter lock should protect you (assuming you are using file objects rather than raw, integer file descriptors).

Here is example code:

import socket, threading, time

sock = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind (('', 8000))
sock.listen (5)

def child ():
  print ('child  accept ...')
  try:  sock.accept ()
  except OSError as exc :  print ('child  exception  %s' % exc)
  print ('child  exit')

threading.Thread ( target = child ).start ()

time.sleep (1)
print ('main   shutdown')
sock.shutdown (socket.SHUT_RD)

time.sleep (1)
print ('main   close')
sock.close ()

time.sleep (1)
print ('main   exit')
mpb
  • 1,277
  • 15
  • 18
  • 4
    I have a C++ program that opens a listen socket in the main thread and spawns a child thread to accept incoming connections. The main thread needs to shut down the acceptor thread upon receiving a signal. `close(s)` doesn't work because `accept(…)` continues to block on the closed socket, but `shutdown(s, SHUT_RD)` makes the `accept(…)` call bail immediately with `EINVAL`. Perfect solution, and it works even if the acceptor thread is between calls to `accept(…)` when the shutdown occurs. Thanks! – Matt Whitlock Apr 02 '16 at 03:56
  • For c++ code example see this answer: https://stackoverflow.com/a/62356967/70405 – Alex Jun 13 '20 at 09:37
  • I've seen several answers for closing a socket from another thread. This one is super clean and doesn't use polling though! Thanks so much! – Eric Reed Aug 27 '20 at 18:50
6

The behavior of accept when called on something which is not a valid socket FD is undefined. "Not a valid socket FD" includes numbers which were once valid sockets but have since been closed. You might say "but Borealid, it's supposed to return EINVAL!", but that's not guaranteed - for instance, the same FD number might be reassigned to a different socket between your close and accept calls.

So, even if you were to isolate and correct whatever makes your program fail, you could still begin to fail again in the future. Don't do it - correct the error that causes you to attempt to accept a connection on a closed socket.

If you meant that a call which was previously made to accept continues blocking after close, then what you should do is send a signal to the thread which is blocked in accept. This will give it EINTR and it can cleanly disengage - and then close the socket. Don't close it from a thread other than the one using it.

Borealid
  • 95,191
  • 9
  • 106
  • 122
  • +1 My guess is the last scenario. Thread 1 is blocking on accept. Thread 2 closes socket. Thread1 blocks forever. – Duck Feb 20 '12 at 17:21
  • This is correct. Note that you will probably want to install an empty signal handler for the signal that you use. – caf Feb 21 '12 at 02:24
  • 1
    Oh this part of POSIX really sucks. – thodg Jun 23 '17 at 14:48
4

The shutdown() function may be what you are looking for. Calling shutdown(Listen_fd, SHUT_RDWR) will cause any blocked call to accept() to return EINVAL. Coupling a call to shutdown() with the use of an atomic flag can help to determine the reason for the EINVAL.

For example, if you have this flag:

std::atomic<bool> safe_shutdown(false);

Then you can instruct the other thread to stop listening via:

shutdown_handler([&]() {
  safe_shutdown = true;
  shutdown(Listen_fd, SHUT_RDWR);
});

For completeness, here's how your thread could call accept:

while (true) {
  sockaddr_in clientAddr = {0};
  socklen_t clientAddrSize = sizeof(clientAddr);
  int connSd = accept(Listen_fd, (sockaddr *)&clientAddr, &clientAddrSize);
  if (connSd < 0) {
    // If shutdown_handler() was called, then exit gracefully
    if (errno == EINVAL && safe_shutdown)
      break;
    // Otherwise, it's an unrecoverable error
    std::terminate();
  }
  char clientname[1024];
  std::cout << "Connected to "
            << inet_ntop(AF_INET, &clientAddr.sin_addr, clientname,
                         sizeof(clientname))
            << std::endl;
  service_connection(connSd);
}
Mike Spear
  • 832
  • 8
  • 13
1

It's a workaround, but you could select on Listen_fd with a timeout, and if a timeout occured check that you're about to close the program. If so, exit the loop, if not, go back to step 1 and do the next select.

Karoly Horvath
  • 94,607
  • 11
  • 117
  • 176
0

Are you checking the return value of close? From linux manpages, (http://www.kernel.org/doc/man-pages/online/pages/man2/close.2.html) "It is probably unwise to close file descriptors while they may be in use by system calls in other threads in the same process. Since a file descriptor may be reused, there are some obscure race conditions that may cause unintended side effects". You can use a select instead of an accept and wait for some event from the other thead, then close the socket in the listener thread.

gztomas
  • 3,030
  • 3
  • 27
  • 38