Repository: MycroftAI/mycroft-core

Issue:
Lock kills service on autoreload
the service is running a tornado instance that autoreloads the service if a file in the mycroft-core tree is modified. The service also contains a Lock that ensures only one service instance is loaded. When the service is reloaded the same PID is used and the lock kills it.


Discussion:

Resolving patch:
diff --git a/mycroft/lock/__init__.py b/mycroft/lock/__init__.py
--- a/mycroft/lock/__init__.py
+++ b/mycroft/lock/__init__.py
@@ -187,8 +187,11 @@ def delete(self, *args):
         *args: Ignored.  Required as this fuction is called as a signel
         handler.
         '''
-        with open(self.path, 'r') as L:
-            if self.__pid == L.read():
-                os.unlink(self.path)
-
+        try:
+            with open(self.path, 'r') as L:
+                pid = int(L.read())
+                if self.__pid == pid:
+                    os.unlink(self.path)
+        except IOError:
+            pass
     # End class Lock
diff --git a/mycroft/messagebus/service/main.py b/mycroft/messagebus/service/main.py
--- a/mycroft/messagebus/service/main.py
+++ b/mycroft/messagebus/service/main.py
@@ -17,6 +17,7 @@
 
 import tornado.ioloop as ioloop
 import tornado.web as web
+import tornado.autoreload as autoreload
 
 from mycroft.configuration import ConfigurationManager
 from mycroft.messagebus.service.ws import WebsocketEventHandler
@@ -35,6 +36,13 @@ def main():
     import tornado.options
     lock = Lock("service")
     tornado.options.parse_command_line()
+
+    def reload_hook():
+        """ Hook to release lock when autoreload is triggered. """
+        lock.delete()
+
+    tornado.autoreload.add_reload_hook(reload_hook)
+
     config = ConfigurationManager.get().get("websocket")
 
     host = config.get("host")


Test patch:
diff --git a/test/lock/__init__.py b/test/lock/__init__.py
new file mode 100644
diff --git a/test/lock/test_lock.py b/test/lock/test_lock.py
new file mode 100644
--- /dev/null
+++ b/test/lock/test_lock.py
@@ -0,0 +1,46 @@
+from os.path import dirname, join, exists, isfile
+import os
+import signal
+import unittest
+import mock
+from shutil import rmtree
+from mycroft.lock import Lock
+
+
+class TestLock(unittest.TestCase):
+    def setUp(self):
+        if exists('/tmp/mycroft'):
+            rmtree('/tmp/mycroft')
+
+    def test_create_lock(self):
+        l1 = Lock('test')
+        self.assertTrue(isfile('/tmp/mycroft/test.pid'))
+
+    def test_delete_lock(self):
+        l1 = Lock('test')
+        self.assertTrue(isfile('/tmp/mycroft/test.pid'))
+        l1.delete()
+        self.assertFalse(isfile('/tmp/mycroft/test.pid'))
+
+    @mock.patch('os.kill')
+    def test_existing_lock(self, mock_kill):
+        """ Test that an existing lock will kill the old pid. """
+        l1 = Lock('test')
+        self.assertTrue(isfile('/tmp/mycroft/test.pid'))
+        l2 = Lock('test2')
+        self.assertFalse(mock_kill.called)
+        l2 = Lock('test')
+        self.assertTrue(mock_kill.called)
+
+    def test_keyboard_interrupt(self):
+        l1 = Lock('test')
+        self.assertTrue(isfile('/tmp/mycroft/test.pid'))
+        try:
+            os.kill(os.getpid(), signal.SIGINT)
+        except KeyboardInterrupt:
+            pass
+        self.assertFalse(isfile('/tmp/mycroft/test.pid'))
+
+
+if __name__ == '__main__':
+    unittest.main()