OnoffProcess: don't fail inopportune transitions
authorHelmut Grohne <helmut@subdivi.de>
Mon, 17 Jun 2013 07:47:36 +0000 (09:47 +0200)
committerHelmut Grohne <helmut@subdivi.de>
Mon, 17 Jun 2013 07:47:36 +0000 (09:47 +0200)
Previously a call to activate while transitioning to ST_INACTIVE would
be ignored.

onoff/process.py

index d380d29..aca7c4d 100644 (file)
@@ -14,17 +14,22 @@ class OnoffProcess(OnoffDevice):
         self.command = command
         self.start_wait = start_wait
         self.termsig = termsig
-        self.state = 0
+        self.desired_state = 0 # bit mask of just ST_ACTIVE
         self.pid = None
         self.starting = None # timeout event during start
         self.watch = None # watch event
+        self.killed = False
+
+    @property
+    def state(self):
+        if self.starting or self.killed:
+            return self.desired_state | ST_TRANSITION
+        return self.desired_state
 
     def start_process(self):
-        assert self.state == 0
         assert self.pid is None
         assert self.starting is None
         logger.info("starting command %s", " ".join(self.command))
-        self.state = ST_ACTIVE|ST_TRANSITION
         ret = GObject.spawn_async(self.command, flags=GObject.SPAWN_SEARCH_PATH | GObject.SPAWN_DO_NOT_REAP_CHILD)
         self.pid = ret[0]
         assert self.pid
@@ -34,49 +39,58 @@ class OnoffProcess(OnoffDevice):
                                             self.process_started)
         self.changestate(self.state)
 
+    def cancel_start_wait(self):
+        if self.starting is None:
+            return
+        logger.debug("cancelling start notification")
+        ret = GObject.source_remove(self.starting)
+        assert ret
+        self.starting = None
+
     def process_died(self, pid, condition):
-        assert self.state != 0
         assert self.pid == pid
         assert self.watch is not None
-        self.state = 0
         self.watch = None
         self.pid = None
+        self.killed = False
         logger.info("process %d died", pid)
-        self.changestate(self.state)
+        self.cancel_start_wait()
+        if self.desired_state == ST_ACTIVE:
+            self.start_process()
+        else:
+            self.changestate(self.state)
 
     def process_started(self):
-        assert self.state == ST_ACTIVE|ST_TRANSITION
+        assert self.desired_state == ST_ACTIVE
         assert self.starting is not None
-        self.state = ST_ACTIVE
         self.starting = None
         logger.debug("process started")
         self.changestate(self.state)
 
     def stop_process(self):
-        assert self.state & ST_ACTIVE
         assert self.pid is not None
-        if self.state & ST_TRANSITION:
-            logger.debug("cancelling start notification")
-            assert self.starting is not None
-            ret = GObject.source_remove(self.starting)
-            assert ret
-            self.starting = None
-        self.state = ST_TRANSITION
+        assert self.watch is not None
+        self.cancel_start_wait()
         logger.info("killing process %d", self.pid)
         os.kill(self.pid, self.termsig)
+        self.killed = True
         self.changestate(self.state)
 
     def activate(self):
-        if self.state == 0:
-            self.start_process()
-        else:
-            logger.debug("not activating from state %d", self.state)
+        if self.desired_state != ST_ACTIVE:
+            self.desired_state = ST_ACTIVE
+            if self.pid is None:
+                self.start_process()
+            else:
+                logger.debug("already activated. nothing to do")
 
     def deactivate(self):
-        if self.state & ST_ACTIVE:
-            self.stop_process()
-        else:
-            logger.debug("not deactivating from state %d", self.state)
+        if self.desired_state != 0:
+            self.desired_state = 0
+            if self.pid is not None and not self.killed:
+                self.stop_process()
+            else:
+                logger.debug("already deactivated. nothing to do.")
 
     def close(self):
         self.deactivate()