diff options
Diffstat (limited to 'toolkit/modules/docs')
-rw-r--r-- | toolkit/modules/docs/AsyncShutdown.rst | 278 | ||||
-rw-r--r-- | toolkit/modules/docs/FirstStartup.rst | 72 | ||||
-rw-r--r-- | toolkit/modules/docs/Region.rst | 72 | ||||
-rw-r--r-- | toolkit/modules/docs/index.rst | 12 |
4 files changed, 434 insertions, 0 deletions
diff --git a/toolkit/modules/docs/AsyncShutdown.rst b/toolkit/modules/docs/AsyncShutdown.rst new file mode 100644 index 0000000000..6e068450a7 --- /dev/null +++ b/toolkit/modules/docs/AsyncShutdown.rst @@ -0,0 +1,278 @@ +.. _AsyncShutdown: + +============== +AsyncShutdown +============== + +During shutdown of the process, subsystems are closed one after another. ``AsyncShutdown`` is a module dedicated to express shutdown-time dependencies between: + +- services and their clients; +- shutdown phases (e.g. profile-before-change) and their clients. + +.. _AsyncShutdown_Barriers: + +Barriers: Expressing shutdown dependencies towards a service +============================================================ + +Consider a service FooService. At some point during the shutdown of the process, this service needs to: + +- inform its clients that it is about to shut down; +- wait until the clients have completed their final operations based on FooService (often asynchronously); +- only then shut itself down. + +This may be expressed as an instance of ``AsyncShutdown.Barrier``. An instance of ``AsyncShutdown.Barrier`` provides: + +- a capability ``client`` that may be published to clients, to let them register or unregister blockers; +- methods for the owner of the barrier to let it consult the state of blockers and wait until all client-registered blockers have been resolved. + +Shutdown timeouts +----------------- + +By design, an instance of ``AsyncShutdown.Barrier`` will cause a crash +if it takes more than 60 seconds `awake` for its clients to lift or +remove their blockers (`awake` meaning that seconds during which the +computer is asleep or too busy to do anything are not counted). This +mechanism helps ensure that we do not leave the process in a state in +which it can neither proceed with shutdown nor be relaunched. + +If the CrashReporter is enabled, this crash will report: + +- the name of the barrier that failed; +- for each blocker that has not been released yet: + + - the name of the blocker; + - the state of the blocker, if a state function has been provided (see :ref:`AsyncShutdown.Barrier.state`). + +Example 1: Simple Barrier client +-------------------------------- + +The following snippet presents an example of a client of FooService that has a shutdown dependency upon FooService. In this case, the client wishes to ensure that FooService is not shutdown before some state has been reached. An example is clients that need write data asynchronously and need to ensure that they have fully written their state to disk before shutdown, even if due to some user manipulation shutdown takes place immediately. + +.. warning:: + `addBlocker()` can throw if it's too late in the shutdown process to add a + new blocker. The consumer must handle this case, especially if the blocker + contains code unblocking other shutdown phases. + `nsIShutdownClient.isClosed` can also be used to check for this condition. + +.. code-block:: javascript + + // Some client of FooService called FooClient + + const { FooService } = ChromeUtils.import( + "resource://gre/modules/FooService.jsm" + ); + + // FooService.shutdown is the `client` capability of a `Barrier`. + // See example 2 for the definition of `FooService.shutdown` + FooService.shutdown.addBlocker( + "FooClient: Need to make sure that we have reached some state", + () => promiseReachedSomeState + ); + // promiseReachedSomeState should be an instance of Promise resolved once + // we have reached the expected state + +Example 2: Simple Barrier owner +------------------------------- + +The following snippet presents an example of a service FooService that +wishes to ensure that all clients have had a chance to complete any +outstanding operations before FooService shuts down. + +.. code-block:: javascript + + // Module FooService + + const { AsyncShutdown } = ChromeUtils.importESModule( + "resource://gre/modules/AsyncShutdown.sys.mjs" + ); + + this.exports = ["FooService"]; + + let shutdown = new AsyncShutdown.Barrier("FooService: Waiting for clients before shutting down"); + + // Export the `client` capability, to let clients register shutdown blockers + FooService.shutdown = shutdown.client; + + // This function should be triggered at some point during shutdown, generally + // as a client to another Barrier or Phase. Triggering this function is not covered + // in this snippet. + let onshutdown = async function() { + // Wait for all registered clients to have lifted the barrier + await shutdown.wait(); + + // Now deactivate FooService itself. + // ... + }); + +Frequently, a service that owns a ``AsyncShutdown.Barrier`` is itself a client of another Barrier. + +.. _AsyncShutdown.Barrier.state: + +Example 3: More sophisticated Barrier client +-------------------------------------------- + +The following snippet presents FooClient2, a more sophisticated client of FooService that needs to perform a number of operations during shutdown but before the shutdown of FooService. Also, given that this client is more sophisticated, we provide a function returning the state of FooClient2 during shutdown. If for some reason FooClient2's blocker is never lifted, this state can be reported as part of a crash report. + +.. code-block:: javascript + + // Some client of FooService called FooClient2 + + const { FooService } = ChromeUtils.import( + "resource://gre/modules/FooService.jsm" + ); + + FooService.shutdown.addBlocker( + "FooClient2: Collecting data, writing it to disk and shutting down", + () => Blocker.wait(), + () => Blocker.state + ); + + let Blocker = { + // This field contains information on the status of the blocker. + // It can be any JSON serializable object. + state: "Not started", + + async wait() { + // This method is called once FooService starts informing its clients that + // FooService wishes to shut down. + + // Update the state as we go. If the Barrier is used in conjunction with + // a Phase, this state will be reported as part of a crash report if FooClient fails + // to shutdown properly. + this.state = "Starting"; + + let data = await collectSomeData(); + this.state = "Data collection complete"; + + try { + await writeSomeDataToDisk(data); + this.state = "Data successfully written to disk"; + } catch (ex) { + this.state = "Writing data to disk failed, proceeding with shutdown: " + ex; + } + + await FooService.oneLastCall(); + this.state = "Ready"; + } + }; + + +Example 4: A service with both internal and external dependencies +----------------------------------------------------------------- + + .. code-block:: javascript + + // Module FooService2 + + let { AsyncShutdown } = ChromeUtils.importESModule( + "resource://gre/modules/AsyncShutdown.sys.mjs" + ); + let { PromiseUtils } = ChromeUtils.importESModule( + "resource://gre/modules/PromiseUtils.sys.mjs" + ); + + this.exports = ["FooService2"]; + + let shutdown = new AsyncShutdown.Barrier("FooService2: Waiting for clients before shutting down"); + + // Export the `client` capability, to let clients register shutdown blockers + FooService2.shutdown = shutdown.client; + + // A second barrier, used to avoid shutting down while any connections are open. + let connections = new AsyncShutdown.Barrier("FooService2: Waiting for all FooConnections to be closed before shutting down"); + + let isClosed = false; + + FooService2.openFooConnection = function(name) { + if (isClosed) { + throw new Error("FooService2 is closed"); + } + + let deferred = PromiseUtils.defer(); + connections.client.addBlocker("FooService2: Waiting for connection " + name + " to close", deferred.promise); + + // ... + + + return { + // ... + // Some FooConnection object. Presumably, it will have additional methods. + // ... + close: function() { + // ... + // Perform any operation necessary for closing + // ... + + // Don't hoard blockers. + connections.client.removeBlocker(deferred.promise); + + // The barrier MUST be lifted, even if removeBlocker has been called. + deferred.resolve(); + } + }; + }; + + + // This function should be triggered at some point during shutdown, generally + // as a client to another Barrier. Triggering this function is not covered + // in this snippet. + let onshutdown = async function() { + // Wait for all registered clients to have lifted the barrier. + // These clients may open instances of FooConnection if they need to. + await shutdown.wait(); + + // Now stop accepting any other connection request. + isClosed = true; + + // Wait for all instances of FooConnection to be closed. + await connections.wait(); + + // Now finish shutting down FooService2 + // ... + }); + +.. _AsyncShutdown_phases: + +Phases: Expressing dependencies towards phases of shutdown +========================================================== + +The shutdown of a process takes place by phase, such as: + +- ``profileBeforeChange`` (once this phase is complete, there is no guarantee that the process has access to a profile directory); +- ``webWorkersShutdown`` (once this phase is complete, JavaScript does not have access to workers anymore); +- ... + +Much as services, phases have clients. For instance, all users of web workers MUST have finished using their web workers before the end of phase ``webWorkersShutdown``. + +Module ``AsyncShutdown`` provides pre-defined barriers for a set of +well-known phases. Each of the barriers provided blocks the corresponding shutdown +phase until all clients have lifted their blockers. + +List of phases +-------------- + +``AsyncShutdown.profileChangeTeardown`` + + The client capability for clients wishing to block asynchronously + during observer notification "profile-change-teardown". + + +``AsyncShutdown.profileBeforeChange`` + + The client capability for clients wishing to block asynchronously + during observer notification "profile-change-teardown". Once the + barrier is resolved, clients other than Telemetry MUST NOT access + files in the profile directory and clients MUST NOT use Telemetry + anymore. + +``AsyncShutdown.sendTelemetry`` + + The client capability for clients wishing to block asynchronously + during observer notification "profile-before-change-telemetry". + Once the barrier is resolved, Telemetry must stop its operations. + +``AsyncShutdown.webWorkersShutdown`` + + The client capability for clients wishing to block asynchronously + during observer notification "web-workers-shutdown". Once the phase + is complete, clients MUST NOT use web workers. diff --git a/toolkit/modules/docs/FirstStartup.rst b/toolkit/modules/docs/FirstStartup.rst new file mode 100644 index 0000000000..6df9a8973e --- /dev/null +++ b/toolkit/modules/docs/FirstStartup.rst @@ -0,0 +1,72 @@ +.. _FirstStartup: + +============== +FirstStartup +============== + +``FirstStartup`` is a module which is invoked on application startup by the Windows Installer, +to initialize services before the first application window appears. + +This is useful for: + +- one-time performance tuning +- downloading critical data (hotfixes, experiments, etc) + +Blocking until the first Application window appears is important because the Installer +will show a progress bar until this happens. This gives a user experience of: + +1. User downloads and starts the Windows Stub Installer. +2. Progress bar advances while the application is downloaded and installed. +3. Installer invokes the application with ``--first-startup``. +4. Application window appears, and the installer window closes. + +Overall, the user experiences a very fast first-startup, with critical tasks that normally +would be deferred until after UI startup already complete. + +.. _FirstStartup Architecture: + +FirstStartup: Example use case +============================== + +An example use of the ``FirstStartup`` module is to invoke the Normandy client to download an experiment +that will be used to customize the first-run page that Firefox shows. + +In this example, the first-run page would be loaded experimentally based on an attribution code provided +by the Installer. The flow for this looks like: + +1. User clicks on download link containing an attribution (UTM) code(s). +2. The download page serves a custom Windows Stub Installer with the appropriate attribution code embedded. +3. The installer invokes Firefox with the `--first-startup` flag, which blocks the first window. +4. Normandy is run by ``FirstStartup`` and downloads a list of available experiments, or "recipes". +5. Recipes are evaluated and filtered based on local information, such as the OS platform and the attribution codes. +6. A recipe is found which matches the current attribution code, and appropriate data is made available to the first-run page. +7. ``FirstStartup`` completes and unblocks, which causes Firefox to show the first window and load the appropriate first-run data. + +List of phases +============== + +``FirstStartup.NOT_STARTED`` + + The ``FirstStartup`` module has not been initialized (the ``init()`` + function has not been called). This is the default state. + +``FirstStartup.IN_PROGRESS`` + + ``FirstStartup.init()`` has been called, and the event loop is + spinning. This state will persist until either all startup tasks + have finished, or time-out has been reached. + + The time-out defaults to 5 seconds, but is configurable via the + ``first-startup.timeout`` pref, which is specified in milliseconds. + +``FirstStartup.TIMED_OUT`` + + The time-out has been reached before startup tasks are complete. + +``FirstStartup.SUCCESS`` + + All startup tasks have completed successfully, and application startup may resume. + +``FirstStartup.UNSUPPORTED`` + + No startup tasks are supported, and `FirstStartup` exited. diff --git a/toolkit/modules/docs/Region.rst b/toolkit/modules/docs/Region.rst new file mode 100644 index 0000000000..1e95739f4a --- /dev/null +++ b/toolkit/modules/docs/Region.rst @@ -0,0 +1,72 @@ +.. _Region: + +====== +Region +====== + +Firefox monitors the users region in order to show relevant local +search engines and content. The region is tracked in 2 properties: + + * ``Region.current`` - The most recent location we detected for the user. + * ``Region.home`` - Where we consider the users home location. + +These are tracked separately as to avoid updating the users +experience repeatedly as they travel for example. In general +callers should use ``Region.home``. + +If the user is detected in a current region that is not there `home` region +for a continuous period (current 2 weeks) then their `home` region +will be updated. + +Testing +======= + +To set the users region for testing you can use ``Region._setHomeRegion("US", false)``, the second parameter ``notify`` +will send a notification that the region has changed and trigger a +reload of search engines and other content. + +Updating test_Region_geocoding.js data +-------------------------------------- + +The test data used in this test is generated by running the MLS geocoding +service locally: + +Follow the Ichnaea location development guide @ https://ichnaea.readthedocs.io/en/latest/local_dev.html. + +Make a list of test locations in a CSV format, for example: + +.. code-block:: shell + + 23.7818724,38.0531587 + 23.7728138,38.0572369 + 1.6780180,48.5973431 + 1.7034801,48.5979913 + 1.6978640,48.5919751 + +You can use the MLS raw data files to get a large sample @ https://location.services.mozilla.com/downloads + +Save a script to run the geocoding in ``ichnaea/ichnaea`` + +.. code-block:: python + + import geocode + geocoder = geocode.Geocoder() + + f = open("mls.csv", "r") + r = open("mls-lookup-results.csv", "a") + + for x in f: + [lat, long] = x.strip().split(",") + region = geocoder.region(lat, long) + r.write("%s\n" % region) + +Run the script + +.. code-block:: shell + + $ make shell + $ cd ichnaea + $ python run.py + +If you want to commit the new test data ~500 seems to be a reasonable +number of data points to test before running into issues with the test length. diff --git a/toolkit/modules/docs/index.rst b/toolkit/modules/docs/index.rst new file mode 100644 index 0000000000..221e73ad23 --- /dev/null +++ b/toolkit/modules/docs/index.rst @@ -0,0 +1,12 @@ +=============== +Toolkit Modules +=============== + +The ``/toolkit/modules`` directory contains a number of self-contained toolkit modules considered small enough that they do not deserve individual directories. + +.. toctree:: + :maxdepth: 1 + + AsyncShutdown + FirstStartup + Region |