Repository: mopidy/mopidy
Issue: Can' stop mopidy

V2.0 c4da2c2:
A  `sudo service mopidy stop` does not stop mopidy. At last it stops after a timeout.

Enabled extensions: mpd, dleyna, http, stream, alsamixer, m3u, local-images, softwaremixer, file, musicbox_webclient, tidal, local, local-sqlite

Target: RPi 2, raspbian

[Edit: removed the `<ctrl-c>`]

<!---
@huboard:{"custom_state":"archived"}
-->


Changed files:
mopidy/commands.py, mopidy/__main__.py, mopidy/internal/process.py

diff --git a/mopidy/__main__.py b/mopidy/__main__.py
index ee87b82d..86a0c19c 100644
--- a/mopidy/__main__.py
+++ b/mopidy/__main__.py
@@ -27,7 +27,7 @@ def main():
     log.bootstrap_delayed_logging()
     logger.info('Starting Mopidy %s', versioning.get_version())
 
-    signal.signal(signal.SIGTERM, process.exit_handler)
+    signal.signal(signal.SIGTERM, process.sigterm_handler)
     # Windows does not have signal.SIGUSR1
     if hasattr(signal, 'SIGUSR1'):
         signal.signal(signal.SIGUSR1, pykka.debug.log_thread_tracebacks)
diff --git a/mopidy/commands.py b/mopidy/commands.py
index 74905f8f..50590172 100644
--- a/mopidy/commands.py
+++ b/mopidy/commands.py
@@ -5,6 +5,7 @@ import collections
 import contextlib
 import logging
 import os
+import signal
 import sys
 
 import pykka
@@ -13,7 +14,7 @@ from mopidy import config as config_lib, exceptions
 from mopidy.audio import Audio
 from mopidy.core import Core
 from mopidy.internal import deps, process, timer, versioning
-from mopidy.internal.gi import GLib, GObject
+from mopidy.internal.gi import GLib
 
 logger = logging.getLogger(__name__)
 
@@ -283,7 +284,13 @@ class RootCommand(Command):
             help='`section/key=value` values to override config options')
 
     def run(self, args, config):
-        loop = GObject.MainLoop()
+        def on_sigterm(loop):
+            logger.info('GLib mainloop got SIGTERM. Exiting...')
+            loop.quit()
+
+        loop = GLib.MainLoop()
+        GLib.unix_signal_add(
+            GLib.PRIORITY_DEFAULT, signal.SIGTERM, on_sigterm, loop)
 
         mixer_class = self.get_mixer_class(config, args.registry['mixer'])
         backend_classes = args.registry['backend']
@@ -300,6 +307,7 @@ class RootCommand(Command):
             backends = self.start_backends(config, backend_classes, audio)
             core = self.start_core(config, mixer, backends, audio)
             self.start_frontends(config, frontend_classes, core)
+            logger.info('Starting GLib mainloop')
             loop.run()
         except (exceptions.BackendError,
                 exceptions.FrontendError,
diff --git a/mopidy/internal/process.py b/mopidy/internal/process.py
index 8c8af18f..4bf681dd 100644
--- a/mopidy/internal/process.py
+++ b/mopidy/internal/process.py
@@ -1,7 +1,6 @@
 from __future__ import absolute_import, unicode_literals
 
 import logging
-import signal
 import threading
 
 import pykka
@@ -12,20 +11,23 @@ from mopidy.compat import thread
 logger = logging.getLogger(__name__)
 
 
-SIGNALS = dict(
-    (k, v) for v, k in signal.__dict__.items()
-    if v.startswith('SIG') and not v.startswith('SIG_'))
-
-
 def exit_process():
     logger.debug('Interrupting main...')
     thread.interrupt_main()
     logger.debug('Interrupted main')
 
 
-def exit_handler(signum, frame):
-    """A :mod:`signal` handler which will exit the program on signal."""
-    logger.info('Got %s signal', SIGNALS[signum])
+def sigterm_handler(signum, frame):
+    """A :mod:`signal` handler which will exit the program on signal.
+
+    This function is not called when the process' main thread is running a GLib
+    mainloop. In that case, the GLib mainloop must listen for SIGTERM signals
+    and quit itself.
+
+    For Mopidy subcommands that does not run the GLib mainloop, this handler
+    ensures a proper shutdown of the process on SIGTERM.
+    """
+    logger.info('Got SIGTERM signal. Exiting...')
     exit_process()