From 399124bf464d11357ac5fa4c3413ee3b832e27cf Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 12 May 2015 21:43:50 +0200 Subject: [PATCH] startup: Handle frontend and backend failures --- mopidy/backend.py | 4 +++ mopidy/commands.py | 64 ++++++++++++++++++++++++++-------------------- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/mopidy/backend.py b/mopidy/backend.py index 72293f94..0cea0f85 100644 --- a/mopidy/backend.py +++ b/mopidy/backend.py @@ -58,6 +58,10 @@ class Backend(object): def has_playlists(self): return self.playlists is not None + def ping(self): + """Called to check if the actor is still alive.""" + return True + class LibraryProvider(object): diff --git a/mopidy/commands.py b/mopidy/commands.py index 137e4a3e..4890c722 100644 --- a/mopidy/commands.py +++ b/mopidy/commands.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, print_function, unicode_literals import argparse import collections +import contextlib import logging import os import sys @@ -232,6 +233,23 @@ class Command(object): raise NotImplementedError +@contextlib.contextmanager +def _actor_error_handling(name): + try: + yield + except exceptions.BackendError as exc: + logger.error( + 'Backend (%s) initialization error: %s', name, exc.message) + except exceptions.FrontendError as exc: + logger.error( + 'Frontend (%s) initialization error: %s', name, exc.message) + except exceptions.MixerError as exc: + logger.error( + 'Mixer (%s) initialization error: %s', name, exc.message) + except Exception: + logger.exception('Got un-handled exception from %s', name) + + # TODO: move out of this utility class class RootCommand(Command): @@ -328,22 +346,14 @@ class RootCommand(Command): def start_mixer(self, config, mixer_class): logger.info('Starting Mopidy mixer: %s', mixer_class.__name__) - mixer = None - - try: + with _actor_error_handling(mixer_class.__name__): mixer = mixer_class.start(config=config).proxy() - mixer.ping().get() - except exceptions.MixerError as exc: - logger.error('Mixer (%s) initialization error: %s', - mixer_class.__name__, exc.message) - except pykka.ActorDeadError as exc: - mixer = None - logger.error('Mixer actor died: %s', exc) - except Exception: - logger.exception('Mixer (%s) initialization exception:', - mixer_class.__name__) - - return mixer + try: + mixer.ping().get() + return mixer + except pykka.ActorDeadError as exc: + logger.error('Actor died: %s', exc) + return None def configure_mixer(self, config, mixer): volume = config['audio']['mixer_volume'] @@ -364,16 +374,19 @@ class RootCommand(Command): backends = [] for backend_class in backend_classes: - try: + with _actor_error_handling(backend_class.__name__): with timer.time_logger(backend_class.__name__): backend = backend_class.start( config=config, audio=audio).proxy() - backends.append(backend) - except exceptions.BackendError as exc: - logger.error( - 'Backend (%s) initialization error: %s', - backend_class.__name__, exc.message) - raise + backends.append(backend) + + # Block until all on_starts have finished, letting them run in parallel + for backend in backends[:]: + try: + backend.ping().get() + except pykka.ActorDeadError as exc: + backends.remove(backend) + logger.error('Actor died: %s', exc) return backends @@ -388,14 +401,9 @@ class RootCommand(Command): ', '.join(f.__name__ for f in frontend_classes) or 'none') for frontend_class in frontend_classes: - try: + with _actor_error_handling(frontend_class.__name__): with timer.time_logger(frontend_class.__name__): frontend_class.start(config=config, core=core) - except exceptions.FrontendError as exc: - logger.error( - 'Frontend (%s) initialization error: %s', - frontend_class.__name__, exc.message) - raise def stop_frontends(self, frontend_classes): logger.info('Stopping Mopidy frontends')