Reimplement IO loops to be more resilient against unintended client behaviour
This commit is contained in:
		@@ -1,6 +1,6 @@
 | 
			
		||||
from contextlib import suppress
 | 
			
		||||
from os import O_NONBLOCK, O_RDONLY, close
 | 
			
		||||
from os import open as osopen
 | 
			
		||||
# pylint: disable=redefined-builtin
 | 
			
		||||
from contextlib import suppress, contextmanager
 | 
			
		||||
from os import O_NONBLOCK, O_RDONLY, O_WRONLY, open, close, write, read
 | 
			
		||||
from threading import Thread
 | 
			
		||||
 | 
			
		||||
from .terminate_process_on_failure import terminate_process_on_failure
 | 
			
		||||
@@ -11,6 +11,7 @@ class PipeWriterThread(Thread):
 | 
			
		||||
    def __init__(self, pipe_path, stop_event):
 | 
			
		||||
        super().__init__(daemon=True)
 | 
			
		||||
        self._pipe_path = pipe_path
 | 
			
		||||
        self._write_fd, self._drain_fd = None, None
 | 
			
		||||
        self._stop_event = stop_event
 | 
			
		||||
        self._write_queue = Deque()
 | 
			
		||||
 | 
			
		||||
@@ -19,24 +20,21 @@ class PipeWriterThread(Thread):
 | 
			
		||||
 | 
			
		||||
    @terminate_process_on_failure
 | 
			
		||||
    def run(self):
 | 
			
		||||
        with self._open() as pipe:
 | 
			
		||||
        with self._open():
 | 
			
		||||
            while True:
 | 
			
		||||
                message = self._write_queue.pop()
 | 
			
		||||
                if message is None:
 | 
			
		||||
                    self._stop_event.set()
 | 
			
		||||
                    break
 | 
			
		||||
                try:
 | 
			
		||||
                    pipe.write(message + b'\n')
 | 
			
		||||
                    pipe.flush()
 | 
			
		||||
                except BrokenPipeError:
 | 
			
		||||
                    try:  # pipe was reopened, close() flushed the message
 | 
			
		||||
                        pipe.close()
 | 
			
		||||
                    except BrokenPipeError:  # close() discarded the message
 | 
			
		||||
                        self._write_queue.push_front(message)
 | 
			
		||||
                    pipe = self._open()
 | 
			
		||||
                write(self._write_fd, message + b'\n')
 | 
			
		||||
 | 
			
		||||
    @contextmanager
 | 
			
		||||
    def _open(self):
 | 
			
		||||
        return open(self._pipe_path, 'wb')
 | 
			
		||||
        self._drain_fd = open(self._pipe_path, O_RDONLY | O_NONBLOCK)
 | 
			
		||||
        self._write_fd = open(self._pipe_path, O_WRONLY)
 | 
			
		||||
        yield
 | 
			
		||||
        close(self._write_fd)
 | 
			
		||||
        close(self._drain_fd)
 | 
			
		||||
 | 
			
		||||
    def stop(self):
 | 
			
		||||
        while self.is_alive():
 | 
			
		||||
@@ -44,7 +42,7 @@ class PipeWriterThread(Thread):
 | 
			
		||||
        self.join()
 | 
			
		||||
 | 
			
		||||
    def _unblock(self):
 | 
			
		||||
        self._write_queue.push_front(None)
 | 
			
		||||
        with suppress(OSError):
 | 
			
		||||
            fd = osopen(self._pipe_path, O_RDONLY | O_NONBLOCK)
 | 
			
		||||
            self._write_queue.push_front(None)
 | 
			
		||||
            close(fd)
 | 
			
		||||
            while read(self._drain_fd, 65536):
 | 
			
		||||
                pass
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user