MicroTask.h (7575B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 2 * vim: set ts=8 sts=4 et sw=4 tw=99: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef js_friend_MicroTask_h 8 #define js_friend_MicroTask_h 9 10 #include "jstypes.h" 11 12 #include "js/GCPolicyAPI.h" 13 #include "js/RootingAPI.h" 14 #include "js/TypeDecls.h" 15 #include "js/UniquePtr.h" 16 #include "js/Value.h" 17 #include "js/ValueArray.h" 18 19 namespace JS { 20 21 // [SMDOC] MicroTasks in SpiderMonkey 22 // 23 // To enable higher performance, this header allows an embedding to work with 24 // a MicroTask queue stored inside the JS engine. This allows optimization of 25 // tasks by avoiding allocations in important cases. 26 // 27 // To make this work, we need some cooperation with the embedding. 28 // 29 // The high level thrust of this is that rather than managing the JobQueue 30 // themselves, embeddings assume that there's a JobQueue available to them 31 // inside the engine. When 'runJobs' happens, the embedding is responsible 32 // for pulling jobs of the queue, doing any setup required, then calling 33 // them. 34 // 35 // Embedding jobs are trivially supportable, since a MicroTask job is 36 // represented as a JS::Value, and thus an embedding job may be put on 37 // the queue by wrapping it in a JS::Value (e.g. using Private to store 38 // C++ pointers). 39 // 40 // The major requirement is that if a MicroTask identifies as a "JS" 41 // MicroTask, by passing the IsJSMicrotask predicate, the job must be 42 // run by calling RunJSMicroTask, while in the realm specified by the 43 // global returned by GetExecutionGlobalFromJSMicroTask, e.g 44 // 45 // JSObject* global = JS::GetExecutionGlobalFromJSMicroTask(job); 46 // if (global) { 47 // AutoRealm ar(cx, global); 48 // if (!JS::RunJSMicroTask(cx, job)) { 49 // ... 50 // } 51 // } 52 53 // A MicroTask is a JS::Value. Using this MicroTask system allows 54 // embedders to put whatever pointer they would like into the queue. 55 // The task will be dequeued unchanged. 56 // 57 // The major requirement here is that if the MicroTask is a JS 58 // MicroTask (as determined by IsJSMicroTask), it must be run 59 // by calling RunJSMicroTask, while in the realm specified by 60 // GetExecutionGlobalFromJSMicroTask. 61 // 62 // An embedding is free to do with non-JS MicroTasks as it 63 // sees fit. 64 using GenericMicroTask = JS::Value; 65 using JSMicroTask = JSObject; 66 67 JS_PUBLIC_API bool IsJSMicroTask(const JS::GenericMicroTask& hv); 68 JS_PUBLIC_API JSMicroTask* ToUnwrappedJSMicroTask( 69 const JS::GenericMicroTask& genericMicroTask); 70 JS_PUBLIC_API JSMicroTask* ToMaybeWrappedJSMicroTask( 71 const JS::GenericMicroTask& genericMicroTask); 72 73 // Run a MicroTask that is known to be a JS MicroTask. This will crash 74 // if provided an invalid task kind. 75 // 76 // This will return false if an exception is thrown while processing. 77 JS_PUBLIC_API bool RunJSMicroTask(JSContext* cx, 78 Handle<JS::JSMicroTask*> entry); 79 80 // Queue Management. This is done per-JSContext. 81 // 82 // Internally we maintain two queues, one for 'debugger' microtasks. These 83 // are expected in normal operation to either be popped off the queue first, 84 // or processed separately. 85 // 86 // Non-debugger MicroTasks are "regular" microtasks, and go to the regular 87 // microtask queue. 88 // 89 // In general, we highly recommend that most embeddings use only the regular 90 // microtask queue. The debugger microtask queue mostly exists to support 91 // patterns used by Gecko. 92 // 93 // These methods only fail for OOM. 94 JS_PUBLIC_API bool EnqueueMicroTask(JSContext* cx, 95 const GenericMicroTask& entry); 96 JS_PUBLIC_API bool EnqueueDebugMicroTask(JSContext* cx, 97 const GenericMicroTask& entry); 98 JS_PUBLIC_API bool PrependMicroTask(JSContext* cx, 99 const GenericMicroTask& entry); 100 101 // Dequeue the next MicroTask. If there are no MicroTasks of the appropriate 102 // kind, each of the below API returns JS::NullValue(). 103 // 104 // The generic DequeueNext will always pull a debugger microtask first, 105 // if one exists, then a regular microtask if one exists. 106 // - DequeueNextDebuggerMicroTask only pulls from the debugger queue. 107 // - DequeueNextRegularMicroTask only pulls from the regular queue. 108 // 109 // Internally, these basically do 110 // 111 // if (HasXMicroTask()) { return X.popFront(); } return NullValue() 112 // 113 // so checking for emptiness before calling these is not required, and is 114 // very slightly less efficient. 115 JS_PUBLIC_API GenericMicroTask DequeueNextMicroTask(JSContext* cx); 116 JS_PUBLIC_API GenericMicroTask DequeueNextDebuggerMicroTask(JSContext* cx); 117 JS_PUBLIC_API GenericMicroTask DequeueNextRegularMicroTask(JSContext* cx); 118 119 // Returns true if there are -any- microtasks pending in the queue. 120 JS_PUBLIC_API bool HasAnyMicroTasks(JSContext* cx); 121 122 // Returns true if there are any debugger microtasks pending in the queue. 123 JS_PUBLIC_API bool HasDebuggerMicroTasks(JSContext* cx); 124 125 // Returns true if there are any regular (non-debugger) microtasks pending in 126 // the queue. 127 JS_PUBLIC_API bool HasRegularMicroTasks(JSContext* cx); 128 129 // Returns the length of the regular microtask queue. 130 JS_PUBLIC_API size_t GetRegularMicroTaskCount(JSContext* cx); 131 132 // This is the global associated with the realm RunJSMicroTask expects to be 133 // in. Returns nullptr if a dead wrapper is found. 134 JS_PUBLIC_API JSObject* GetExecutionGlobalFromJSMicroTask(JSMicroTask* entry); 135 136 // To handle cases where the queue needs to be set aside for some reason 137 // (mostly the Debugger API), we provide a Save and Restore API. 138 // 139 // When restoring the saved queue, the JSContext microtask queue must be 140 // empty -- you cannot drop items by restoring over a non-empty queue 141 // (so HasAnyMicroTasks must be false). 142 class SavedMicroTaskQueue { 143 public: 144 SavedMicroTaskQueue() = default; 145 virtual ~SavedMicroTaskQueue() = default; 146 SavedMicroTaskQueue(const SavedMicroTaskQueue&) = delete; 147 SavedMicroTaskQueue& operator=(const SavedMicroTaskQueue&) = delete; 148 }; 149 150 // This will return nullptr (and set OutOfMemory) if the save operation 151 // fails. 152 JS_PUBLIC_API js::UniquePtr<SavedMicroTaskQueue> SaveMicroTaskQueue( 153 JSContext* cx); 154 JS_PUBLIC_API void RestoreMicroTaskQueue( 155 JSContext* cx, js::UniquePtr<SavedMicroTaskQueue> savedQueue); 156 157 // Via the following API functions various host defined data is exposed to the 158 // embedder (see JobQueue::getHostDefinedData). 159 // 160 // These return true on success and false on failure. They return false if 161 // there are any unwrapping issues (e.g., dead wrappers), and true with nullptr 162 // if there just isn't any data. 163 // 164 // This disambiguates between no-data and the dead wrapper case 165 JS_PUBLIC_API bool MaybeGetHostDefinedDataFromJSMicroTask( 166 JSMicroTask* entry, MutableHandleObject out); 167 JS_PUBLIC_API bool MaybeGetAllocationSiteFromJSMicroTask( 168 JSMicroTask* entry, MutableHandleObject out); 169 170 // In some circumstances an entry may not have host defined data but may 171 // still have a host defined global; 172 JS_PUBLIC_API JSObject* MaybeGetHostDefinedGlobalFromJSMicroTask( 173 JSMicroTask* entry); 174 175 JS_PUBLIC_API JSObject* MaybeGetPromiseFromJSMicroTask(JSMicroTask* entry); 176 177 // Get the flow ID from a JS microtask for profiler markers. 178 // This only returns false if entry has become a dead wrapper, 179 // in which case the microtask doesn't run anyhow. 180 JS_PUBLIC_API bool GetFlowIdFromJSMicroTask(JSMicroTask* entry, uint64_t* uid); 181 182 } // namespace JS 183 184 #endif /* js_friend_MicroTask_h */