From cf68d3448dd721c27a844f1e457134ec1c327be7 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Tue, 24 Sep 2024 11:18:19 -0400 Subject: [PATCH 01/13] Wrap all pipe.close() in try/except --- devtools/pipe.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/devtools/pipe.py b/devtools/pipe.py index 693a07a8..30c41e98 100644 --- a/devtools/pipe.py +++ b/devtools/pipe.py @@ -75,13 +75,33 @@ def read_jsons(self, blocking=True, debug=None): return jsons def close(self): + os.set_blocking(self.write_from_chromium, False) + os.set_blocking(self.read_from_chromium, False) + os.set_blocking(self.write_to_chromium, False) + os.set_blocking(self.read_to_chromium, False) if platform.system() == "Windows": try: - os.set_blocking(self.write_from_chromium, False) os.write(self.write_from_chromium, b'{bye}\n') - except Exception: - pass - os.close(self.write_to_chromium) - os.close(self.read_from_chromium) - os.close(self.write_from_chromium) - os.close(self.read_to_chromium) + except BaseException as e: + if self.debug: + print(f"Caught error in self-wrte bye: {str(e)}", file=sys.stderr) + try: + os.close(self.write_to_chromium) + except BaseException as e: + if self.debug: + print(f"Caught error in self-wrte bye: {str(e)}", file=sys.stderr) + try: + os.close(self.read_from_chromium) + except BaseException as e: + if self.debug: + print(f"Caught error in self-wrte bye: {str(e)}", file=sys.stderr) + try: + os.close(self.write_from_chromium) + except BaseException as e: + if self.debug: + print(f"Caught error in self-wrte bye: {str(e)}", file=sys.stderr) + try: + os.close(self.read_to_chromium) + except BaseException as e: + if self.debug: + print(f"Caught error in self-wrte bye: {str(e)}", file=sys.stderr) From adf35c8b2aca878a1ce7c8079dc1256e9afe7c50 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Tue, 24 Sep 2024 12:54:57 -0400 Subject: [PATCH 02/13] Reduce code dup in Pipe() w/ _foo()s --- devtools/pipe.py | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/devtools/pipe.py b/devtools/pipe.py index 30c41e98..d8f5a193 100644 --- a/devtools/pipe.py +++ b/devtools/pipe.py @@ -74,34 +74,32 @@ def read_jsons(self, blocking=True, debug=None): print(f"read_jsons: {jsons[-1]}", file=sys.stderr) return jsons + def _unblock_fd(self, fd): + try: + os.set_blocking(fd, False) + except BaseException as e: + if self.debug: + print(f"Error unblocking {str(fd)}: {str(e)}", file=sys.stderr) + + def _close_fd(self, fd): + try: + os.close(fd) + except BaseException as e: + if self.debug: + print(f"Error closing {str(fd)}: {str(e)}", file=sys.stderr) + def close(self): - os.set_blocking(self.write_from_chromium, False) - os.set_blocking(self.read_from_chromium, False) - os.set_blocking(self.write_to_chromium, False) - os.set_blocking(self.read_to_chromium, False) + self._unblock_fd(self.write_from_chromium) + self._unblock_fd(self.read_from_chromium) + self._unblock_fd(self.write_to_chromium) + self._unblock_fd(self.read_to_chromium) if platform.system() == "Windows": try: os.write(self.write_from_chromium, b'{bye}\n') except BaseException as e: if self.debug: print(f"Caught error in self-wrte bye: {str(e)}", file=sys.stderr) - try: - os.close(self.write_to_chromium) - except BaseException as e: - if self.debug: - print(f"Caught error in self-wrte bye: {str(e)}", file=sys.stderr) - try: - os.close(self.read_from_chromium) - except BaseException as e: - if self.debug: - print(f"Caught error in self-wrte bye: {str(e)}", file=sys.stderr) - try: - os.close(self.write_from_chromium) - except BaseException as e: - if self.debug: - print(f"Caught error in self-wrte bye: {str(e)}", file=sys.stderr) - try: - os.close(self.read_to_chromium) - except BaseException as e: - if self.debug: - print(f"Caught error in self-wrte bye: {str(e)}", file=sys.stderr) + self._close_fd(self.write_to_chromium) + self._close_fd(self.read_from_chromium) + self._close_fd(self.write_from_chromium) + self._close_fd(self.read_to_chromium) From bd686b7adbf630b63791b1901f67166fdbab3ef5 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Tue, 24 Sep 2024 12:58:21 -0400 Subject: [PATCH 03/13] Factor close() into _foos() + improve naming --- devtools/browser.py | 163 +++++++++++++++++++++++++------------------- 1 file changed, 91 insertions(+), 72 deletions(-) diff --git a/devtools/browser.py b/devtools/browser.py index f85cbc32..9777d03c 100644 --- a/devtools/browser.py +++ b/devtools/browser.py @@ -191,14 +191,12 @@ async def _open_async(self): await self.populate_targets() self.future_self.set_result(self) - # Closers: close() calls sync or async, both call finish_close - - def finish_close(self): - + # TODO create tempdir warning + def _clean_temp(self): try: self.temp_dir.cleanup() except Exception as e: - print(str(e)) + warnings.warn(str(e)) # windows doesn't like python's default cleanup if platform.system() == "Windows": @@ -223,95 +221,116 @@ def remove_readonly(func, path, excinfo): f"The temporary directory could not be deleted, execution will continue. {type(e)}: {e}" ) - def sync_process_close(self): - self.send_command("Browser.close") + async def _is_closed_async(self, wait=0): + waiter = self.subprocess.wait() try: - self.subprocess.wait(3) - self.pipe.close() + await asyncio.wait_for(waiter, wait) + return True + except: # noqa + return False + + def _is_closed(self, wait=0): + if not wait: + if not self.subprocess.poll(): + return False + else: + return True + else: + try: + self.subprocess.wait(wait) + return True + except: # noqa + return False + + # _sync_close and _async_close are basically the same thing + + def _sync_close(self): + if self._is_closed(): + if self.debug: print("Browser was already closed.", file=sys.stderr) + return + # check if no sessions or targets + self.send_command("Browser.close") + if self._is_closed(wait = .2): + if self.debug: print("Browser.close method closed browser", file=sys.stderr) return - except Exception: - pass self.pipe.close() + if self._is_closed(): + if self.debug: print("pipe.close() method closed browser", file=sys.stderr) + return + + # Start a kill if platform.system() == "Windows": - if self.subprocess.poll() is None: + if not self._is_closed(): subprocess.call( ["taskkill", "/F", "/T", "/PID", str(self.subprocess.pid)] ) # TODO probably needs to be silenced - try: - self.subprocess.wait(2) + if self._is_closed(): return - except Exception: - pass - else: - return - self.subprocess.terminate() - try: - self.subprocess.wait(2) - return - except Exception: - pass - self.subprocess.kill() + else: + raise RuntimeError("Couldn't kill browser subprocess") + else: + self.subprocess.terminate() + if self._is_closed(): + if self.debug: print("terminate() closed the browser", file=sys.stderr) + self.subprocess.kill() + if self._is_closed(): + if self.debug: print("kill() closed the browser", file=sys.stderr) + return - async def async_process_close(self): - await self.send_command("Browser.close") - waiter = self.subprocess.wait() - try: - await asyncio.wait_for(waiter, 3) - self.finish_close() - self.pipe.close() + + async def _async_close(self): + if await self._is_closed_async(): + if self.debug: print("Browser was already closed.", file=sys.stderr) + return + # TODO: Above doesn't work with closed tabs for some reason + # TODO: check if tabs? + # TODO: track tabs? + await asyncio.wait([self.send_command("Browser.close")], timeout=2) + if await self._is_closed_async(wait = .2): + if self.debug: print("Browser.close method closed browser", file=sys.stderr) return - except Exception: - pass self.pipe.close() + if await self._is_closed_async(): + if self.debug: print("pipe.close() method closed browser", file=sys.stderr) + return + + # Start a kill if platform.system() == "Windows": - waiter = self.subprocess.wait() - try: - await asyncio.wait_for(waiter, 1) - self.finish_close() + if not await self._is_closed_async(): + subprocess.call( + ["taskkill", "/F", "/T", "/PID", str(self.subprocess.pid)] + ) # TODO probably needs to be silenced + if await self._is_closed_async(): + return + else: + raise RuntimeError("Couldn't kill browser subprocess") + else: + self.subprocess.terminate() + if await self._is_closed_async(): + if self.debug: print("terminate() closed the browser", file=sys.stderr) return - except Exception: - pass - # need try - subprocess.call( - ["taskkill", "/F", "/T", "/PID", str(self.subprocess.pid)] - ) # TODO probably needs to be silenced - waiter = self.subprocess.wait() - try: - await asyncio.wait_for(waiter, 2) - self.finish_close() + + self.subprocess.kill() + if await self._is_closed_async(): + if self.debug: print("kill() closed the browser", file=sys.stderr) return - except Exception: - pass - self.subprocess.terminate() - waiter = self.subprocess.wait() - try: - await asyncio.wait_for(waiter, 2) - self.finish_close() - return - except Exception: - pass - self.subprocess.kill() + return def close(self): if self.loop: - if not len(self.tabs): + async def close_task(): + await self._async_close() self.pipe.close() - self.finish_close() - future = self.loop.create_future() - future.set_result(None) - return future - else: - return asyncio.create_task(self.async_process_close()) - + self._clean_temp() # can we make async + return asyncio.create_task(close_task()) else: - if self.subprocess.poll() is None: - self.sync_process_close() - # I'd say race condition but the user needs to take care of it - self.finish_close() + self._sync_close() + self.pipe.close() + self._clean_temp() if self.debug: print(f"Tempfile still exists?: {bool(os.path.isfile(str(self.temp_dir.name)))}") - # These are effectively stubs to allow use with with + def __enter__(self): return self From 0e07ce56297b91a8a0d5c768753beee6856122e9 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Tue, 24 Sep 2024 13:02:14 -0400 Subject: [PATCH 04/13] Ignore kaledio in ruff --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 5123b0bd..d355b241 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,3 +18,5 @@ dev = [ [tool.ruff.lint] ignore = ["E701"] +[tool.ruff.lint.per-file-ignores] +"kaleido/**" = ["F", "E", "D"] From cd53d5b0ba0af153aae4e96eaae96cbd2a5a3b5b Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Tue, 24 Sep 2024 13:02:50 -0400 Subject: [PATCH 05/13] Revert commit, unnecessary with github --- pyproject.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d355b241..5123b0bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,5 +18,3 @@ dev = [ [tool.ruff.lint] ignore = ["E701"] -[tool.ruff.lint.per-file-ignores] -"kaleido/**" = ["F", "E", "D"] From 40ce54e4ff1c47409f25938f57d2f8af79a16dc8 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Tue, 24 Sep 2024 21:30:17 -0400 Subject: [PATCH 06/13] Add more timeout to close/kill commands --- devtools/browser.py | 11 ++++++----- devtools/pipe.py | 15 +++++++++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/devtools/browser.py b/devtools/browser.py index 9777d03c..7d0213cc 100644 --- a/devtools/browser.py +++ b/devtools/browser.py @@ -250,7 +250,7 @@ def _sync_close(self): return # check if no sessions or targets self.send_command("Browser.close") - if self._is_closed(wait = .2): + if self._is_closed(wait = .5): if self.debug: print("Browser.close method closed browser", file=sys.stderr) return self.pipe.close() @@ -264,7 +264,7 @@ def _sync_close(self): subprocess.call( ["taskkill", "/F", "/T", "/PID", str(self.subprocess.pid)] ) # TODO probably needs to be silenced - if self._is_closed(): + if self._is_closed(wait = 2): return else: raise RuntimeError("Couldn't kill browser subprocess") @@ -272,6 +272,7 @@ def _sync_close(self): self.subprocess.terminate() if self._is_closed(): if self.debug: print("terminate() closed the browser", file=sys.stderr) + return self.subprocess.kill() if self._is_closed(): @@ -287,7 +288,7 @@ async def _async_close(self): # TODO: check if tabs? # TODO: track tabs? await asyncio.wait([self.send_command("Browser.close")], timeout=2) - if await self._is_closed_async(wait = .2): + if await self._is_closed_async(wait = .5): if self.debug: print("Browser.close method closed browser", file=sys.stderr) return self.pipe.close() @@ -301,7 +302,7 @@ async def _async_close(self): subprocess.call( ["taskkill", "/F", "/T", "/PID", str(self.subprocess.pid)] ) # TODO probably needs to be silenced - if await self._is_closed_async(): + if await self._is_closed_async(wait = 2): return else: raise RuntimeError("Couldn't kill browser subprocess") @@ -314,9 +315,9 @@ async def _async_close(self): self.subprocess.kill() if await self._is_closed_async(): if self.debug: print("kill() closed the browser", file=sys.stderr) - return return + def close(self): if self.loop: async def close_task(): diff --git a/devtools/pipe.py b/devtools/pipe.py index d8f5a193..90b646c6 100644 --- a/devtools/pipe.py +++ b/devtools/pipe.py @@ -87,18 +87,21 @@ def _close_fd(self, fd): except BaseException as e: if self.debug: print(f"Error closing {str(fd)}: {str(e)}", file=sys.stderr) + def _fake_bye(self): + self._unblock_fd(self.write_from_chromium) + try: + os.write(self.write_from_chromium, b'{bye}\n') + except BaseException as e: + if self.debug: + print(f"Caught error in self-wrte bye: {str(e)}", file=sys.stderr) def close(self): + if platform.system() == "Windows": + self._fake_bye() self._unblock_fd(self.write_from_chromium) self._unblock_fd(self.read_from_chromium) self._unblock_fd(self.write_to_chromium) self._unblock_fd(self.read_to_chromium) - if platform.system() == "Windows": - try: - os.write(self.write_from_chromium, b'{bye}\n') - except BaseException as e: - if self.debug: - print(f"Caught error in self-wrte bye: {str(e)}", file=sys.stderr) self._close_fd(self.write_to_chromium) self._close_fd(self.read_from_chromium) self._close_fd(self.write_from_chromium) From 5d2ca1f4a2427d21900e08d6b4421e2a8f677c79 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Tue, 24 Sep 2024 21:42:01 -0400 Subject: [PATCH 07/13] Properly silence task kill --- devtools/browser.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/devtools/browser.py b/devtools/browser.py index 7d0213cc..4daf4181 100644 --- a/devtools/browser.py +++ b/devtools/browser.py @@ -263,7 +263,9 @@ def _sync_close(self): if not self._is_closed(): subprocess.call( ["taskkill", "/F", "/T", "/PID", str(self.subprocess.pid)] - ) # TODO probably needs to be silenced + stderr=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + ) if self._is_closed(wait = 2): return else: @@ -300,8 +302,10 @@ async def _async_close(self): if platform.system() == "Windows": if not await self._is_closed_async(): subprocess.call( - ["taskkill", "/F", "/T", "/PID", str(self.subprocess.pid)] - ) # TODO probably needs to be silenced + ["taskkill", "/F", "/T", "/PID", str(self.subprocess.pid)], + stderr=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + ) if await self._is_closed_async(wait = 2): return else: From eb1d0a96c75d184062deffdb2c29ade783a49a5f Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 25 Sep 2024 11:18:28 -0400 Subject: [PATCH 08/13] Adjust timeout times in close() --- devtools/browser.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/devtools/browser.py b/devtools/browser.py index 4daf4181..070f4a88 100644 --- a/devtools/browser.py +++ b/devtools/browser.py @@ -250,12 +250,12 @@ def _sync_close(self): return # check if no sessions or targets self.send_command("Browser.close") - if self._is_closed(wait = .5): + if self._is_closed(): if self.debug: print("Browser.close method closed browser", file=sys.stderr) return self.pipe.close() - if self._is_closed(): - if self.debug: print("pipe.close() method closed browser", file=sys.stderr) + if self._is_closed(wait = 1): + if self.debug: print("pipe.close() (or slow Browser.close) method closed browser", file=sys.stderr) return # Start a kill @@ -265,7 +265,7 @@ def _sync_close(self): ["taskkill", "/F", "/T", "/PID", str(self.subprocess.pid)] stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL, - ) + ) if self._is_closed(wait = 2): return else: @@ -289,12 +289,12 @@ async def _async_close(self): # TODO: Above doesn't work with closed tabs for some reason # TODO: check if tabs? # TODO: track tabs? - await asyncio.wait([self.send_command("Browser.close")], timeout=2) - if await self._is_closed_async(wait = .5): + await asyncio.wait([self.send_command("Browser.close")], timeout=1) + if await self._is_closed_async(): if self.debug: print("Browser.close method closed browser", file=sys.stderr) return self.pipe.close() - if await self._is_closed_async(): + if await self._is_closed_async(wait=1): if self.debug: print("pipe.close() method closed browser", file=sys.stderr) return From 925c92953dd22e466d1e9351c78627696bf2f318 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 25 Sep 2024 16:33:20 -0400 Subject: [PATCH 09/13] Fix typo --- devtools/browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/browser.py b/devtools/browser.py index 070f4a88..52961825 100644 --- a/devtools/browser.py +++ b/devtools/browser.py @@ -262,7 +262,7 @@ def _sync_close(self): if platform.system() == "Windows": if not self._is_closed(): subprocess.call( - ["taskkill", "/F", "/T", "/PID", str(self.subprocess.pid)] + ["taskkill", "/F", "/T", "/PID", str(self.subprocess.pid)], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL, ) From fb0f677d480e7d034fc091184e62f5eea1ad1d94 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 25 Sep 2024 16:44:33 -0400 Subject: [PATCH 10/13] Add close_fds to win_only to avoid error --- devtools/chrome_wrapper.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/devtools/chrome_wrapper.py b/devtools/chrome_wrapper.py index 362694c3..c112439a 100644 --- a/devtools/chrome_wrapper.py +++ b/devtools/chrome_wrapper.py @@ -31,7 +31,7 @@ elif system == "Linux": default_path = "/usr/bin/google-chrome-stable" else: # assume mac, or system == "Darwin" - default_path = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + default_path = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" def open_browser(to_chromium, from_chromium, stderr=None, env=None, loop=None, loop_hack=False): path = env.get("BROWSER_PATH", default_path) @@ -62,12 +62,11 @@ def open_browser(to_chromium, from_chromium, stderr=None, env=None, loop=None, l f"--remote-debugging-io-pipes={str(to_chromium_handle)},{str(from_chromium_handle)}" ] if platform.system() == "Windows": - win_only = {"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP} + win_only = {"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP, "close_fds":False} if not loop: return subprocess.Popen( cli, stderr=stderr, - close_fds=False, # TODO sh/could be true? pass_fds=(to_chromium, from_chromium) if system != "Windows" else None, **win_only, ) @@ -76,7 +75,6 @@ def run(): return subprocess.Popen( cli, stderr=stderr, - close_fds=False, # TODO sh/could be true? pass_fds=(to_chromium, from_chromium) if system != "Windows" else None, **win_only, ) @@ -86,7 +84,6 @@ def run(): cli[0], *cli[1:], stderr=stderr, - close_fds=False, # TODO: sh/could be true? pass_fds=(to_chromium, from_chromium) if system != "Windows" else None, **win_only) From 41e85e4348d4ff45f9a5f9b40effe625a02d18f2 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 25 Sep 2024 16:47:20 -0400 Subject: [PATCH 11/13] Add check for closed pipe during read --- devtools/pipe.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/devtools/pipe.py b/devtools/pipe.py index 90b646c6..7303a8b5 100644 --- a/devtools/pipe.py +++ b/devtools/pipe.py @@ -47,7 +47,10 @@ def read_jsons(self, blocking=True, debug=None): if debug: print(f"read_jsons ({'blocking' if blocking else 'not blocking'}):", file=sys.stderr) jsons = [] - os.set_blocking(self.read_from_chromium, blocking) + try: + os.set_blocking(self.read_from_chromium, blocking) + except OSError as e: + raise PipeClosedError() from e try: raw_buffer = os.read( self.read_from_chromium, 10000 From 79be0f0fdce7641a29b95b40503bd1477fb178ad Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 25 Sep 2024 16:48:57 -0400 Subject: [PATCH 12/13] Soften up expected error messages --- devtools/pipe.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devtools/pipe.py b/devtools/pipe.py index 7303a8b5..8ea93c8c 100644 --- a/devtools/pipe.py +++ b/devtools/pipe.py @@ -82,21 +82,21 @@ def _unblock_fd(self, fd): os.set_blocking(fd, False) except BaseException as e: if self.debug: - print(f"Error unblocking {str(fd)}: {str(e)}", file=sys.stderr) + print(f"Expected error unblocking {str(fd)}: {str(e)}", file=sys.stderr) def _close_fd(self, fd): try: os.close(fd) except BaseException as e: if self.debug: - print(f"Error closing {str(fd)}: {str(e)}", file=sys.stderr) + print(f"Expected error closing {str(fd)}: {str(e)}", file=sys.stderr) def _fake_bye(self): self._unblock_fd(self.write_from_chromium) try: os.write(self.write_from_chromium, b'{bye}\n') except BaseException as e: if self.debug: - print(f"Caught error in self-wrte bye: {str(e)}", file=sys.stderr) + print(f"Caught expected error in self-wrte bye: {str(e)}", file=sys.stderr) def close(self): if platform.system() == "Windows": From 09e66012474fb752f5e231d5b8b6b2b18c3fb02e Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 26 Sep 2024 11:11:47 -0400 Subject: [PATCH 13/13] Catch OSErrors in reads() to be PipeClosedError --- devtools/pipe.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/devtools/pipe.py b/devtools/pipe.py index 8ea93c8c..9384b1a2 100644 --- a/devtools/pipe.py +++ b/devtools/pipe.py @@ -67,6 +67,14 @@ def read_jsons(self, blocking=True, debug=None): if debug: print("read_jsons: BlockingIOError caught.", file=sys.stderr) return jsons + except OSError as e: + if debug: + print(f"caught OSError in read() {str(e)}", file=sys.stderr) + if not raw_buffer: + raise PipeClosedError() + # TODO this could be hard to test as it is a real OS corner case + # but possibly raw_buffer is partial + # and we don't check for partials decoded_buffer = raw_buffer.decode("utf-8") for raw_message in decoded_buffer.split("\0"): if raw_message: @@ -90,6 +98,7 @@ def _close_fd(self, fd): except BaseException as e: if self.debug: print(f"Expected error closing {str(fd)}: {str(e)}", file=sys.stderr) + def _fake_bye(self): self._unblock_fd(self.write_from_chromium) try: