summaryrefslogtreecommitdiffstats
path: root/dom/serviceworkers/ServiceWorkerShutdownBlocker.h
blob: a200325c5b83207dad5680b012249ced0c4ed526 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_dom_serviceworkershutdownblocker_h__
#define mozilla_dom_serviceworkershutdownblocker_h__

#include "nsCOMPtr.h"
#include "nsIAsyncShutdown.h"
#include "nsISupportsImpl.h"
#include "nsITimer.h"

#include "ServiceWorkerShutdownState.h"
#include "mozilla/InitializedOnce.h"
#include "mozilla/MozPromise.h"
#include "mozilla/NotNull.h"
#include "mozilla/HashTable.h"

namespace mozilla::dom {

class ServiceWorkerManager;

/**
 * Main thread only.
 *
 * A ServiceWorkerShutdownBlocker will "accept promises", and each of these
 * promises will be a "pending promise" while it hasn't settled. At some point,
 * `StopAcceptingPromises()` should be called and the state will change to "not
 * accepting promises" (this is a one way state transition). The shutdown phase
 * of the shutdown client the blocker is created with will be blocked until
 * there are no more pending promises.
 *
 * It doesn't matter whether the state changes to "not accepting promises"
 * before or during the associated shutdown phase.
 *
 * In beta/release builds there will be an additional timer that starts ticking
 * once both the shutdown phase has been reached and the state is "not accepting
 * promises". If when the timer expire there are still pending promises,
 * shutdown will be forcefully unblocked.
 */
class ServiceWorkerShutdownBlocker final : public nsIAsyncShutdownBlocker,
                                           public nsITimerCallback,
                                           public nsINamed {
 public:
  using Progress = ServiceWorkerShutdownState::Progress;
  static const uint32_t kInvalidShutdownStateId = 0;

  NS_DECL_ISUPPORTS
  NS_DECL_NSIASYNCSHUTDOWNBLOCKER
  NS_DECL_NSITIMERCALLBACK
  NS_DECL_NSINAMED

  /**
   * Returns the registered shutdown blocker if registration succeeded and
   * nullptr otherwise.
   */
  static already_AddRefed<ServiceWorkerShutdownBlocker> CreateAndRegisterOn(
      nsIAsyncShutdownClient& aShutdownBarrier,
      ServiceWorkerManager& aServiceWorkerManager);

  /**
   * Blocks shutdown until `aPromise` settles.
   *
   * Can be called multiple times, and shutdown will be blocked until all the
   * calls' promises settle, but all of these calls must happen before
   * `StopAcceptingPromises()` is called (assertions will enforce this).
   *
   * See `CreateShutdownState` for aShutdownStateId, which is needed to clear
   * the shutdown state if the shutdown process aborts for some reason.
   */
  void WaitOnPromise(GenericNonExclusivePromise* aPromise,
                     uint32_t aShutdownStateId);

  /**
   * Once this is called, shutdown will be blocked until all promises
   * passed to `WaitOnPromise()` settle, and there must be no more calls to
   * `WaitOnPromise()` (assertions will enforce this).
   */
  void StopAcceptingPromises();

  /**
   * Start tracking the shutdown of an individual ServiceWorker for hang
   * reporting purposes. Returns a "shutdown state ID" that should be used
   * in subsequent calls to ReportShutdownProgress. The shutdown of an
   * individual ServiceWorker is presumed to be completed when its `Progress`
   * reaches `Progress::ShutdownCompleted`.
   */
  uint32_t CreateShutdownState();

  void ReportShutdownProgress(uint32_t aShutdownStateId, Progress aProgress);

 private:
  explicit ServiceWorkerShutdownBlocker(
      ServiceWorkerManager& aServiceWorkerManager);

  ~ServiceWorkerShutdownBlocker();

  /**
   * No-op if any of the following are true:
   * 1) `BlockShutdown()` hasn't been called yet, or
   * 2) `StopAcceptingPromises()` hasn't been called yet, or
   * 3) `StopAcceptingPromises()` HAS been called, but there are still pending
   *    promises.
   */
  void MaybeUnblockShutdown();

  /**
   * Requires `BlockShutdown()` to have been called.
   */
  void UnblockShutdown();

  /**
   * Returns the remaining pending promise count (i.e. excluding the promise
   * that just settled).
   */
  uint32_t PromiseSettled();

  bool IsAcceptingPromises() const;

  uint32_t GetPendingPromises() const;

  /**
   * Initializes a timer that will unblock shutdown unconditionally once it's
   * expired (even if there are still pending promises). No-op if:
   * 1) not a beta or release build, or
   * 2) shutdown is not being blocked or `StopAcceptingPromises()` has not been
   *    called.
   */
  void MaybeInitUnblockShutdownTimer();

  struct AcceptingPromises {
    uint32_t mPendingPromises = 0;
  };

  struct NotAcceptingPromises {
    explicit NotAcceptingPromises(AcceptingPromises aPreviousState);

    uint32_t mPendingPromises = 0;
  };

  Variant<AcceptingPromises, NotAcceptingPromises> mState;

  nsCOMPtr<nsIAsyncShutdownClient> mShutdownClient;

  HashMap<uint32_t, ServiceWorkerShutdownState> mShutdownStates;

  nsCOMPtr<nsITimer> mTimer;
  LazyInitializedOnceEarlyDestructible<
      const NotNull<RefPtr<ServiceWorkerManager>>>
      mServiceWorkerManager;
};

}  // namespace mozilla::dom

#endif  // mozilla_dom_serviceworkershutdownblocker_h__