0

I'm trying to find a way to forward stdin input from my main process to a child process, and what I've found that works is basically to open a socket on the main process and then send text via the socket to the children processes. But what I'm finding is that half of the time my socket gets refused, and I have no idea what's going on.

I've followed the instructions on this question 16130786 but to no avail, I can connect via telnet, but the software still fails.

Here is the minimally reproducable example I've made

from multiprocessing import Process, Queue
from queue import Full, Empty
from io import TextIOBase
import socket
import selectors


class SocketConsoleClient(TextIOBase):
    def __init__(self, port: int):
        self.port = port
        self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.conn.connect(('', self.port))
        self.selector = selectors.DefaultSelector()
        self.conn.setblocking(False)
        self.selector.register(self.conn, selectors.EVENT_WRITE, data='hello')

    def readline(self, size: int = ...) -> str:
        while True:
            for k, _ in self.selector.select(timeout=None):
                if k.data == 'hello':
                    try:
                        return str(self.conn.recv(1024).decode('latin1'))
                    except Exception as e:
                        # print(e)
                        continue


class SocketConsoleWriter(Process):
    def __init__(self):
        super().__init__()
        self.writes = Queue()
        self.connections = []
        self.listener = None
        self.selector = None

        self.port = 10000

    def run(self) -> None:
        while True:
            try:
                self.listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self.listener.bind(('', self.port))
                self.listener.listen()
                print('listening on', ('', self.port))
                self.listener.setblocking(False)
                break
            except Exception as _:
                self.port += 1  # if errno is 98, then port is not available.

        self.selector = selectors.DefaultSelector()
        self.selector.register(self.listener, selectors.EVENT_READ, data='test')

        while True:
            try:
                w = self.writes.get_nowait()
                if w == '$$$EXIT!!!':
                    break
                else:
                    for c in self.connections:
                        c.send(w.encode('latin1'))
            except Empty:
                pass

            try:
                d = self.selector.select(1)
                for k, _ in d:
                    if k.data == 'test':
                        conn, addr = self.listener.accept()
                        print('{} connected'.format(addr))
                        self.connections.append(conn)
            except Exception as e:
                # print(e)
                pass


class SocketConsoleServer:

    server = None

    def __init__(self):
        if SocketConsoleServer.server is None:
            SocketConsoleServer.server = SocketConsoleWriter()
            SocketConsoleServer.server.start()

    @staticmethod
    def port() -> int:
        if SocketConsoleServer.server is None:
            SocketConsoleServer.server = SocketConsoleWriter()
            SocketConsoleServer.server.start()

        return SocketConsoleServer.server.port

    @staticmethod
    def write(msg: str):
        if SocketConsoleServer.server is None:
            SocketConsoleServer.server = SocketConsoleWriter()
            SocketConsoleServer.server.start()

        SocketConsoleServer.server.writes.put(msg)


if __name__ == '__main__':
    import sys, time

    serv = SocketConsoleServer()
    time.sleep(1)

    class TestProcessSocket(Process):
        def run(self):
            sys.stdin = SocketConsoleClient(serv.port())
            time.sleep(1)
            print(input())

    client = TestProcessSocket()
    client.start()

    serv.write(input('Type something: '))
    client.join()

Why is my socket connection getting refused, I'm using ubuntu?

iggy12345
  • 1,233
  • 12
  • 31
  • Your connection is most likely refused because the socket is still in the [`TIME_WAIT`](https://networkengineering.stackexchange.com/questions/19581/what-is-the-purpose-of-time-wait-in-tcp-connection-tear-down) state, making sure the process shuts down gracefully. You can make sure you call `shutdown()` and `close()` on the socket before the process exits to close them nicely. – bnaecker May 01 '20 at 01:08
  • But I'm also curious: why use sockets? You can pipe the standard input of one process to another in many ways, most of which are simpler than dealing with TCP sockets. [This is a nice overview](https://lyceum-allotments.github.io/2017/03/python-and-pipes-part-5-subprocesses-and-pipes/) of using the `subprocess` module to do so. – bnaecker May 01 '20 at 01:13
  • I was using an IO object because I didn't know what `Popen` did exactly, I saw it once, but realized that as long as I implemented `readline()` in a class I could override the `sys.stdin` object in the child process, then the adapter I was making for a python web client could call the `input()` function without getting an EOF error. Can I call Popen on a function name isntead of a file? – iggy12345 May 01 '20 at 01:21
  • I'm trying to make a reusable webscraping library, so it would be nice to build this into my StateMachine Process class – iggy12345 May 01 '20 at 01:22
  • If by "call Popen on a function name", you mean run a specific Python function in a separate process, no you can't do that with the `subprocess` module. You would use the `multiprocessing` module and pass the function as the `target`. – bnaecker May 01 '20 at 01:36
  • Hmm, I'll try reimplementing what I've done with a `Pipe` from the `multiprocessing` library and get back to you – iggy12345 May 01 '20 at 01:57
  • Using a pipe definitely solved my problem, I'm not sure how you'll do it, but if you make it into some sort of answer, I'll accept it. – iggy12345 May 01 '20 at 23:10

0 Answers0