| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| |
| import 'package:fidl/fidl.dart'; |
| import 'package:fidl_fuchsia_modular/fidl.dart' as fidl; |
| import 'package:lib.app.dart/app.dart'; |
| import 'package:lib.app.dart/logging.dart'; |
| import 'package:meta/meta.dart'; |
| |
| import 'lifecycle_impl.dart'; |
| |
| export 'package:lib.app.dart/app.dart' show ServiceProviderImpl; |
| |
| /// Hosts a [LifecycleImpl] and manages the underlying [binding] connection. |
| class LifecycleHost { |
| /// The underlying [Binding] that connects the [impl] to client requests. |
| final fidl.LifecycleBinding binding = fidl.LifecycleBinding(); |
| |
| /// Callback for when the system starts to shutdown this process. |
| final LifecycleTerminateCallback onTerminate; |
| |
| /// The underlying impl that handles client requests by delegating to |
| /// the [onTerminate] callback. |
| LifecycleImpl impl; |
| |
| /// Constructor. |
| LifecycleHost({ |
| @required this.onTerminate, |
| }) : assert(onTerminate != null) { |
| impl = LifecycleImpl( |
| onTerminate: _handleTerminate, |
| ); |
| |
| binding |
| ..onBind = _handleBind |
| ..onClose = _handleClose |
| ..onConnectionError = _handleConnectionError |
| ..onUnbind = _handleUnbind; |
| } |
| |
| Completer<Null> _addService; |
| |
| /// Connect this LifecycleHost's impl to the |
| /// [StartupContext#outgoingServices]. |
| Future<Null> addService({ |
| @required StartupContext startupContext, |
| }) { |
| assert(startupContext != null); |
| |
| log.fine('starting lifecycle host'); |
| |
| // Do not create an error by rebinding if, for some reason, this method has been called already. |
| if (_addService != null) { |
| Exception err = |
| Exception('#addService() should only be called once.'); |
| _addService.completeError(err); |
| return _addService.future; |
| } else { |
| _addService = Completer<Null>(); |
| } |
| |
| startupContext.outgoingServices.addServiceForName( |
| (InterfaceRequest<fidl.Lifecycle> request) { |
| try { |
| binding.bind(impl, request); |
| } on Exception catch (err, stackTrace) { |
| _addService.completeError(err, stackTrace); |
| } |
| |
| // There is no async way to hook into a success path once the lifecycle |
| // service has been added. Additionally, errors can occur on the underlying |
| // binding at anytime. Use a microtask to check for the future being |
| // completed (with an error via _handleConnectionError) and complete |
| // successfully if it hasn't. |
| scheduleMicrotask(() { |
| if (!_addService.isCompleted) { |
| _addService.complete(null); |
| } |
| }); |
| }, |
| fidl.Lifecycle.$serviceName, |
| ); |
| |
| return _addService.future; |
| } |
| |
| void _handleConnectionError() { |
| Exception err = Exception('binding connection failed'); |
| |
| if (_addService != null && !_addService.isCompleted) { |
| _addService.completeError(err); |
| return; |
| } |
| |
| // NOTE: this should be very a rare case. |
| log.warning('binding connection failed outside of async control flow.'); |
| throw err; |
| } |
| |
| void _handleBind() { |
| log.fine('binding ready'); |
| } |
| |
| void _handleUnbind() { |
| log.fine('binding unbound'); |
| } |
| |
| void _handleClose() { |
| log.fine('binding closed'); |
| } |
| |
| Future<Null> _handleTerminate() async { |
| await Future.wait(<Future<Null>>[ |
| terminate(), |
| onTerminate(), |
| ]); |
| } |
| |
| /// Closes the underlying binding, usually called as a direct effect of |
| /// Lifecycle::terminate (see https://goo.gl/MmZ2dc) being triggered by the |
| /// framework. |
| Future<Null> terminate() async { |
| log.fine('terminate called, closing $binding'); |
| binding.close(); |
| return null; |
| } |
| } |