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