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

Side by Side 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 unified diff | 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 »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 /// This library enables one to create a service scope in which code can run.
6 ///
7 /// A service scope is an environment in which code runs. The environment is a
8 /// [Zone] with added functionality. Code can be run inside a new service scope
9 /// by using the `fork(callback)` method. This will call `callback` inside a new
10 /// service scope and will keep the scope alive until the Future returned by the
11 /// callback completes. At this point the service scope ends.
12 ///
13 /// Code running inside a new service scope can
14 ///
15 /// - register objects (e.g. a database connection pool or a logging service)
16 /// - look up previously registered objects
17 /// - register on-scope-exit handlers
18 ///
19 /// Service scopes can be nested. All registered values from the parent service
20 /// scope are still accessible as long as they have not been overridden. The
21 /// callback passed to `fork()` is responsible for not completing it's returned
22 /// Future until all nested service scopes have ended.
23 ///
24 /// The on-scope-exit callbacks will be called when the service scope ends. The
25 /// callbacks are run in reverse registration order and are guaranteed to be
26 /// executed. During a scope exit callback the active service scope cannot
27 /// be modified anymore and `lookup()`s will only return values which were
28 /// registered before the registration of the on-scope-exit callback.
29 ///
30 /// One use-case of this is making services available to a server application.
31 /// The server application will run inside a service scope which will have all
32 /// necessary services registered.
33 /// Once the server app shuts down, the registered on-scope-exit callbacks will
34 /// automatically be invoked and the process will shut down cleanly.
35 ///
36 /// Here is an example use case:
37 ///
38 /// import 'dart:async';
39 /// import 'package:gcloud/service_scope.dart' as scope;
40 ///
41 /// class DBPool { ... }
42 ///
43 /// DBPool get dbService => scope.lookup(#dbpool);
44 ///
45 /// Future runApp() {
46 /// // The application can use the registered objects (here the
47 /// // dbService). It does not need to pass it around, but can use a
48 /// // global getter.
49 /// return dbService.query( ... ).listen(print).asFuture();
50 /// }
51 ///
52 /// main() {
53 /// // Creates a new service scope and runs the given closure inside it.
54 /// ss.fork(() {
55 /// // We create a new database pool with a 10 active connections and
56 /// // add it to the current service scope with key `#dbpool`.
57 /// // In addition we insert a on-scope-exit callback which will be
58 /// // called once the application is done.
59 /// var pool = new DBPool(connections: 10);
60 /// scope.register(#dbpool, pool, onScopeExit: () => pool.close());
61 /// return runApp();
62 /// }).then((_) {
63 /// print('Server application shut down cleanly');
64 /// });
65 /// }
66 ///
67 /// As an example, the `package:appengine/appengine.dart` package runs request
68 /// handlers inside a service scope, which has most `package:gcloud` services
69 /// registered.
70 ///
71 /// The core application code can then be independent of `package:appengine`
72 /// and instead depend only on the services needed (e.g.
73 /// `package:gcloud/storage.dart`) by using getters in the service library (e.g.
74 /// the `storageService`) which are implemented with service scope lookups.
75 library gcloud.service_scope;
76
77 import 'dart:async';
78
79 /// The Symbol used as index in the zone map for the service scope object.
80 const Symbol _ServiceScopeKey = #_gcloud.service_scope;
81
82 /// An empty service scope.
83 ///
84 /// New service scope can be created by calling [fork] on the empty
85 /// service scope.
86 final _ServiceScope _emptyServiceScope = new _ServiceScope();
87
88 /// Returns the current [_ServiceScope] object.
89 _ServiceScope get _serviceScope => Zone.current[_ServiceScopeKey];
90
91 /// Start a new zone with a new service scope and run [func] inside it.
92 ///
93 /// The function [func] must return a `Future` and the service scope will end
94 /// when this future completes.
95 ///
96 /// If an uncaught error occurs and [onError] is given, it will be called. The
97 /// `onError` parameter can take the same values as `Zone.current.fork`.
98 Future fork(Future func(), {Function onError}) {
99 var currentServiceScope = _serviceScope;
100 if (currentServiceScope == null) {
101 currentServiceScope = _emptyServiceScope;
102 }
103 return currentServiceScope._fork(func, onError: onError);
104 }
105
106 /// Register a new [object] into the current service scope using the given
107 /// [key].
108 ///
109 /// If [onScopeExit] is provided, it will be called when the service scope ends.
110 ///
111 /// The registered on-scope-exit functions are executed in reverse registration
112 /// order.
113 void register(Object key, Object value, {onScopeExit()}) {
114 var serviceScope = _serviceScope;
115 if (serviceScope == null) {
116 throw new StateError('Not running inside a service scope zone.');
117 }
118 serviceScope.register(key, value, onScopeExit: onScopeExit);
119 }
120
121 /// Register a [onScopeExitCallback] to be invoked when this service scope ends.
122 ///
123 /// The registered on-scope-exit functions are executed in reverse registration
124 /// order.
125 Object registerScopeExitCallback(onScopeExitCallback()) {
126 var serviceScope = _serviceScope;
127 if (serviceScope == null) {
128 throw new StateError('Not running inside a service scope zone.');
129 }
130 return serviceScope.registerOnScopeExitCallback(onScopeExitCallback);
131 }
132
133 /// Look up an item by it's key in the currently active service scope.
134 ///
135 /// Returns `null` if there is no entry with the given key.
136 Object lookup(Object key) {
137 var serviceScope = _serviceScope;
138 if (serviceScope == null) {
139 throw new StateError('Not running inside a service scope zone.');
140 }
141 return serviceScope.lookup(key);
142 }
143
144 /// Represents a global service scope of values stored via zones.
145 class _ServiceScope {
146 /// A mapping of keys to values stored inside the service scope.
147 final Map<Object, Object> _key2Values = new Map<Object, Object>();
148
149 /// A set which indicates whether an object was copied from it's parent.
150 final Set<Object> _parentCopies = new Set<Object>();
151
152 /// On-Scope-Exit functions which will be called in reverse insertion order.
153 final List<_RegisteredEntry> _registeredEntries = [];
154
155 bool _cleaningUp = false;
156 bool _destroyed = false;
157
158 /// Looks up an object by it's service scope key - returns `null` if not
159 /// found.
160 Object lookup(Object serviceScope) {
161 _ensureNotInDestroyingState();
162 var entry = _key2Values[serviceScope];
163 return entry != null ? entry.value : null;
164 }
165
166 /// Inserts a new item to the service scope using [serviceScopeKey].
167 ///
168 /// Optionally calls a [onScopeExit] function once this service scope ends.
169 void register(Object serviceScopeKey, Object value, {onScopeExit()}) {
170 _ensureNotInCleaningState();
171 _ensureNotInDestroyingState();
172
173 bool isParentCopy = _parentCopies.contains(serviceScopeKey);
174 if (!isParentCopy && _key2Values.containsKey(serviceScopeKey)) {
175 throw new ArgumentError(
176 'Servie scope already contains key $serviceScopeKey.');
177 }
178
179 var entry = new _RegisteredEntry(serviceScopeKey, value, onScopeExit);
180
181 _key2Values[serviceScopeKey] = entry;
182 if (isParentCopy) _parentCopies.remove(serviceScopeKey);
183
184 _registeredEntries.add(entry);
185 }
186
187 /// Inserts a new on-scope-exit function to be called once this service scope
188 /// ends.
189 void registerOnScopeExitCallback(onScopeExitCallback()) {
190 _ensureNotInCleaningState();
191 _ensureNotInDestroyingState();
192
193 if (onScopeExitCallback != null) {
194 _registeredEntries.add(
195 new _RegisteredEntry(null, null, onScopeExitCallback));
196 }
197 }
198
199 /// Start a new zone with a forked service scope.
200 Future _fork(Future func(), {Function onError}) {
201 _ensureNotInCleaningState();
202 _ensureNotInDestroyingState();
203
204 var serviceScope = _copy();
205 var map = { _ServiceScopeKey: serviceScope };
206 return runZoned(() {
207 var f = func();
208 if (f is! Future) {
209 throw new ArgumentError('Forking a service scope zone requires the '
210 'callback function to return a future.');
211 }
212 return f.whenComplete(serviceScope._runScopeExitHandlers);
213 }, zoneValues: map, onError: onError);
214 }
215
216 void _ensureNotInDestroyingState() {
217 if (_destroyed) {
218 throw new StateError(
219 'The service scope has already been exited. It is therefore '
220 'forbidden to use this service scope anymore. '
221 'Please make sure that your code waits for all asynchronous tasks '
222 'before the closure passed to fork() completes.');
223 }
224 }
225
226 void _ensureNotInCleaningState() {
227 if (_cleaningUp) {
228 throw new StateError(
229 'The service scope is in the process of cleaning up. It is therefore '
230 'forbidden to make any modifications to the current service scope. '
231 'Please make sure that your code waits for all asynchronous tasks '
232 'before the closure passed to fork() completes.');
233 }
234 }
235
236 /// Copies all service scope entries to a new service scope, but not their
237 /// on-scope-exit handlers.
238 _ServiceScope _copy() {
239 var serviceScopeCopy = new _ServiceScope();
240 serviceScopeCopy._key2Values.addAll(_key2Values);
241 serviceScopeCopy._parentCopies.addAll(_key2Values.keys);
242 return serviceScopeCopy;
243 }
244
245 /// Runs all on-scope-exit functions in [_ServiceScope].
246 Future _runScopeExitHandlers() {
247 _cleaningUp = true;
248 var errors = [];
249
250 // We are running all on-scope-exit functions in reverse registration order.
251 // Even if one fails, we continue cleaning up and report then the list of
252 // errors (if there were any).
253 return Future.forEach(_registeredEntries.reversed,
254 (_RegisteredEntry registeredEntry) {
255 if (registeredEntry.key != null) {
256 _key2Values.remove(registeredEntry.key);
257 }
258 if (registeredEntry.scopeExitCallback != null) {
259 return new Future.sync(registeredEntry.scopeExitCallback)
260 .catchError((e, s) => errors.add(e));
261 } else {
262 return new Future.value();
263 }
264 }).then((_) {
265 _cleaningUp = true;
266 _destroyed = true;
267 if (errors.length > 0) {
268 throw new Exception(
269 'The following errors occured while running scope exit handlers'
270 ': $errors');
271 }
272 });
273 }
274 }
275
276 class _RegisteredEntry {
277 final Object key;
278 final Object value;
279 final Function scopeExitCallback;
280
281 _RegisteredEntry(this.key, this.value, this.scopeExitCallback);
282 }
OLDNEW
« 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