How can I start a node.js subprocess in python on linux with a readable non-blocking output pipe

2

I am trying to create a subprocess of node.js in python to execute javascript code and read the output.

This code works in Windows 10 but on Ubuntu Linux it gives an error. When node starts it gives a prompt of > on stdout and this code attempts to read that prompt to verify that node has started and that the pipe is readable but it doesn't work correctly on Linux.

import os
import subprocess
import time
import re
import json
import logging

logger = logging.getLogger(__name__)

if "nt" == os.name:
    import msvcrt
    from ctypes import windll, byref, wintypes, GetLastError, WinError
    from ctypes.wintypes import HANDLE, DWORD, BOOL, LPDWORD
    PIPE_NOWAIT = wintypes.DWORD(0x00000001)
    ERROR_NO_DATA = 232
else:
    import fcntl

class NodeEngine:

    def __init__(self):
        self.stdout_fd = None
        self.p_stdout_fd = None
        self.stdout = None
        self.proc = None
        self.__init_pipes()

    def __init_pipes(self):
        self.stdout_fd, self.p_stdout_fd = os.pipe()
        self.pipe_no_wait(self.stdout_fd)
        self.stdout = os.fdopen(self.p_stdout_fd,"w")



    def pipe_no_wait(self, pipefd):

        if "nt" == os.name:
            SetNamedPipeHandleState = windll.kernel32.SetNamedPipeHandleState
            SetNamedPipeHandleState.argtypes = [HANDLE, LPDWORD, LPDWORD, LPDWORD]
            SetNamedPipeHandleState.restype = BOOL
            h = msvcrt.get_osfhandle(pipefd)
            res = windll.kernel32.SetNamedPipeHandleState(h, byref(PIPE_NOWAIT), None, None)
            if res == 0:
                print(WinError())
                return False
            return True
        else:
            fl = fcntl.fcntl(pipefd, fcntl.F_GETFL)
            fcntl.fcntl(pipefd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

    def start(self):
        self.proc = subprocess.Popen(
            ["node","-i"],
            stdin=subprocess.PIPE,
            stdout=self.stdout,
            # stderr=subprocess.PIPE,
            shell=True
        )
        bytes_read = 0
        ret = ""
        timeout = time.time() + 10

        while 0 == bytes_read or 1024 == bytes_read:
            try:
                data = os.read(self.stdout_fd,1024)
                ret = ret + data.decode("utf-8")
                bytes_read = len(data)
            except Exception as e:
                if time.time() >= timeout:
                    raise e
                self.stdout.flush()
                bytes_read = 0



    def close(self):
        try:
            self.proc.stdin.close()
        except:
            pass

        self.proc.terminate()
        self.proc.wait(timeout=0.2)

if __name__ == '__main__':
    engine = NodeEngine()
    engine.start()

It seems the flush command isn't working. Is there a way to get this to work properly on Linux and Windows?

python
node.js
linux
subprocess
nonblocking
asked on Stack Overflow Nov 10, 2018 by Ralph Ritoch

1 Answer

0

I found the bug and it was very subtle. When using Popen with shell=True the first argument shouldn't be a list.

I changed

    self.proc = subprocess.Popen(
        ["node","-i"],
        stdin=subprocess.PIPE,
        stdout=self.stdout,
        # stderr=subprocess.PIPE,
        shell=True
    )

to

    self.proc = subprocess.Popen(
        "node -i",
        stdin=subprocess.PIPE,
        stdout=self.stdout,
        # stderr=subprocess.PIPE,
        shell=True
    )

and now it works on both Linux and Windows.

Note: I'll leave this question open for better solutions

answered on Stack Overflow Nov 10, 2018 by Ralph Ritoch

User contributions licensed under CC BY-SA 3.0