Bug 1363345 - Don't use the pingsender when the OS is shutting down. r=gfritzsche,data-review=bsmedberg draft
authorAlessio Placitelli <alessio.placitelli@gmail.com>
Fri, 12 May 2017 12:23:07 +0200
changeset 582498 3bef34d4a18e7bd9e9b9cedbc06d76a82a9df2e4
parent 582482 f9ca97a334296facd2e0ea5582e7f12d0fe70fe4
child 629802 953a2a37c1bae6d68d79114d93695188296587be
push id60117
push useralessio.placitelli@gmail.com
push dateMon, 22 May 2017 19:14:02 +0000
reviewersgfritzsche
bugs1363345
milestone55.0a1
Bug 1363345 - Don't use the pingsender when the OS is shutting down. r=gfritzsche,data-review=bsmedberg MozReview-Commit-ID: 9d8paU8M1T7
toolkit/components/telemetry/Scalars.yaml
toolkit/components/telemetry/TelemetrySend.jsm
toolkit/components/telemetry/docs/data/main-ping.rst
toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -262,16 +262,207 @@ storage.sync.api.usage:
     kind: uint
     keyed: true
     notification_emails:
       - eglassercamp@mozilla.com
     release_channel_collection: opt-out
     record_in_processes:
       - main
 
+# The following section contains WebRTC nICEr scalars
+# For more info on ICE, see https://tools.ietf.org/html/rfc5245
+# For more info on STUN, see https://tools.ietf.org/html/rfc5389
+# For more info on TURN, see https://tools.ietf.org/html/rfc5766
+webrtc.nicer:
+  stun_retransmits:
+    bug_numbers:
+      - 1325536
+    description: >
+      The count of STUN message retransmissions during a WebRTC call.
+      When sending STUN request messages over UDP, messages may be
+      dropped by the network. Retransmissions are the mechanism used to
+      accomplish reliability of the STUN request/response transaction.
+      This can happen during both successful and unsuccessful WebRTC
+      calls.
+      For more info on ICE, see https://tools.ietf.org/html/rfc5245
+      For more info on STUN, see https://tools.ietf.org/html/rfc5389
+      For more info on TURN, see https://tools.ietf.org/html/rfc5766
+    expires: "57"
+    kind: uint
+    notification_emails:
+      - webrtc-ice-telemetry-alerts@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes:
+      - 'main'
+      - 'content'
+
+  turn_401s:
+    bug_numbers:
+      - 1325536
+    description: >
+      The count of TURN 401 (Unauthorized) responses to allocation
+      requests. Only 401 responses beyond the first, expected 401 are
+      counted. More than one 401 repsonse indicates the client is
+      experiencing difficulty authenticating with the TURN server. This
+      can happen during both successful and unsuccessful WebRTC calls.
+      For more info on ICE, see https://tools.ietf.org/html/rfc5245
+      For more info on STUN, see https://tools.ietf.org/html/rfc5389
+      For more info on TURN, see https://tools.ietf.org/html/rfc5766
+    expires: "57"
+    kind: uint
+    notification_emails:
+      - webrtc-ice-telemetry-alerts@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes:
+      - 'main'
+      - 'content'
+
+  turn_403s:
+    bug_numbers:
+      - 1325536
+    description: >
+      The count of TURN 403 (Forbidden) responses to CreatePermission or
+      ChannelBind requests.  This indicates that the TURN server is
+      refusing the request for an IP address or IP address/port
+      combination, likely due to administrative restrictions.
+      For more info on ICE, see https://tools.ietf.org/html/rfc5245
+      For more info on STUN, see https://tools.ietf.org/html/rfc5389
+      For more info on TURN, see https://tools.ietf.org/html/rfc5766
+    expires: "57"
+    kind: uint
+    notification_emails:
+      - webrtc-ice-telemetry-alerts@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes:
+      - 'main'
+      - 'content'
+
+  turn_438s:
+    bug_numbers:
+      - 1325536
+    description: >
+      The count of TURN 438 (Stale Nonce) responses to allocation
+      requests. This can happen during both successful and unsuccessful
+      WebRTC calls.
+      For more info on ICE, see https://tools.ietf.org/html/rfc5245
+      For more info on STUN, see https://tools.ietf.org/html/rfc5389
+      For more info on TURN, see https://tools.ietf.org/html/rfc5766
+    expires: "57"
+    kind: uint
+    notification_emails:
+      - webrtc-ice-telemetry-alerts@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes:
+      - 'main'
+      - 'content'
+
+# The following section contains content process base counters.
+dom.contentprocess:
+  troubled_due_to_memory:
+    bug_numbers:
+      - 1305091
+    description: >
+      The number of content processes that were marked as troubled because
+      it was running low on virtual memory.
+    expires: "58"
+    kind: uint
+    notification_emails:
+      - benjamin@smedbergs.us
+      - mconley@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes:
+      - 'main'
+
+devtools.toolbar.eyedropper:
+  opened:
+    bug_numbers:
+      - 1247985
+      - 1352115
+    description: Number of times the DevTools Eyedropper has been opened via the inspector toolbar.
+    expires: never
+    kind: uint
+    notification_emails:
+      - dev-developer-tools@lists.mozilla.org
+    release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
+
+devtools.copy.unique.css.selector:
+  opened:
+    bug_numbers:
+      - 1323700
+      - 1352115
+    description: Number of times the DevTools copy unique CSS selector has been used.
+    expires: "57"
+    kind: uint
+    notification_emails:
+      - dev-developer-tools@lists.mozilla.org
+    release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
+
+devtools.copy.full.css.selector:
+  opened:
+    bug_numbers:
+      - 1323700
+      - 1352115
+    description: Number of times the DevTools copy full CSS selector has been used.
+    expires: "57"
+    kind: uint
+    notification_emails:
+      - dev-developer-tools@lists.mozilla.org
+    release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
+
+navigator.storage:
+  estimate_count:
+    bug_numbers:
+      - 1359708
+    description: >
+      Number of times navigator.storage.estimate has been used.
+    expires: "60"
+    kind: uint
+    notification_emails:
+      - shuang@mozilla.com
+      - ttung@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes:
+      - 'main'
+      - 'content'
+
+  persist_count:
+    bug_numbers:
+      - 1359708
+    description: >
+      Number of times navigator.storage.persist has been used.
+    expires: "60"
+    kind: uint
+    notification_emails:
+      - shuang@mozilla.com
+      - ttung@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes:
+      - 'main'
+      - 'content'
+
+telemetry:
+  os_shutting_down:
+    bug_numbers:
+      - 1363345
+    description: >
+      Records true if there is a signal that Firefox was quitting because
+      the OS was shutting down. Only available on Windows.
+    expires: "58"
+    kind: boolean
+    notification_emails:
+      - telemetry-client-dev@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
 
 # The following section is for probes testing the Telemetry system. They will not be
 # submitted in pings and are only used for testing.
 telemetry.test:
   unsigned_int_kind:
     bug_numbers:
       - 1276190
     description: >
@@ -443,184 +634,11 @@ telemetry.test:
     description: A testing string scalar; not meant to be touched.
     expires: never
     kind: string
     notification_emails:
       - telemetry-client-dev@mozilla.com
     record_in_processes:
       - 'all_childs'
 
-# The following section contains WebRTC nICEr scalars
-# For more info on ICE, see https://tools.ietf.org/html/rfc5245
-# For more info on STUN, see https://tools.ietf.org/html/rfc5389
-# For more info on TURN, see https://tools.ietf.org/html/rfc5766
-webrtc.nicer:
-  stun_retransmits:
-    bug_numbers:
-      - 1325536
-    description: >
-      The count of STUN message retransmissions during a WebRTC call.
-      When sending STUN request messages over UDP, messages may be
-      dropped by the network. Retransmissions are the mechanism used to
-      accomplish reliability of the STUN request/response transaction.
-      This can happen during both successful and unsuccessful WebRTC
-      calls.
-      For more info on ICE, see https://tools.ietf.org/html/rfc5245
-      For more info on STUN, see https://tools.ietf.org/html/rfc5389
-      For more info on TURN, see https://tools.ietf.org/html/rfc5766
-    expires: "57"
-    kind: uint
-    notification_emails:
-      - webrtc-ice-telemetry-alerts@mozilla.com
-    release_channel_collection: opt-in
-    record_in_processes:
-      - 'main'
-      - 'content'
-
-  turn_401s:
-    bug_numbers:
-      - 1325536
-    description: >
-      The count of TURN 401 (Unauthorized) responses to allocation
-      requests. Only 401 responses beyond the first, expected 401 are
-      counted. More than one 401 repsonse indicates the client is
-      experiencing difficulty authenticating with the TURN server. This
-      can happen during both successful and unsuccessful WebRTC calls.
-      For more info on ICE, see https://tools.ietf.org/html/rfc5245
-      For more info on STUN, see https://tools.ietf.org/html/rfc5389
-      For more info on TURN, see https://tools.ietf.org/html/rfc5766
-    expires: "57"
-    kind: uint
-    notification_emails:
-      - webrtc-ice-telemetry-alerts@mozilla.com
-    release_channel_collection: opt-in
-    record_in_processes:
-      - 'main'
-      - 'content'
-
-  turn_403s:
-    bug_numbers:
-      - 1325536
-    description: >
-      The count of TURN 403 (Forbidden) responses to CreatePermission or
-      ChannelBind requests.  This indicates that the TURN server is
-      refusing the request for an IP address or IP address/port
-      combination, likely due to administrative restrictions.
-      For more info on ICE, see https://tools.ietf.org/html/rfc5245
-      For more info on STUN, see https://tools.ietf.org/html/rfc5389
-      For more info on TURN, see https://tools.ietf.org/html/rfc5766
-    expires: "57"
-    kind: uint
-    notification_emails:
-      - webrtc-ice-telemetry-alerts@mozilla.com
-    release_channel_collection: opt-in
-    record_in_processes:
-      - 'main'
-      - 'content'
-
-  turn_438s:
-    bug_numbers:
-      - 1325536
-    description: >
-      The count of TURN 438 (Stale Nonce) responses to allocation
-      requests. This can happen during both successful and unsuccessful
-      WebRTC calls.
-      For more info on ICE, see https://tools.ietf.org/html/rfc5245
-      For more info on STUN, see https://tools.ietf.org/html/rfc5389
-      For more info on TURN, see https://tools.ietf.org/html/rfc5766
-    expires: "57"
-    kind: uint
-    notification_emails:
-      - webrtc-ice-telemetry-alerts@mozilla.com
-    release_channel_collection: opt-in
-    record_in_processes:
-      - 'main'
-      - 'content'
-
-# The following section contains content process base counters.
-dom.contentprocess:
-  troubled_due_to_memory:
-    bug_numbers:
-      - 1305091
-    description: >
-      The number of content processes that were marked as troubled because
-      it was running low on virtual memory.
-    expires: "58"
-    kind: uint
-    notification_emails:
-      - benjamin@smedbergs.us
-      - mconley@mozilla.com
-    release_channel_collection: opt-in
-    record_in_processes:
-      - 'main'
-
-devtools.toolbar.eyedropper:
-  opened:
-    bug_numbers:
-      - 1247985
-      - 1352115
-    description: Number of times the DevTools Eyedropper has been opened via the inspector toolbar.
-    expires: never
-    kind: uint
-    notification_emails:
-      - dev-developer-tools@lists.mozilla.org
-    release_channel_collection: opt-out
-    record_in_processes:
-      - 'main'
-
-devtools.copy.unique.css.selector:
-  opened:
-    bug_numbers:
-      - 1323700
-      - 1352115
-    description: Number of times the DevTools copy unique CSS selector has been used.
-    expires: "57"
-    kind: uint
-    notification_emails:
-      - dev-developer-tools@lists.mozilla.org
-    release_channel_collection: opt-out
-    record_in_processes:
-      - 'main'
-
-devtools.copy.full.css.selector:
-  opened:
-    bug_numbers:
-      - 1323700
-      - 1352115
-    description: Number of times the DevTools copy full CSS selector has been used.
-    expires: "57"
-    kind: uint
-    notification_emails:
-      - dev-developer-tools@lists.mozilla.org
-    release_channel_collection: opt-out
-    record_in_processes:
-      - 'main'
-
-navigator.storage:
-  estimate_count:
-    bug_numbers:
-      - 1359708
-    description: >
-      Number of times navigator.storage.estimate has been used.
-    expires: "60"
-    kind: uint
-    notification_emails:
-      - shuang@mozilla.com
-      - ttung@mozilla.com
-    release_channel_collection: opt-in
-    record_in_processes:
-      - 'main'
-      - 'content'
-
-  persist_count:
-    bug_numbers:
-      - 1359708
-    description: >
-      Number of times navigator.storage.persist has been used.
-    expires: "60"
-    kind: uint
-    notification_emails:
-      - shuang@mozilla.com
-      - ttung@mozilla.com
-    release_channel_collection: opt-in
-    record_in_processes:
-      - 'main'
-      - 'content'
+# NOTE: Please don't add new definitions below this point. Consider adding
+# them earlier in the file and leave the telemetry.test group as the last
+# one for readability.
--- a/toolkit/components/telemetry/TelemetrySend.jsm
+++ b/toolkit/components/telemetry/TelemetrySend.jsm
@@ -50,17 +50,20 @@ const LOGGER_PREFIX = "TelemetrySend::";
 const PREF_BRANCH = "toolkit.telemetry.";
 const PREF_SERVER = PREF_BRANCH + "server";
 const PREF_UNIFIED = PREF_BRANCH + "unified";
 const PREF_ENABLED = PREF_BRANCH + "enabled";
 const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
 const PREF_OVERRIDE_OFFICIAL_CHECK = PREF_BRANCH + "send.overrideOfficialCheck";
 
 const TOPIC_IDLE_DAILY = "idle-daily";
-const TOPIC_QUIT_APPLICATION = "quit-application";
+// The following topics are notified when Firefox is closing
+// because the OS is shutting down.
+const TOPIC_QUIT_APPLICATION_GRANTED = "quit-application-granted";
+const TOPIC_QUIT_APPLICATION_FORCED = "quit-application-forced";
 
 // Whether the FHR/Telemetry unification features are enabled.
 // Changing this pref requires a restart.
 const IS_UNIFIED_TELEMETRY = Preferences.get(PREF_UNIFIED, false);
 
 const PING_FORMAT_VERSION = 4;
 
 const MS_IN_A_MINUTE = 60 * 1000;
@@ -581,22 +584,25 @@ var TelemetrySendImpl = {
   // This tracks all pending ping requests to the server.
   _pendingPingRequests: new Map(),
   // This tracks all the pending async ping activity.
   _pendingPingActivity: new Set(),
   // This is true when running in the test infrastructure.
   _testMode: false,
   // This holds pings that we currently try and haven't persisted yet.
   _currentPings: new Map(),
-
+  // Used to skip spawning the pingsender if OS is shutting down.
+  _isOSShutdown: false,
   // Count of pending pings that were overdue.
   _overduePingCount: 0,
 
   OBSERVER_TOPICS: [
     TOPIC_IDLE_DAILY,
+    TOPIC_QUIT_APPLICATION_GRANTED,
+    TOPIC_QUIT_APPLICATION_FORCED,
   ],
 
   OBSERVED_PREFERENCES: [
     PREF_ENABLED,
     PREF_FHR_UPLOAD_ENABLED,
   ],
 
   // Whether sending pings has been overridden.
@@ -625,16 +631,21 @@ var TelemetrySendImpl = {
   },
 
   setTestModeEnabled(testing) {
     this._testMode = testing;
   },
 
   earlyInit() {
     this._annotateCrashReport();
+
+    // Install the observer to detect OS shutdown early enough, so
+    // that we catch this before the delayed setup happens.
+    Services.obs.addObserver(this, TOPIC_QUIT_APPLICATION_FORCED);
+    Services.obs.addObserver(this, TOPIC_QUIT_APPLICATION_GRANTED);
   },
 
   async setup(testing) {
     this._log.trace("setup");
 
     this._testMode = testing;
     this._sendingEnabled = true;
 
@@ -756,16 +767,17 @@ var TelemetrySendImpl = {
   },
 
   reset() {
     this._log.trace("reset");
 
     this._shutdown = false;
     this._currentPings = new Map();
     this._overduePingCount = 0;
+    this._isOSShutdown = false;
 
     const histograms = [
       "TELEMETRY_SUCCESS",
       "TELEMETRY_SEND_SUCCESS",
       "TELEMETRY_SEND_FAILURE",
     ];
 
     histograms.forEach(h => Telemetry.getHistogramById(h).clear());
@@ -781,20 +793,34 @@ var TelemetrySendImpl = {
     // crash reporter that it can send crash pings if appropriate.
     SendScheduler.triggerSendingPings(true);
     this._annotateCrashReport();
 
     return this.promisePendingPingActivity();
   },
 
   observe(subject, topic, data) {
+    let setOSShutdown = () => {
+      this._log.trace("setOSShutdown - in OS shutdown");
+      this._isOSShutdown = true;
+      Telemetry.scalarSet("telemetry.os_shutting_down", true);
+    };
+
     switch (topic) {
     case TOPIC_IDLE_DAILY:
       SendScheduler.triggerSendingPings(true);
       break;
+    case TOPIC_QUIT_APPLICATION_FORCED:
+      setOSShutdown();
+      break;
+    case TOPIC_QUIT_APPLICATION_GRANTED:
+      if (data == "syncShutdown") {
+        setOSShutdown();
+      }
+      break;
     }
   },
 
   /**
    * Spawn the PingSender process that sends a ping. This function does
    * not return an error or throw, it only logs an error.
    *
    * Even if the function doesn't fail, it doesn't mean that the ping was
@@ -821,17 +847,24 @@ var TelemetrySendImpl = {
     if (!this.sendingEnabled(ping)) {
       this._log.trace("submitPing - Telemetry is not allowed to send pings.");
       return Promise.resolve();
     }
 
     // Send the ping using the PingSender, if requested and the user was
     // notified of our policy. We don't support the pingsender on Android,
     // so ignore this option on that platform (see bug 1335917).
+    // Moreover, if the OS is shutting down, we don't want to spawn the
+    // pingsender as it could unnecessarily slow down OS shutdown.
+    // Additionally, it could be be killed before it can complete its tasks,
+    // for example after successfully sending the ping but before removing
+    // the copy from the disk, resulting in receiving duplicate pings when
+    // Firefox restarts.
     if (options.usePingSender &&
+        !this._isOSShutdown &&
         TelemetryReportingPolicy.canUpload() &&
         AppConstants.platform != "android") {
       const url = this._buildSubmissionURL(ping);
       // Serialize the ping to the disk and then spawn the PingSender.
       return savePing(ping).then(() => this._sendWithPingSender(ping.id, url));
     }
 
     if (!this.canSendNow) {
--- a/toolkit/components/telemetry/docs/data/main-ping.rst
+++ b/toolkit/components/telemetry/docs/data/main-ping.rst
@@ -7,17 +7,17 @@
 
 This is the "main" Telemetry ping type, whose payload contains most of the measurements that are used to track the performance and health of Firefox in the wild.
 It includes the histograms and other performance and diagnostic data.
 
 This ping is triggered by different scenarios, which is documented by the ``reason`` field:
 
 * ``aborted-session`` - this ping is regularly saved to disk (every 5 minutes), overwriting itself, and deleted at shutdown. If a previous aborted session ping is found at startup, it gets sent to the server. The first aborted-session ping is generated as soon as Telemetry starts
 * ``environment-change`` - the :doc:`environment` changed, so the session measurements got reset and a new subsession starts
-* ``shutdown`` - triggered when the browser session ends. For the first browsing session, this ping is saved to disk and sent on the next browser restart. From the second browsing session on, this ping is sent immediately on shutdown using the :doc:`../internals/pingsender`
+* ``shutdown`` - triggered when the browser session ends. For the first browsing session, this ping is saved to disk and sent on the next browser restart. From the second browsing session on, this ping is sent immediately on shutdown using the :doc:`../internals/pingsender`, unless the OS is shutting down
 * ``daily`` - a session split triggered in 24h hour intervals at local midnight. If an ``environment-change`` ping is generated by the time it should be sent, the daily ping is rescheduled for the next midnight
 * ``saved-session`` - the *"classic"* Telemetry payload with measurements covering the whole browser session (only submitted for a transition period)
 
 Most reasons lead to a session split, initiating a new *subsession*. We reset important measurements for those subsessions.
 
 After a new subsession split, the ``internal-telemetry-after-subsession-split`` topic is notified to all the observers. *This is an internal topic and is only meant for internal Telemetry usage.*
 
 .. note::
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
@@ -1360,45 +1360,89 @@ add_task(async function test_sendShutdow
       (AppConstants.platform == "linux" && OS.Constants.Sys.bits == 32)) {
     // We don't support the pingsender on Android, yet, see bug 1335917.
     // We also don't suppor the pingsender testing on Treeherder for
     // Linux 32 bit (due to missing libraries). So skip it there too.
     // See bug 1310703 comment 78.
     return;
   }
 
+  const OSSHUTDOWN_SCALAR = "telemetry.os_shutting_down";
+
+  let checkPendingShutdownPing = async function() {
+    let pendingPings = await TelemetryStorage.loadPendingPingList();
+    Assert.equal(pendingPings.length, 2,
+                 "We expect 2 pending pings: shutdown and saved-session.");
+    // Load the pings off the disk.
+    const pings = [
+      await TelemetryStorage.loadPendingPing(pendingPings[0].id),
+      await TelemetryStorage.loadPendingPing(pendingPings[1].id)
+    ];
+    // Find the shutdown main ping and check that it contains the right data.
+    const shutdownPing = pings.find(p => p.type == "main");
+    Assert.ok(shutdownPing, "The 'shutdown' ping must be saved to disk.");
+    Assert.ok(pings.find(p => p.type == "saved-session"),
+              "The 'saved-session' ping must be saved to disk.");
+    Assert.equal("shutdown", shutdownPing.payload.info.reason,
+                 "The 'shutdown' ping must be saved to disk.");
+    Assert.ok(shutdownPing.payload.processes.parent.scalars[OSSHUTDOWN_SCALAR],
+              "The OS shutdown scalar must be set to true.");
+  };
+
   Preferences.set(PREF_SHUTDOWN_PINGSENDER, true);
   Preferences.set(PREF_POLICY_FIRSTRUN, false);
   // Make sure the reporting policy picks up the updated pref.
   TelemetryReportingPolicy.testUpdateFirstRun();
   PingServer.clearRequests();
+  Telemetry.clearScalars();
 
   // Shutdown telemetry and wait for an incoming ping.
   let nextPing = PingServer.promiseNextPing();
   await TelemetryController.testShutdown();
   const ping = await nextPing;
 
   // Check that we received a shutdown ping.
   checkPingFormat(ping, ping.type, true, true);
   Assert.equal(ping.payload.info.reason, REASON_SHUTDOWN);
   Assert.equal(ping.clientId, gClientID);
-
+  Assert.ok(!(OSSHUTDOWN_SCALAR in ping.payload.processes.parent.scalars),
+            "The OS shutdown scalar must not be set.");
   // Try again, this time disable ping upload. The PingSender
   // should not be sending any ping!
   PingServer.registerPingHandler(() => Assert.ok(false, "Telemetry must not send pings if not allowed to."));
   Preferences.set(PREF_FHR_UPLOAD_ENABLED, false);
   await TelemetryController.testReset();
   await TelemetryController.testShutdown();
 
   // Make sure we have no pending pings between the runs.
   await TelemetryStorage.testClearPendingPings();
 
-  // Enable ping upload and disable the "submission policy".
-  // The shutdown ping must not be sent.
+  // Enable ping upload and signal an OS shutdown. The pingsender
+  // will not be spawned and no ping will be sent.
   Preferences.set(PREF_FHR_UPLOAD_ENABLED, true);
+  await TelemetryController.testReset();
+  Services.obs.notifyObservers(null, "quit-application-forced");
+  await TelemetryController.testShutdown();
+
+  // Check that the "shutdown" ping was correctly saved to disk.
+  await checkPendingShutdownPing();
+
+  // Make sure we have no pending pings between the runs.
+  await TelemetryStorage.testClearPendingPings();
+  Telemetry.clearScalars();
+
+  await TelemetryController.testReset();
+  Services.obs.notifyObservers(null, "quit-application-granted", "syncShutdown");
+  await TelemetryController.testShutdown();
+  await checkPendingShutdownPing();
+
+  // Make sure we have no pending pings between the runs.
+  await TelemetryStorage.testClearPendingPings();
+
+  // Disable the "submission policy". The shutdown ping must not be sent.
   Preferences.set(PREF_BYPASS_NOTIFICATION, false);
   await TelemetryController.testReset();
   await TelemetryController.testShutdown();
 
   // Make sure we have no pending pings between the runs.
   await TelemetryStorage.testClearPendingPings();
 
   // We cannot reset PREF_BYPASS_NOTIFICATION, as we need it to be