RFC/TaskDependencies: Difference between revisions
(Created page with "This page details a proposal for handling dependencies between tasks in Firefox. = Problems addressed by this proposal = Firefox is progressively being refactored and extended ...") |
(→API) |
||
| Line 20: | Line 20: | ||
interface nsIPromiseService: nsISupports { | interface nsIPromiseService: nsISupports { | ||
/** | /** | ||
* Return a fresh | * Return a fresh promise. | ||
*/ | */ | ||
nsIPromise makePromise(); | |||
/** | /** | ||
* Return a timeout, i.e. a | * Return a timeout, i.e. a promise that will automatically | ||
* be | * be rejected after some delay. | ||
*/ | */ | ||
nsIPromise timeout(long milliseconds) | nsIPromise timeout(long milliseconds, nsIPromise origin) | ||
/** | /** | ||
* Return a | * Return a promise that is satisfied when all the arguments are satisfied. | ||
*/ | */ | ||
nsIPromise and(in uint32 count, | nsIPromise and(in uint32 count, | ||
| Line 37: | Line 37: | ||
/** | /** | ||
* Return a | * Return a promise that is satisfied once at least one of the | ||
* arguments is satisfied. | * arguments is satisfied. | ||
*/ | */ | ||
| Line 56: | Line 56: | ||
void then(in nsIPromiseCallback onResolve, | void then(in nsIPromiseCallback onResolve, | ||
[optional] in nsIPromiseCallback onReject); | [optional] in nsIPromiseCallback onReject); | ||
/** | /** | ||
* Resolve this dependency | * Resolve this dependency | ||
| Line 77: | Line 72: | ||
void run(); | void run(); | ||
}; | }; | ||
= Informal examples = | |||
== Simple dependency == | |||
Component <tt>Foo</tt> can only be initialized once component <tt>Bar</tt> has reached a state "ready". | |||
Steps: | |||
* component <tt>Bar</tt> publishes a promise <tt>ready</tt>; | |||
* component <tt>Foo</tt>'s initialization is triggered once <tt>ready</tt> is resolved. | |||
== Dependency with timeout == | |||
Component <tt>Foo</tt> can only be initialized once component <tt>Bar</tt> has reached a state "ready". However, if <tt>Bar</tt> does not reach that state after 1 second, | |||
Steps: | |||
* component <tt>Bar</tt> publishes a promise <tt>ready</tt>; | |||
* component <tt>Foo</tt>'s derives a new promise using <tt>nsIPromiseService::timeout</tt> and this new promise triggers initialization of <tt>Foo</tt>. | |||
== Shutdown dependency == | |||
Component <tt>FooService</tt> can only be shutdown once all the components that use it have reached a state in which they do not require <tt>FooService</tt> anymore. | |||
Steps: | |||
* component <tt>FooService</tt> offers a method <tt>addBlocker</tt> which registers and returns a new <tt>nsIPromise</tt> to each caller; | |||
* each client of <tt>FooService</tt> calls <tt>addBlocker</tt> and resolves the promise only once it is ready to release <tt>FooService</tt>; | |||
* once <tt>FooService</tt> is willing to shutdown, it waits for all the promises registered with <tt>addBlocker</tt> until shutdown can proceed. | |||
Revision as of 15:46, 12 January 2013
This page details a proposal for handling dependencies between tasks in Firefox.
Problems addressed by this proposal
Firefox is progressively being refactored and extended to make is asynchronous, so as to avoid jank. However, mozilla-central currently offers little support for this model of programming.
This proposal introduces a minimal API and guideline that may be used to simplify the task of developing asynchronous tasks linked together by dependencies.
General ideas
- Provide a nsIPromise interface and a few functions to manipulate it. An instance of nsIPromise is a state for which some component may wish to wait.
- Get used to exposing nsIPromise in components so that other components can explicitly wait for them.
- JavaScript components can expose regular promises (existing module promise/core.js) instead of instances of nsIPromise
API
nsIPromise
interface nsIPromiseService: nsISupports {
/**
* Return a fresh promise.
*/
nsIPromise makePromise();
/**
* Return a timeout, i.e. a promise that will automatically
* be rejected after some delay.
*/
nsIPromise timeout(long milliseconds, nsIPromise origin)
/**
* Return a promise that is satisfied when all the arguments are satisfied.
*/
nsIPromise and(in uint32 count,
[array, size_is(count)] in nsIPromise dependencies);
/**
* Return a promise that is satisfied once at least one of the
* arguments is satisfied.
*/
nsIPromise or(in uint32 count,
[array, size_is(count)] in nsIPromise dependencies);
/**
* Interactions between XPCOM promises and JavaScript promises
*/
nsIPromise import(jsval jsPromise);
jsval export(nsIPromise xpcomPromise);
};
interface nsIPromise: nsISupports {
/**
* Run a callback once the dependency is resolved
*/
void then(in nsIPromiseCallback onResolve,
[optional] in nsIPromiseCallback onReject);
/**
* Resolve this dependency
*/
void resolve();
/**
* Reject this dependency
*/
void reject();
};
[function]
interface nsIPromiseCallback: nsISupports {
void run();
};
Informal examples
Simple dependency
Component Foo can only be initialized once component Bar has reached a state "ready".
Steps:
- component Bar publishes a promise ready;
- component Foo's initialization is triggered once ready is resolved.
Dependency with timeout
Component Foo can only be initialized once component Bar has reached a state "ready". However, if Bar does not reach that state after 1 second,
Steps:
- component Bar publishes a promise ready;
- component Foo's derives a new promise using nsIPromiseService::timeout and this new promise triggers initialization of Foo.
Shutdown dependency
Component FooService can only be shutdown once all the components that use it have reached a state in which they do not require FooService anymore.
Steps:
- component FooService offers a method addBlocker which registers and returns a new nsIPromise to each caller;
- each client of FooService calls addBlocker and resolves the promise only once it is ready to release FooService;
- once FooService is willing to shutdown, it waits for all the promises registered with addBlocker until shutdown can proceed.