Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / docs / workersAndStorage / WorkerLifeCycleAndWorkerRefs.md
blobb20f7733b622eb8e89401d6a3ee92e4e242a0acf
1 # Worker’s Life-Cycle and WorkerRefs
3 Worker, as a thread programming model, is introduced to the Web world to use the computing power efficiently in the Web world. Just like the regular thread programming, Worker can be created and deleted anytime when needed.
5 Since Worker can be deleted anytime, when developing APIs on the Workers should be careful when handling the shutdown behavior. Otherwise, memory problems, UAF, memory leaking, or shutdown hang, would not be a surprise. In addition, debugging these issues on Workers sometimes is not easy. The crash stack might not provide very useful information. The bug sometimes needs a special sequence to reproduce since it could be a thread interleaving problem. To avoid getting into these troubles, keeping the Worker’s life cycle and how to play with WorkerRefs in mind would be very helpful.
8 ## Worker Life-Cycle
10 The worker’s life cycle is maintained by a status machine in the WorkerPrivate class. A Worker could be in following status
12 - Pending
13 - Running
14 - Closing
15 - Canceling
16 - Killing
17 - Dead
19 Following we briefly describe what is done for each status.
22 ### Pending:
24 This is the initial status of a Worker.
26 Worker’s initialization is done in this status in the parent(main or the parent worker) and worker thread.
28 Worker’s initialization starts from its parent thread, which includes
30 1. Get WorkerLoadInfo from parent window/worker
31 2. Create a WorkerPrivate for the Worker
32 3. Register the Worker in the RuntimeService object
33 4. Initialize a thread(worker thread) for Worker, and dispatch a WorkerThreadPrimaryRunnable on the worker thread
34 5. Connect debugger
35 6. Dispatch CompileScriptRunnable to the worker thread
37 Before the Worker thread starts running runnables, a Worker could have already been exposed to its parent window/worker. So the parent window/worker can send messages to the worker through the postMessage() method. If the Worker is not in the “Running” status yet, these runnables would be kept in WorkerPrivate::mPreStartRunnables.
39 When WorkerThreadPrimaryRunnable starts executing on the worker thread, it continues the initialization on the worker thread, which includes
41 1. Build the connection between WorkerPrivate and the worker thread. Then moving the WorkerPrivate::mPreStartRunnables to the worker thread’s event queue.
42 2. Initialize the PerformanceStorage for the Worker.
43 3. Start the Cycle-Collector for the Worker.
44 4. Initialize the JS context for the Worker.
45 5. Call WorkerPrivate::DoRunLoop() to consume the Runnables in the worker thread’s event queue.
48 ### Running:
50 This is the status which the Worker starts to execute runnables on the worker thread.
52 Once the Worker gets into “Running”,
54 1. Enable the memory reporter
55 2. Start the GC timer.
57 “Running” is the status where we play with the Worker. At this time point, we can
59 1. Create WorkerRefs to get the Worker shutdown notifications and run shutdown cleanup jobs through the registered callback.
60 2. Create sync-eventLoop to make the worker thread to wait for another thread's execution.
61 3. Dispatching events to WorkerGlobalScope to trigger event callbacks defined in the script.
63 We will talk about WorkerRef, Sync-EventLoop in detail later.
66 ### Closing:
68 This is a special status for DedicatedWorker and SharedWorker when DedicateWorkerGlobalScope.close()/SharedWorkerGlobalScope.close() is called.
70 When Worker enters into the “Closing” status,
72 1. Cancel all Timeouts/TimeIntervals of the Worker.
73 2. Do not allow BroadcastChannel.postMessage() on the WorkerGlobalScope.
75 Worker will keep in the “Closing” status until all sync-eventLoops of the Worker are closed.
78 ### Canceling:
80 When Worker gets into the “Canceling” status, it starts the Worker shutdown steps.
82 1. Set the WorkerGlobalScope(nsIGlobalObject) as dying.
84 This means the event will not be dispatched to the WorkerGlobalScope and the callbacks of the pending dispatched event will not be executed.
86 2. Cancel all Timeouts/TimeIntervals of the Worker.
87 3. Notify WorkerRef holders and children Workers.
89 So the WorkerRef holders and children Workers will start the shutdown jobs
91 4. Abort the script immediately.
93 Once all sync-eventLoops are closed,
95 1. Disconnect the EventTarget/WebTaskScheduler of the WorkerGlobalScope
98 ### Killing:
100 This is the status that starts to destroy the Worker
102 1. Shutdown the GC Timer
103 2. Disable the memory reporter
104 3. Switch the status to “Dead”
105 4. Cancel and release the remaining WorkerControlRunnables
106 5. Exit the WorkerPrivate::DoRunLoop()
109 ### Dead:
111 The Worker quits the main event loop, it continues the shutdown process
113 1. Release the remaining WorkerDebuggerRunnables
115 2. Unroot the WorkerGlobalScope and WorkerDebugGlobalScope
117    1. Trigger GC to release GlobalScopes
119 3. Shutdown the Cycle-Collector for Worker
121 4. Dispatch TopLevelWorkerFinishRunnable/WorkerFinishRunnable to the parent thread
123    1. Disable/Disconnect the WorkerDebugger
124    2. Unregister the Worker in the RuntimeService object
125    3. Release WorkerPrivate::mSelf and WorkerPrivate::mParentEventTargetRef
127 The WorkerPrivate is supposed to be released after its self-reference is nullified.
129 5. Dispatch FinishedRunnable to the main thread to release the worker thread.
132 ### How to shutdown a Worker
134 Normally, there are four situations making a Worker get into shutdown.
136 1. Worker is GC/CCed.
138    1. Navigating to another page.
139    2. Worker is idle for a while. (Notice that idle is not a status of Worker, it is a condition in “Running” status)
141 2. self.close() is called in the worker's script.
143 3. Worker.terminate() is called in its parent’s script.
145 4. Firefox shutdown.
148 ### Worker Status Flowchart
150 ![Worker Status Flowchart](./WorkerStatusFlowchart.svg)
152 This flowchart shows how the status of a Worker is changing.
154 When the WorkerThreadPrimaryRunnable calls WorkerPrivate::DoRunLoop on the worker thread, the status changes from “Pending” to “Running.” If Firefox shutdown happens before entering into “Running,” the status directly changes from “Pending” to “Dead.”
156 When a Worker is in “Running,” status changing must be caused by requesting a Worker shutdown. The status switches to “Closing,” for the special case that worker’s script calls self.close(). Otherwise, the status switches to “Canceling.” And a “Closing” Worker will switch to “Canceling” when all sync-eventLoops are completed.
158 A “Canceling” Worker switches its status to “Killing” when following requirements are fulfilled.
160 1. No WorkerRefs, no children Workers, no Timeouts, and no sync-eventLoops
161 2. No pending runnable for the worker thread main event queue, control runnables and debugger runnables
163 The status switches from “Killing” to “Dead” automatically.
166 ## WorkerRefs
168 Since a Worker’s shutdown can happen at any time, knowing when the shutdown starts is important for development, especially for releasing the resources and completing the operation in the Worker shutdown phase. Therefore, WorkerRefs is introduced to get the notification of the Worker’s shutdown. When a Worker enters the “Canceling” status, it notifies the corresponding WorkerRefs to execute the registered callback on the worker thread. The WorkerRefs holder completes its shutdown steps synchronously or asynchronously in the registered callback and then releases the WorkerRef.
170 According to the following requirements, four types of WorkerRefs are introduced.
172 - Should the WorkerRef block the Worker's shutdown
173 - Should the WorkerRef block cycle-collection on the Worker
174 - Should the WorkerRef need to be held on other threads.
177 ### WeakWorkerRef
179 WeakWorkerRef, as its name, is a “Weak” reference since WeakWorkerRef releases the internal reference to the Worker immediately after WeakWorkerRef’s registered callback execution completes. Therefore, WeakWorkerRef does not block the Worker’s shutdown. In addition, holding a WeakWorkerRef would not block GC/CC the Worker. This means a Worker will be considered to be cycle-collected even if there are WeakWorkerRefs to the Worker.
181 WeakWorkerRef is ref-counted, but not thread-safe.
183 WeakWorkerRef is designed for just getting the Worker’s shutdown notification and completing shutdown steps synchronously.
186 ### StrongWorkerRef
188 Unlike WeakWorkerRef, StrongWorkerRef does not release its internal reference to the Worker after the callback execution. StrongWorkerRef’s internal reference is released when the StrongWorkerRef gets destroyed. That means StrongWorkerRef allows its holder to determine when to release the Worker by nulling the StrongWorkerRef. This also makes StrongWorkerRef's holder block the Worker's shutdown.
190 When using the StrongWorkerRef, resource cleanup might involve multiple threads and asynchronous behavior. StrongWorkerRef release timing becomes crucial not to cause memory problems, such as UAF or leaking. StrongWorkerRef must be released. Otherwise, a shutdown hang would not be a surprise.
192 StrongWorkerRef also blocks the GC/CC a Worker. Once there is a StrongWorkerRef to the Worker, GC/CC will not collect the Worker.
194 StrongWorkerRef is ref-counted, but not thread-safe.
197 ### ThreadSafeWorkerRef
199 ThreadSafeWorkerRef is an extension of StrongWorkerRef. The difference is ThreadSafeWorkerRef holder can be on another thread. Since it is an extension of StrongWorkerRef, it gives the same characters as StrongWorkerRef. Which means its holder blocks the Worker’s shutdown, and It also blocks GC/CC a Worker.
201 Playing with ThreadSafeWorkerRef, just like StrongWorkerRef, ThreadSafeWorkerRef release timing is important for memory problems. Except the release timing, it should be noticed the callback execution on the worker thread, not on the holder’s owning thread.
203 ThreadSafeWorkerRef is ref-counted and thread-safe.
206 ### IPCWorkerRef
208 IPCWorkerRef is a special WorkerRef for IPC actors which binds its life-cycle with Worker’s shutdown notification. (In our current codebase, Cache API and Client API uses IPCWorkerRef)
210 Because some IPC shutdown needs to be in a special sequence during the Worker's shutdown. However, to make these IPC shutdown needs to ensure the Worker is kept alive, so IPCWorkerRef blocks the Worker's shutdown. But IPC shutdown no need to block GC/CC a Worker.
212 IPCWorkerRef is ref-counted, but not thread-safe.
214 Following is a table for the comparison between WorkerRefs
216 |                           |               |                 |                     |               |
217 | ------------------------- | :-----------: | :-------------: | :-----------------: | :-----------: |
218 |                           | WeakWorkerRef | StrongWorkerRef | ThreadSafeWorkerRef |  IPCWorkerRef |
219 | Holder thread             | Worker thread |  Worker thread  |      Any thread     | Worker thread |
220 | Callback execution thread | Worker thread |  Worker thread  |    Worker thread    | Worker thread |
221 | Block Worker’s shutdown   |       No      |       Yes       |         Yes         |      Yes      |
222 | Block GC a Worker         |       No      |       Yes       |         Yes         |       No      |
225 ### WorkerRef Callback
227 WorkerRef Callback can be registered when creating a WorkerRef. The Callback takes the responsibility for releasing the resources related to WorkerRef’s holder. For example, resolving/rejecting the promises created by the WorkerRef’s holder. The cleanup behavior might be synchronous or asynchronous depending on how complicated the functionality involved. For example, Cache APIs might need to wait until the operation finishes on the IO thread and release the main-thread-only objects on the main thread.
229 To avoid memory problems, there are some things need to keep in mind for WorkerRef callback
231 - Don’t release WorkerRef before finishing cleanup steps. (UAF)
232 - Don’t forget to release resources related. (Memory leaking)
233 - Don’t forget to release WorkerRef(StrongWorkerRef/ThreadWorkerRef/IPCWorkerRef) (Shutdown hang)