siddhant1999
siddhant1999

Reputation: 65

Pipe unbuffered stdout from subprocess to websocket

How would you pipe the stdout from subprocess to the websocket without needing to wait for a newline character? Currently, the code below only sends the stdout on a newline.

Code attached for the script being run by the subprocess. Is the output not being flushed properly from there?

send_data.py:

import asyncio
import websockets
import subprocess
import sys
import os

async def foo(websocket, path):
        print ("socket open")
        await websocket.send("successfully connected")

        with subprocess.Popen(['sudo','python3', '-u','inline_print.py'],stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0, universal_newlines=True) as p:
                for line in p.stdout:
                    line = str(line.rstrip())
                    await websocket.send(line)
                    p.stdout.flush()
                for line in p.stderr:
                    line = str(line.rstrip())
                    await websocket.send(line)
                    p.stdout.flush()


start_server = websockets.serve(foo, "localhost", 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

inline_print.py:

from time import sleep
import sys

loading = 'LOADING...LOADING...LOADING...LOADING...LOADING...'
for i in range(50):
    print(loading[i], sep='', end=' ', flush=True)
    sleep(0.1)

if end=' ' is changed to end='\n' then the stdout from send_data.py occurs in realtime.

js client:

var ws = new WebSocket('ws://localhost:8765/');

ws.onmessage = function(event) {
  console.log(event.data);
};

I acknowledge this question is similar to these:

catching-stdout-in-realtime-from-subprocess

how-do-i-get-real-time-information-back-from-a-subprocess-popen-in-python-2-5

intercepting-stdout-of-a-subprocess-while-it-is-running

yet none of the solutions work without a newline character from the subprocess.

Upvotes: 2

Views: 1506

Answers (1)

gelonida
gelonida

Reputation: 5630

If you write

      for line in p.stdout:

then you (kind of) implicitly say, that you want to wait for a complete line

you had to use read(num_bytes) and not readline()

Below one example to illustrate:

sub.py: (example subprocess)

import sys, time
for v in range(20):
    print(".", end="")
    sys.stdout.flush()
    if v % 4 == 0:
        print()
    if v % 3 != 0:
        time.sleep(0.5)

rdunbuf.py: (example reading stddout unbuffered)

contextlib, time, subprocess

def unbuffered(proc, stream='stdout'):
    stream = getattr(proc, stream)
    with contextlib.closing(stream):
        while True:
            last = stream.read(80) # read up to 80 chars
            # stop when end of stream reached
            if not last:
                if proc.poll() is not None:
                    break
            else:
                yield last

# open subprocess without buffering and without universal_newlines=True
proc = subprocess.Popen(["./sub.py"], stdout=subprocess.PIPE, bufsize=0)

for l in unbuffered(proc):
    print(l)
print("end")

Please note as well, that your code might block if it produces a lot of error messages before producing normal output, as you try first to read all normal output and only then data from stderr.

You should read whataver data your subprocess produces as before any pipeline buffers are blocking independently whether this is stdout or stderr. You can use select.select() ( https://docs.python.org/3.8/library/select.html#select.select ) in order to decide whether you had to read from stdout, or stderr

Upvotes: 1

Related Questions