| 2267 | } |
| 2268 | |
| 2269 | void ObjectInstance::wrapped_gobj_toggle_notify(void* instance, GObject*, |
| 2270 | gboolean is_last_ref) { |
| 2271 | bool toggle_up_queued, toggle_down_queued; |
| 2272 | auto* self = static_cast<ObjectInstance*>(instance); |
| 2273 | |
| 2274 | GjsContextPrivate* gjs = GjsContextPrivate::from_current_context(); |
| 2275 | if (gjs->destroying()) { |
| 2276 | // Do nothing here - we're in the process of disassociating the objects. |
| 2277 | return; |
| 2278 | } |
| 2279 | |
| 2280 | /* We only want to touch javascript from one thread. If we're not in that |
| 2281 | * thread, then we need to defer processing to it. |
| 2282 | * |
| 2283 | * In case we're toggling up (and thus rooting the JS object) we also need |
| 2284 | * to take care if GC is running. The marking side of it is taken care by |
| 2285 | * JS::Heap, which we use in GjsMaybeOwned, so we're safe. As for sweeping, |
| 2286 | * it is too late: the JS object is dead, and attempting to keep it alive |
| 2287 | * would soon crash the process. Plus, if we touch the JSAPI from another |
| 2288 | * thread, libmozjs aborts in most cases when in debug mode. Thus, we drain |
| 2289 | * the toggle queue when GC starts, in order to prevent this from happening. |
| 2290 | * |
| 2291 | * In practice, a toggle up during JS finalize can only happen for temporary |
| 2292 | * refs/unrefs of objects that are garbage anyway, because JS code is never |
| 2293 | * invoked while the finalizers run and C code needs to clean after itself |
| 2294 | * before it returns from dispose()/finalize(). On the other hand, toggling |
| 2295 | * down is a lot simpler, because we're creating more garbage. So we just |
| 2296 | * unroot the object, make it a weak pointer, and wait for the next GC |
| 2297 | * cycle. |
| 2298 | * |
| 2299 | * Note that one would think that toggling up only happens in the main |
| 2300 | * thread (because toggling up is the result of the JS object, previously |
| 2301 | * visible only to JS code, becoming visible to the refcounted C world), but |
| 2302 | * because of weird weak singletons like g_bus_get_sync() objects can see |
| 2303 | * toggle-ups from different threads too. |
| 2304 | */ |
| 2305 | bool is_main_thread = gjs->is_owner_thread(); |
| 2306 | |
| 2307 | auto toggle_queue = ToggleQueue::get_default(); |
| 2308 | std::tie(toggle_down_queued, toggle_up_queued) = |
| 2309 | toggle_queue->is_queued(self); |
| 2310 | bool anything_queued = toggle_up_queued || toggle_down_queued; |
| 2311 | |
| 2312 | if (is_last_ref) { |
| 2313 | // We've transitioned from 2 -> 1 references. The JSObject is rooted and |
| 2314 | // we need to unroot it so it can be garbage collected |
| 2315 | if (is_main_thread && !anything_queued) { |
| 2316 | self->toggle_down(); |
| 2317 | } else { |
| 2318 | toggle_queue->enqueue(self, ToggleQueue::DOWN, toggle_handler); |
| 2319 | } |
| 2320 | } else { |
| 2321 | // We've transitioned from 1 -> 2 references. The JSObject associated |
| 2322 | // with the GObject is not rooted, but it needs to be. We'll root it. |
| 2323 | if (is_main_thread && !anything_queued && |
| 2324 | !JS::RuntimeHeapIsCollecting()) { |
| 2325 | self->toggle_up(); |
| 2326 | } else { |
nothing calls this directly
no test coverage detected