From b6ddbffa9c46e63a3c35ad9a2aee0219b3144d0f Mon Sep 17 00:00:00 2001 From: Andrej Rode Date: Tue, 26 Nov 2019 14:58:56 +0100 Subject: [PATCH 1/2] gnuplotlib: Add synchronization with helper thread Use helper thread to read stderr and block on read instead of non-crossplatform compatible select() --- gnuplotlib.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/gnuplotlib.py b/gnuplotlib.py index 59ebfee..cb1f227 100755 --- a/gnuplotlib.py +++ b/gnuplotlib.py @@ -888,6 +888,16 @@ class gnuplotlib has a separate gnuplot process and a plot window. If multiple import numpy as np import numpysane as nps +from threading import Thread +from queue import Queue, Empty + +ON_POSIX = 'posix' in sys.builtin_module_names + +def enqueue_output(out, queue): + while True: + result = out.read(1).decode() + queue.put(result) + # setup.py assumes the version is a simple string in '' quotes __version__ = '0.31' @@ -1281,6 +1291,8 @@ def __init__(self, **plotOptions): self._logEvent("_startgnuplot() finished") + + def _startgnuplot(self): self._logEvent("_startgnuplot()") @@ -1311,7 +1323,7 @@ def _startgnuplot(self): # I need this to make fdDupSTDOUT available to the # child gnuplot. close_fds=False was default in # python2, but was changed in python3 - close_fds = False, + close_fds = ON_POSIX, # This was helpful in python3 to implicitly # encode() strings, but it broke the @@ -1329,6 +1341,11 @@ def _startgnuplot(self): #encoding = 'utf-8', ) + # Handle stderr sync crossplatform + self.stderr_queue = Queue() + self.stderr_thread = Thread(target=enqueue_output, args=(self.gnuplotProcess.stderr, self.stderr_queue)) + self.stderr_thread.daemon = True + self.stderr_thread.start() # What is the default terminal? self._printGnuplotPipe( "show terminal\n" ) errorMessage, warnings = self._checkpoint('printwarnings') @@ -1491,24 +1508,27 @@ def _checkpoint(self, flags=''): self._logEvent("Trying to read from gnuplot") - rlist,wlist,xlist = select.select([self.gnuplotProcess.stderr],[], [], - None if waitforever else 15) + # rlist,wlist,xlist = select.select([self.gnuplotProcess.stderr],[], [], + # None if waitforever else 15) - if rlist: + try: + byte = self.stderr_queue.get(timeout=None if waitforever else 15) + # if rlist: # read a byte. I'd like to read "as many bytes as are # available", but I don't know how to this in a very portable # way (I just know there will be windows users complaining if I # simply do a non-blocking read). Very little data will be # coming in anyway, so doing this a byte at a time is an # irrelevant inefficiency - byte = self.gnuplotProcess.stderr.read(1).decode() + # byte = self.gnuplotProcess.stderr.read(1).decode() fromerr += byte if byte is not None and len(byte): self._logEvent("Read byte '{}' ({}) from gnuplot child process".format(byte, hex(ord(byte)))) else: self._logEvent("read() returned no data") - else: + except Empty: + # else: self._logEvent("Gnuplot read timed out") self.checkpoint_stuck = True From ffbd841d25b27b1cfccdb48554f7d42604dfb5cc Mon Sep 17 00:00:00 2001 From: Andrej Rode Date: Tue, 26 Nov 2019 15:14:50 +0100 Subject: [PATCH 2/2] Use os.devnull instead of /dev/null for cross-platform compatiblity --- gnuplotlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnuplotlib.py b/gnuplotlib.py index cb1f227..3e500e7 100755 --- a/gnuplotlib.py +++ b/gnuplotlib.py @@ -2332,7 +2332,7 @@ def make_subplot_data_embedded_kwargs(subplot): # stdout output, then eventually the buffer fills up and gnuplot blocks. # So keep it going to /dev/null, or make sure to read the test plot from # stdout - self._printGnuplotPipe( "set output '/dev/null'\n" ) + self._printGnuplotPipe( f"set output '{os.devnull}'\n" ) self._printGnuplotPipe( "set terminal dumb\n" ) if self.processOptions.get('multiplot'):