5 from gi.repository import GObject
7 from .common import ST_ACTIVE, ST_TRANSITION, OnoffDevice
8 from .gobject import spawn_child
10 logger = logging.getLogger("onoff.process")
12 class OnoffProcess(OnoffDevice):
13 """A device that in activated by starting a process and deactivated by
16 @type pid: int or None
17 @ivar pid: is either None if there is no process or the pid of the
19 @ivar starting: is either None or a GObject event source id of the
20 callback sigalling the end of the activation transition.
21 @ivar watch: is either None or a GObject event source id of the
22 callback waiting for the termination of the spawned process.
24 @ivar killed: indicates whether the termination signal has been sent
25 to the spawned process.
27 def __init__(self, command, start_wait=0, termsig=signal.SIGTERM):
30 @param command: an argument vector to be executed. The first element
31 is used as executable and looked up in $PATH.
32 @param start_wait: duration of the transition period from inactive to
34 @param termsig: termination signal to be sent to the process to
35 deactivate it. The process must exit in response to this
38 OnoffDevice.__init__(self)
39 self.command = command
40 self.start_wait = start_wait
41 self.termsig = termsig
42 self.desired_state = 0 # bit mask of just ST_ACTIVE
44 self.starting = None # timeout event during start
45 self.watch = None # watch event
50 if self.starting or self.killed:
51 return self.desired_state | ST_TRANSITION
52 return self.desired_state
54 def start_process(self):
55 assert self.pid is None
56 assert self.starting is None
57 logger.info("starting command %s", " ".join(self.command))
58 self.pid, self.watch = spawn_child(self.command, self.process_died)
59 logger.debug("started as pid %d", self.pid)
60 self.starting = GObject.timeout_add(int(1000 * self.start_wait),
62 self.changestate(self.state)
64 def cancel_start_wait(self):
65 if self.starting is None:
67 logger.debug("cancelling start notification")
68 ret = GObject.source_remove(self.starting)
72 def process_died(self, pid, condition):
73 assert self.pid == pid
74 assert self.watch is not None
78 logger.info("process %d died", pid)
79 self.cancel_start_wait()
80 if self.desired_state == ST_ACTIVE:
83 self.changestate(self.state)
85 def process_started(self):
86 assert self.desired_state == ST_ACTIVE
87 assert self.starting is not None
89 logger.debug("process started")
90 self.changestate(self.state)
92 def stop_process(self):
93 assert self.pid is not None
94 assert self.watch is not None
95 self.cancel_start_wait()
96 logger.info("killing process %d", self.pid)
97 os.kill(self.pid, self.termsig)
99 self.changestate(self.state)
102 if self.desired_state != ST_ACTIVE:
103 self.desired_state = ST_ACTIVE
107 logger.debug("already activated. nothing to do")
109 def deactivate(self):
110 if self.desired_state != 0:
111 self.desired_state = 0
112 if self.pid is not None and not self.killed:
115 logger.debug("already deactivated. nothing to do.")