RFC/TaskDependencies

From MozillaWiki
Jump to: navigation, search

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 nsIDependencyService: nsISupports {
   /**
    * Return a fresh promise.
    */
   nsIPromise makePromise();
   /**
    * Return a fresh barrier.
    */
   nsIBarrier makeBarrier();
   /**
    * 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(nsresult status);
 };
 /**
  * A synchronization barrier.
  *
  * The barrier is lifted once all the promises
  * obtained by addBlocker are resolved.
  */
 interface nsIBarrier: nsISupports {
   nsIPromise addBlocker();
   /**
    * Register callbacks to be called once all the
    * promises obtained by addBlocker are resolved.
    */
   void then(in nsIPromiseCallback onResolve,
             [optional] in nsIPromiseCallback onReject);
 };

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.