From 3bd459b909a8a5007d069c5f4a59d00c91120e48 Mon Sep 17 00:00:00 2001 From: Ryan Kelly Date: Mon, 5 Sep 2011 10:47:03 +1000 Subject: [PATCH] esky.sudo: monitor helper process to avoid deadlock if it dies --- ChangeLog.txt | 1 + esky/sudo/__init__.py | 35 ++++++++++++++++++++++++++++++++++- esky/sudo/sudo_base.py | 2 +- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 757732c..058224a 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -7,6 +7,7 @@ v0.9.5 * Clean up handling of unbuffered mode in py2exe. (The old code never actually worked; the new code works most of the time but might give you the wrong mode if running from a partial update) + * esky.sudo: avoid deadlocking if the helper process dies. v0.9.4 diff --git a/esky/sudo/__init__.py b/esky/sudo/__init__.py index 4ded796..791e6c7 100644 --- a/esky/sudo/__init__.py +++ b/esky/sudo/__init__.py @@ -32,6 +32,7 @@ from __future__ import absolute_import import sys +import time from esky.util import lazy_import @@ -48,6 +49,14 @@ def pickle(): import pickle return pickle +@lazy_import +def threading(): + try: + import threading + except ImportError: + threading = None + return threading + if sys.platform == "win32": @lazy_import @@ -114,9 +123,33 @@ def start(self): (self.proc,self.pipe) = spawn_sudo(self) if self.proc.poll() is not None: raise RuntimeError("sudo helper process terminated unexpectedly") - if self.pipe.read() != b("READY"): + # If threading is available, run a background thread to monitor + # the sudo process. If it dies, terminate things immediately. + if threading: + self._do_monitor_proc = True + monitor_thread = threading.Thread(target=self._monitor_proc) + monitor_thread.daemon = True + monitor_thread.start() + # Try to read initialisation message from the pipe. + # If this fails, the helper program must have died. + try: + msg = self.pipe.read() + except EOFError: + msg = b("") + if msg != b("READY"): self.close() raise RuntimeError("failed to spawn helper app") + if threading: + self._do_monitor_proc = False + monitor_thread.join() + + def _monitor_proc(self): + while self._do_monitor_proc: + if self.proc.poll() is not None: + self.pipe._recover() + self.pipe.close() + break + time.sleep(0) def close(self): self.pipe.write(b("CLOSE")) diff --git a/esky/sudo/sudo_base.py b/esky/sudo/sudo_base.py index dbc1718..b712f01 100644 --- a/esky/sudo/sudo_base.py +++ b/esky/sudo/sudo_base.py @@ -99,7 +99,7 @@ def check_connection(self): self.connected = True def close(self): - pass + self.connected = False def read(self): """Read the next string from the pipe.