1import datetime
2import os
3import signal
4import subprocess
5
6from .compat import ON_WINDOWS
7from .printer import Message
8
9
10class Process:
11 """
12 A simple utility wrapper around a subprocess.Popen that stores
13 a number of attributes needed by Poncho and supports forwarding process
14 lifecycle events and output to a queue.
15 """
16
17 def __init__(self, cmd, name=None, color=None, quiet=False, env=None, cwd=None):
18 self.cmd = cmd
19 self.color = color
20 self.quiet = quiet
21 self.name = name
22 self.env = os.environ.copy() if env is None else env
23 self.cwd = cwd
24
25 self._clock = datetime.datetime
26 self._child = None
27 self._child_ctor = Popen
28
29 def run(self, events=None, ignore_signals=False):
30 self._events = events
31 self._child = self._child_ctor(self.cmd, env=self.env, cwd=self.cwd)
32 self._send_message({"pid": self._child.pid}, type="start")
33
34 # Don't pay attention to SIGINT/SIGTERM. The process itself is
35 # considered unkillable, and will only exit when its child (the shell
36 # running the Procfile process) exits.
37 if ignore_signals:
38 signal.signal(signal.SIGINT, signal.SIG_IGN)
39 signal.signal(signal.SIGTERM, signal.SIG_IGN)
40
41 for line in iter(self._child.stdout.readline, b""):
42 if not self.quiet:
43 self._send_message(line)
44 self._child.stdout.close()
45 self._child.wait()
46
47 self._send_message({"returncode": self._child.returncode}, type="stop")
48
49 def _send_message(self, data, type="line"):
50 if self._events is not None:
51 self._events.put(
52 Message(
53 type=type,
54 data=data,
55 time=self._clock.now(),
56 name=self.name,
57 color=self.color,
58 )
59 )
60
61
62class Popen(subprocess.Popen):
63 def __init__(self, cmd, **kwargs):
64 start_new_session = kwargs.pop("start_new_session", True)
65 options = {
66 "stdout": subprocess.PIPE,
67 "stderr": subprocess.STDOUT,
68 "shell": True,
69 "close_fds": not ON_WINDOWS,
70 }
71 options.update(**kwargs)
72
73 if ON_WINDOWS:
74 # MSDN reference:
75 # http://msdn.microsoft.com/en-us/library/windows/desktop/ms684863%28v=vs.85%29.aspx
76 create_no_window = 0x08000000
77 options.update(creationflags=create_no_window)
78 elif start_new_session:
79 options.update(start_new_session=True)
80
81 super().__init__(cmd, **options)