deduplicate spawn_async invocations
[~helmut/onoff.git] / onoff / process.py
1 import logging
2 import os
3 import signal
4
5 from gi.repository import GObject
6
7 from .common import ST_ACTIVE, ST_TRANSITION, OnoffDevice
8 from .gobject import spawn_child
9
10 logger = logging.getLogger("onoff.process")
11
12 class OnoffProcess(OnoffDevice):
13     def __init__(self, command, start_wait=0, termsig=signal.SIGTERM):
14         OnoffDevice.__init__(self)
15         self.command = command
16         self.start_wait = start_wait
17         self.termsig = termsig
18         self.desired_state = 0 # bit mask of just ST_ACTIVE
19         self.pid = None
20         self.starting = None # timeout event during start
21         self.watch = None # watch event
22         self.killed = False
23
24     @property
25     def state(self):
26         if self.starting or self.killed:
27             return self.desired_state | ST_TRANSITION
28         return self.desired_state
29
30     def start_process(self):
31         assert self.pid is None
32         assert self.starting is None
33         logger.info("starting command %s", " ".join(self.command))
34         self.pid, self.watch = spawn_child(self.command, self.process_died)
35         logger.debug("started as pid %d", self.pid)
36         self.starting = GObject.timeout_add(1000 * self.start_wait,
37                                             self.process_started)
38         self.changestate(self.state)
39
40     def cancel_start_wait(self):
41         if self.starting is None:
42             return
43         logger.debug("cancelling start notification")
44         ret = GObject.source_remove(self.starting)
45         assert ret
46         self.starting = None
47
48     def process_died(self, pid, condition):
49         assert self.pid == pid
50         assert self.watch is not None
51         self.watch = None
52         self.pid = None
53         self.killed = False
54         logger.info("process %d died", pid)
55         self.cancel_start_wait()
56         if self.desired_state == ST_ACTIVE:
57             self.start_process()
58         else:
59             self.changestate(self.state)
60
61     def process_started(self):
62         assert self.desired_state == ST_ACTIVE
63         assert self.starting is not None
64         self.starting = None
65         logger.debug("process started")
66         self.changestate(self.state)
67
68     def stop_process(self):
69         assert self.pid is not None
70         assert self.watch is not None
71         self.cancel_start_wait()
72         logger.info("killing process %d", self.pid)
73         os.kill(self.pid, self.termsig)
74         self.killed = True
75         self.changestate(self.state)
76
77     def activate(self):
78         if self.desired_state != ST_ACTIVE:
79             self.desired_state = ST_ACTIVE
80             if self.pid is None:
81                 self.start_process()
82             else:
83                 logger.debug("already activated. nothing to do")
84
85     def deactivate(self):
86         if self.desired_state != 0:
87             self.desired_state = 0
88             if self.pid is not None and not self.killed:
89                 self.stop_process()
90             else:
91                 logger.debug("already deactivated. nothing to do.")
92
93     def close(self):
94         self.deactivate()