Unblock recvfrom when socket is closed

前端 未结 5 1269
自闭症患者
自闭症患者 2020-12-07 01:44

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

相关标签:
5条回答
  • 2020-12-07 02:04

    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:

    1. A thread is about to call recvfrom, but it gets pre-empted by other things the system needs to do.
    2. Later, the thread calls close.
    3. A thread started by the system's I/O library calls socket and gets the same decsriptor as the one you closed.
    4. Finally, the thread calls 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.

    0 讨论(0)
  • 2020-12-07 02:20

    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.

    0 讨论(0)
  • 2020-12-07 02:22

    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.

    0 讨论(0)
  • 2020-12-07 02:24

    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()
    
    0 讨论(0)
  • 2020-12-07 02:28

    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.

    0 讨论(0)
提交回复
热议问题