Let\'s say I start a thread to receive on a port. The socket call will block on recvfrom. Then, somehow in another thread, I close the socket.
On Windows, this will
You are asking for the impossible. There is simply no possible way for the thread that calls close
to know that the other thread is blocked in recvfrom
. Try to write code that guarantees that this happens, you will find that it is impossible.
No matter what you do, it will always be possible for the call to close
to race with the call to recvfrom
. The call to close
changes what the socket descriptor refers to, so it can change the semantic meaning of the call to recvfrom
.
There is no way for the thread that enters recvfrom
to somehow signal to the thread that calls close
that it is blocked (as opposed to being about to block or just entering the system call). So there is literally no possible way to ensure the behavior of close
and recvfrom
are predictable.
Consider the following:
recvfrom
, but it gets pre-empted by other things the system needs to do.close
.socket
and gets the same decsriptor as the one you close
d.recvfrom
, and now it's receiving from the socket the library opened.Oops.
Don'd ever do anything even remotely like this. A resource must not be released while another thread is, or might be, using it. Period.
Call shutdown(sock, SHUT_RDWR)
on the socket, then wait for the thread to exit. (i.e. pthread_join
).
You would think that close()
would unblock the recvfrom()
, but it doesn't on linux.
Here's a sketch of a simple way to use select() to deal with this problem:
// Note: untested code, may contain typos or bugs
static volatile bool _threadGoAway = false;
void MyThread(void *)
{
int fd = (your socket fd);
while(1)
{
struct timeval timeout = {1, 0}; // make select() return once per second
fd_set readSet;
FD_ZERO(&readSet);
FD_SET(fd, &readSet);
if (select(fd+1, &readSet, NULL, NULL, &timeout) >= 0)
{
if (_threadGoAway)
{
printf("MyThread: main thread wants me to scram, bye bye!\n");
return;
}
else if (FD_ISSET(fd, &readSet))
{
char buf[1024];
int numBytes = recvfrom(fd, buf, sizeof(buf), 0);
[...handle the received bytes here...]
}
}
else perror("select");
}
}
// To be called by the main thread at shutdown time
void MakeTheReadThreadGoAway()
{
_threadGoAway = true;
(void) pthread_join(_thread, NULL); // may block for up to one second
}
A more elegant method would be to avoid using the timeout feature of select, and instead create a socket pair (using socketpair()) and have the main thread send a byte on its end of the socket pair when it wants the I/O thread to go away, and have the I/O thread exit when it receives a byte on its socket at the other end of the socketpair. I'll leave that as an exercise for the reader though. :)
It's also often a good idea to set the socket to non-blocking mode also, to avoid the (small but non-zero) chance that the recvfrom() call might block even after select() indicated the socket is ready-to-read, as described here. But blocking mode might be "good enough" for your purpose.
When the socket is closed, I want recvfrom to unblock
recvfrom() is a function specific to UDP sockets on Python. Here's a brief summary of how I solved the problem using an idea referred to as "polling" (try running the program as well, the print statements will give you a solid idea of whats going on):
import socket
import threading
import signal
import time
# Custom class to create a socket, close the socket, and poll the socket
class ServerSocket():
def __init__(self, addresses):
# "Standard" way to create and preapare a working socket
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.bind(addresses)
def poll(self):
self.socket.settimeout(2)
modifiedMsg, senderAddress = self.socket.recvfrom(1024)
def close(self):
self.socket.close()
class ServiceExit(Exception):
"""
Custom exception which is used to trigger the clean exit
of all running threads and the main program.
"""
pass
def service_shutdown(signum, frame):
raise ServiceExit
# Custom class to create a UDP server on a separate thread.
# This server will know to close the blocking UDP socket when the user
# of the main program signals termination via typing CTRL-C into the terminal
class Server(threading.Thread):
def __init__(self, addresses):
threading.Thread.__init__(self)
self.mysocket = ServerSocket(addresses)
# This flag below will help us determine when to stop the "run" loop below
# The while loop below is where interrupt the blocking recvfrom() call by
# timing out every 2 seconds and checking to see if the flag has been set
# to discontinue the while loop
self.shutdown_flag = threading.Event()
def run(self):
while not self.shutdown_flag.is_set():
try:
print('socket blocking')
self.mysocket.poll()
except socket.timeout:
print('socket unblocked')
pass
# as a final step, we close the socket
self.mysocket.close()
print('socket closed')
def main():
# assign the methods that will be called when our main program receives a SIGTERM or SIGINT signal
# You can send this main problem such a signal by typing CTRL-C after you run this program
signal.signal(signal.SIGTERM, service_shutdown)
signal.signal(signal.SIGINT, service_shutdown)
# Start the server thread that will eventually block on recvfrom()
try:
print('starting udp server thread')
udp_server = Server(('localhost', 5000))
udp_server.start()
while True:
time.sleep(0.5)
# This server will accept UDP packets on the local host at port 5000
# Feel free to change these settings to fit your needs
except ServiceExit:
print('shutting down server thread')
udp_server.shutdown_flag.set()
udp_server.join()
print('server thread shut down')
if __name__ == '__main__':
main()
Not an answer, but the Linux close man page contains the interesting quote:
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.