Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(182)

Unified Diff: pkg/gcloud/lib/service_scope.dart

Issue 804973002: Add appengine/gcloud/mustache dependencies. (Closed) Base URL: git@github.com:dart-lang/pub-dartlang-dart.git@master
Patch Set: Added AUTHORS/LICENSE/PATENTS files Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/gcloud/lib/http.dart ('k') | pkg/gcloud/lib/src/datastore_impl.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/gcloud/lib/service_scope.dart
diff --git a/pkg/gcloud/lib/service_scope.dart b/pkg/gcloud/lib/service_scope.dart
new file mode 100644
index 0000000000000000000000000000000000000000..856365c77de4b7f1164857e86dd51c56bb6eac7a
--- /dev/null
+++ b/pkg/gcloud/lib/service_scope.dart
@@ -0,0 +1,282 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This library enables one to create a service scope in which code can run.
+///
+/// A service scope is an environment in which code runs. The environment is a
+/// [Zone] with added functionality. Code can be run inside a new service scope
+/// by using the `fork(callback)` method. This will call `callback` inside a new
+/// service scope and will keep the scope alive until the Future returned by the
+/// callback completes. At this point the service scope ends.
+///
+/// Code running inside a new service scope can
+///
+/// - register objects (e.g. a database connection pool or a logging service)
+/// - look up previously registered objects
+/// - register on-scope-exit handlers
+///
+/// Service scopes can be nested. All registered values from the parent service
+/// scope are still accessible as long as they have not been overridden. The
+/// callback passed to `fork()` is responsible for not completing it's returned
+/// Future until all nested service scopes have ended.
+///
+/// The on-scope-exit callbacks will be called when the service scope ends. The
+/// callbacks are run in reverse registration order and are guaranteed to be
+/// executed. During a scope exit callback the active service scope cannot
+/// be modified anymore and `lookup()`s will only return values which were
+/// registered before the registration of the on-scope-exit callback.
+///
+/// One use-case of this is making services available to a server application.
+/// The server application will run inside a service scope which will have all
+/// necessary services registered.
+/// Once the server app shuts down, the registered on-scope-exit callbacks will
+/// automatically be invoked and the process will shut down cleanly.
+///
+/// Here is an example use case:
+///
+/// import 'dart:async';
+/// import 'package:gcloud/service_scope.dart' as scope;
+///
+/// class DBPool { ... }
+///
+/// DBPool get dbService => scope.lookup(#dbpool);
+///
+/// Future runApp() {
+/// // The application can use the registered objects (here the
+/// // dbService). It does not need to pass it around, but can use a
+/// // global getter.
+/// return dbService.query( ... ).listen(print).asFuture();
+/// }
+///
+/// main() {
+/// // Creates a new service scope and runs the given closure inside it.
+/// ss.fork(() {
+/// // We create a new database pool with a 10 active connections and
+/// // add it to the current service scope with key `#dbpool`.
+/// // In addition we insert a on-scope-exit callback which will be
+/// // called once the application is done.
+/// var pool = new DBPool(connections: 10);
+/// scope.register(#dbpool, pool, onScopeExit: () => pool.close());
+/// return runApp();
+/// }).then((_) {
+/// print('Server application shut down cleanly');
+/// });
+/// }
+///
+/// As an example, the `package:appengine/appengine.dart` package runs request
+/// handlers inside a service scope, which has most `package:gcloud` services
+/// registered.
+///
+/// The core application code can then be independent of `package:appengine`
+/// and instead depend only on the services needed (e.g.
+/// `package:gcloud/storage.dart`) by using getters in the service library (e.g.
+/// the `storageService`) which are implemented with service scope lookups.
+library gcloud.service_scope;
+
+import 'dart:async';
+
+/// The Symbol used as index in the zone map for the service scope object.
+const Symbol _ServiceScopeKey = #_gcloud.service_scope;
+
+/// An empty service scope.
+///
+/// New service scope can be created by calling [fork] on the empty
+/// service scope.
+final _ServiceScope _emptyServiceScope = new _ServiceScope();
+
+/// Returns the current [_ServiceScope] object.
+_ServiceScope get _serviceScope => Zone.current[_ServiceScopeKey];
+
+/// Start a new zone with a new service scope and run [func] inside it.
+///
+/// The function [func] must return a `Future` and the service scope will end
+/// when this future completes.
+///
+/// If an uncaught error occurs and [onError] is given, it will be called. The
+/// `onError` parameter can take the same values as `Zone.current.fork`.
+Future fork(Future func(), {Function onError}) {
+ var currentServiceScope = _serviceScope;
+ if (currentServiceScope == null) {
+ currentServiceScope = _emptyServiceScope;
+ }
+ return currentServiceScope._fork(func, onError: onError);
+}
+
+/// Register a new [object] into the current service scope using the given
+/// [key].
+///
+/// If [onScopeExit] is provided, it will be called when the service scope ends.
+///
+/// The registered on-scope-exit functions are executed in reverse registration
+/// order.
+void register(Object key, Object value, {onScopeExit()}) {
+ var serviceScope = _serviceScope;
+ if (serviceScope == null) {
+ throw new StateError('Not running inside a service scope zone.');
+ }
+ serviceScope.register(key, value, onScopeExit: onScopeExit);
+}
+
+/// Register a [onScopeExitCallback] to be invoked when this service scope ends.
+///
+/// The registered on-scope-exit functions are executed in reverse registration
+/// order.
+Object registerScopeExitCallback(onScopeExitCallback()) {
+ var serviceScope = _serviceScope;
+ if (serviceScope == null) {
+ throw new StateError('Not running inside a service scope zone.');
+ }
+ return serviceScope.registerOnScopeExitCallback(onScopeExitCallback);
+}
+
+/// Look up an item by it's key in the currently active service scope.
+///
+/// Returns `null` if there is no entry with the given key.
+Object lookup(Object key) {
+ var serviceScope = _serviceScope;
+ if (serviceScope == null) {
+ throw new StateError('Not running inside a service scope zone.');
+ }
+ return serviceScope.lookup(key);
+}
+
+/// Represents a global service scope of values stored via zones.
+class _ServiceScope {
+ /// A mapping of keys to values stored inside the service scope.
+ final Map<Object, Object> _key2Values = new Map<Object, Object>();
+
+ /// A set which indicates whether an object was copied from it's parent.
+ final Set<Object> _parentCopies = new Set<Object>();
+
+ /// On-Scope-Exit functions which will be called in reverse insertion order.
+ final List<_RegisteredEntry> _registeredEntries = [];
+
+ bool _cleaningUp = false;
+ bool _destroyed = false;
+
+ /// Looks up an object by it's service scope key - returns `null` if not
+ /// found.
+ Object lookup(Object serviceScope) {
+ _ensureNotInDestroyingState();
+ var entry = _key2Values[serviceScope];
+ return entry != null ? entry.value : null;
+ }
+
+ /// Inserts a new item to the service scope using [serviceScopeKey].
+ ///
+ /// Optionally calls a [onScopeExit] function once this service scope ends.
+ void register(Object serviceScopeKey, Object value, {onScopeExit()}) {
+ _ensureNotInCleaningState();
+ _ensureNotInDestroyingState();
+
+ bool isParentCopy = _parentCopies.contains(serviceScopeKey);
+ if (!isParentCopy && _key2Values.containsKey(serviceScopeKey)) {
+ throw new ArgumentError(
+ 'Servie scope already contains key $serviceScopeKey.');
+ }
+
+ var entry = new _RegisteredEntry(serviceScopeKey, value, onScopeExit);
+
+ _key2Values[serviceScopeKey] = entry;
+ if (isParentCopy) _parentCopies.remove(serviceScopeKey);
+
+ _registeredEntries.add(entry);
+ }
+
+ /// Inserts a new on-scope-exit function to be called once this service scope
+ /// ends.
+ void registerOnScopeExitCallback(onScopeExitCallback()) {
+ _ensureNotInCleaningState();
+ _ensureNotInDestroyingState();
+
+ if (onScopeExitCallback != null) {
+ _registeredEntries.add(
+ new _RegisteredEntry(null, null, onScopeExitCallback));
+ }
+ }
+
+ /// Start a new zone with a forked service scope.
+ Future _fork(Future func(), {Function onError}) {
+ _ensureNotInCleaningState();
+ _ensureNotInDestroyingState();
+
+ var serviceScope = _copy();
+ var map = { _ServiceScopeKey: serviceScope };
+ return runZoned(() {
+ var f = func();
+ if (f is! Future) {
+ throw new ArgumentError('Forking a service scope zone requires the '
+ 'callback function to return a future.');
+ }
+ return f.whenComplete(serviceScope._runScopeExitHandlers);
+ }, zoneValues: map, onError: onError);
+ }
+
+ void _ensureNotInDestroyingState() {
+ if (_destroyed) {
+ throw new StateError(
+ 'The service scope has already been exited. It is therefore '
+ 'forbidden to use this service scope anymore. '
+ 'Please make sure that your code waits for all asynchronous tasks '
+ 'before the closure passed to fork() completes.');
+ }
+ }
+
+ void _ensureNotInCleaningState() {
+ if (_cleaningUp) {
+ throw new StateError(
+ 'The service scope is in the process of cleaning up. It is therefore '
+ 'forbidden to make any modifications to the current service scope. '
+ 'Please make sure that your code waits for all asynchronous tasks '
+ 'before the closure passed to fork() completes.');
+ }
+ }
+
+ /// Copies all service scope entries to a new service scope, but not their
+ /// on-scope-exit handlers.
+ _ServiceScope _copy() {
+ var serviceScopeCopy = new _ServiceScope();
+ serviceScopeCopy._key2Values.addAll(_key2Values);
+ serviceScopeCopy._parentCopies.addAll(_key2Values.keys);
+ return serviceScopeCopy;
+ }
+
+ /// Runs all on-scope-exit functions in [_ServiceScope].
+ Future _runScopeExitHandlers() {
+ _cleaningUp = true;
+ var errors = [];
+
+ // We are running all on-scope-exit functions in reverse registration order.
+ // Even if one fails, we continue cleaning up and report then the list of
+ // errors (if there were any).
+ return Future.forEach(_registeredEntries.reversed,
+ (_RegisteredEntry registeredEntry) {
+ if (registeredEntry.key != null) {
+ _key2Values.remove(registeredEntry.key);
+ }
+ if (registeredEntry.scopeExitCallback != null) {
+ return new Future.sync(registeredEntry.scopeExitCallback)
+ .catchError((e, s) => errors.add(e));
+ } else {
+ return new Future.value();
+ }
+ }).then((_) {
+ _cleaningUp = true;
+ _destroyed = true;
+ if (errors.length > 0) {
+ throw new Exception(
+ 'The following errors occured while running scope exit handlers'
+ ': $errors');
+ }
+ });
+ }
+}
+
+class _RegisteredEntry {
+ final Object key;
+ final Object value;
+ final Function scopeExitCallback;
+
+ _RegisteredEntry(this.key, this.value, this.scopeExitCallback);
+}
« no previous file with comments | « pkg/gcloud/lib/http.dart ('k') | pkg/gcloud/lib/src/datastore_impl.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698