Compare commits
3 Commits
nilmrun-1.
...
nilmrun-1.
Author | SHA1 | Date | |
---|---|---|---|
477c27a4e6 | |||
bed26e099b | |||
9224566f9b |
@@ -92,6 +92,12 @@ class Process(object):
|
||||
except OSError: # pragma: no cover
|
||||
return None
|
||||
|
||||
def kill(pid, sig):
|
||||
try:
|
||||
return os.kill(pid, sig)
|
||||
except OSError: # pragma: no cover
|
||||
return
|
||||
|
||||
# Find all children
|
||||
group = getpgid(self._process.pid)
|
||||
main = psutil.Process(self._process.pid)
|
||||
@@ -100,7 +106,7 @@ class Process(object):
|
||||
# Kill with SIGTERM, if they're still in this process group
|
||||
for proc in allproc:
|
||||
if getpgid(proc.pid) == group:
|
||||
os.kill(proc.pid, signal.SIGTERM)
|
||||
kill(proc.pid, signal.SIGTERM)
|
||||
|
||||
# Wait for it to die again
|
||||
if self._join(timeout):
|
||||
@@ -109,7 +115,7 @@ class Process(object):
|
||||
# One more try with SIGKILL
|
||||
for proc in allproc:
|
||||
if getpgid(proc.pid) == group:
|
||||
os.kill(proc.pid, signal.SIGKILL)
|
||||
kill(proc.pid, signal.SIGKILL)
|
||||
|
||||
# See if it worked
|
||||
return self._join(timeout)
|
||||
@@ -178,9 +184,27 @@ class ProcessManager(object):
|
||||
"""Track and manage a collection of Process objects"""
|
||||
def __init__(self):
|
||||
self.processes = {}
|
||||
self.tmpfiles = {}
|
||||
self.tmpdir = tempfile.mkdtemp(prefix = "nilmrun-usercode-")
|
||||
atexit.register(shutil.rmtree, self.tmpdir)
|
||||
self.tmpdirs = {}
|
||||
atexit.register(self._atexit)
|
||||
|
||||
def _cleanup_tmpdir(self, pid):
|
||||
if pid in self.tmpdirs:
|
||||
try:
|
||||
shutil.rmtree(self.tmpdirs[pid])
|
||||
except OSError: # pragma: no cover
|
||||
pass
|
||||
del self.tmpdirs[pid]
|
||||
|
||||
def _atexit(self):
|
||||
# Kill remaining processes, remove their dirs
|
||||
for pid in self.processes.keys():
|
||||
try:
|
||||
self.processes[pid].terminate()
|
||||
del self.processes[pid]
|
||||
shutil.rmtree(self.tmpdirs[pid])
|
||||
del self.tmpdirs[pid]
|
||||
except Exception: # pragma: no cover
|
||||
pass
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.processes.keys())
|
||||
@@ -193,15 +217,33 @@ class ProcessManager(object):
|
||||
executed. The arguments, which must be strings, will be
|
||||
accessible in the code as sys.argv[1:]."""
|
||||
# The easiest way to do this, by far, is to just write the
|
||||
# code to a file.
|
||||
(fd, path) = tempfile.mkstemp(prefix = "nilmrun-usercode-",
|
||||
suffix = ".py", dir=self.tmpdir)
|
||||
with os.fdopen(fd, 'w') as f:
|
||||
f.write(code)
|
||||
argv = [ sys.executable, "-B", "-s", "-u", path ] + args
|
||||
pid = self.run_command(argv)
|
||||
self.tmpfiles[pid] = path
|
||||
return pid
|
||||
# code to a file. Make a directory to put it in.
|
||||
tmpdir = tempfile.mkdtemp(prefix = "nilmrun-usercode-")
|
||||
try:
|
||||
# Write the code
|
||||
codepath = os.path.join(tmpdir, "usercode.py")
|
||||
with open(codepath, "w") as f:
|
||||
f.write(code)
|
||||
# Save the args too, for debugging purposes
|
||||
with open(os.path.join(tmpdir, "args.txt"), "w") as f:
|
||||
f.write(repr(args))
|
||||
|
||||
# Run the code
|
||||
argv = [ sys.executable, "-B", "-s", "-u", codepath ] + args
|
||||
pid = self.run_command(argv)
|
||||
|
||||
# Save the temp dir
|
||||
self.tmpdirs[pid] = tmpdir
|
||||
tmpdir = None # Don't need to remove it anymore
|
||||
|
||||
return pid
|
||||
finally:
|
||||
# Clean up tempdir if we didn't finish
|
||||
if tmpdir is not None:
|
||||
try:
|
||||
shutil.rmtree(tmpdir)
|
||||
except OSError: # pragma: no cover
|
||||
pass
|
||||
|
||||
def run_command(self, argv):
|
||||
"""Execute a command line program"""
|
||||
@@ -213,12 +255,7 @@ class ProcessManager(object):
|
||||
return self.processes[pid].terminate()
|
||||
|
||||
def remove(self, pid):
|
||||
if pid in self.tmpfiles:
|
||||
try:
|
||||
os.unlink(self.tmpfiles[pid])
|
||||
except OSError: # pragma: no cover
|
||||
pass
|
||||
del self.tmpfiles[pid]
|
||||
self._cleanup_tmpdir(pid)
|
||||
del self.processes[pid]
|
||||
|
||||
def get_info(self):
|
||||
|
@@ -210,6 +210,7 @@ class Server(object):
|
||||
manager = serializer_proxy(nilmrun.processmanager.ProcessManager)()
|
||||
|
||||
# Build up the application and mount it
|
||||
self._manager = manager
|
||||
root = App()
|
||||
root.process = AppProcess(manager)
|
||||
root.run = AppRun(manager)
|
||||
|
@@ -390,3 +390,20 @@ class TestClient(object):
|
||||
# Programs that spit out invalid UTF-8 should get replacement
|
||||
# markers
|
||||
verify("echo -ne \\\\xae", u"\ufffd")
|
||||
|
||||
def test_client_11_atexit(self):
|
||||
# Leave a directory and running process behind, for the atexit
|
||||
# handler to clean up. Here we trigger the atexit manually,
|
||||
# since it's hard to trigger it as part of the test suite.
|
||||
client = HTTPClient(baseurl = testurl, post_json = True)
|
||||
code = textwrap.dedent("""
|
||||
import time
|
||||
time.sleep(10)
|
||||
""")
|
||||
client.post("run/code", { "code": code, "args": [ "hello"] })
|
||||
|
||||
# Trigger atexit function
|
||||
test_server._manager._atexit()
|
||||
|
||||
# Ensure no processes exit
|
||||
eq_(client.get("process/list"), [])
|
||||
|
Reference in New Issue
Block a user