Home > Net >  File descriptor / socket handling differences when migrating from Python 2 to 3
File descriptor / socket handling differences when migrating from Python 2 to 3

Time:06-09

I'm migrating an application from Python 2 to 3. The app involves in a Python script that orchestrates a few instances of a C app. The python script opens a socket for each app and then passes the corresponding file descriptor to the C program. This works with the original version in Python 2.7 but breaks with Python 3.6 or 3.9.

I was able to find one change: the file descriptors apart from stdin, stdout and stderr are not inherited by the child processes by default (more info here)

What I do is the following:

import socket                                
import os                                    
import subprocess                            

sock = socket.socket()                       
sock.bind(('10.80.100.32',0))                
sock                                         
# Out[6]: <socket.socket fd=11, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('10.80.100.32', 36737)>

env  = os.environ.copy()                     
env["LD_LIBRARY_PATH"] = env["LD_LIBRARY_PATH"]   ":%s" % os.getcwd()                             
p = subprocess.Popen(["./app", "--sockfd", "11"], close_fds = False, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)                                               
p.pid                                       
# Out[10]: 393727

Then I check the corresponding process: either it exists and there is a server waiting for a connection in case of Python 2 or the process is dead in case of Python 3.

I tried to set the file descriptor to be inheritable:

os.get_inheritable(11)                      
# Out[15]: False
os.set_inheritable(11, True)

However that did not help, the app still crashes.

I also tried to explicitly pass pass_fds = [11] to Popen, that also did not help.

If I run the app and let it create the socket on its own then it works fine including when it is started from the Python script. So at this point I'm fairly certain that the problem has to do with some changes from Python 2 to Python 3.

Are there any other changes that could be having an impact on the observed behavior? What else could I try to make it work?

CodePudding user response:

The problem here appears to be that you're never calling listen() on your socket. If I modify your code to both (a) set the inheritable flag, and (b) call listen, so that it looks like this:

import socket
import os
import subprocess

sock = socket.socket()
sock.set_inheritable(True)
sock.bind(("0.0.0.0", 0))
sock.listen(5)
print("listening on", sock.getsockname()[1])

env = os.environ.copy()
p = subprocess.Popen(
    ["./socklisten", "{}".format(sock.fileno())],
    close_fds=False,
    env=env,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
)
p.wait()

Where socklisten is the following simple program that prints out a string on a given file descriptor:

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/wait.h>

int main(int argc, char **argv) {
    int sock;

    if (argc <= 1) {
        fprintf(stderr, "missing socket\n");
        exit(1);
    }

    sock = atoi(argv[1]);
    if (sock == 0) {
        fprintf(stderr, "invalid socket number\n");
        exit(1);
    }

    while (1) {
        int client;
        char msg[100];
        struct sockaddr_in clientaddr;
        socklen_t clientlen = sizeof(struct sockaddr_in);
        if (-1 == (client = accept(sock, (struct sockaddr *)&clientaddr, &clientlen))) {
            perror("accept");
            exit(1);
        }

        sprintf(msg, "This is a test.\r\n");
        write(client, msg, strlen(msg) 1);
        close(client);
    }

}

It all works as expected. If I run the Python code, it opens the socket and waits for the child process to exit:

$ python server.py
listening on 51163

If I connect to that port, I see the expected response:

$ nc localhost 51163
This is a test.

If I remove either the call to sock.listen or the call to sock.set_inheritable, the code fails as you describe in your question.

  • Related