diff options
author | Philip Chimento <philip.chimento@gmail.com> | 2017-01-11 23:34:40 -0800 |
---|---|---|
committer | Philip Chimento <philip@endlessm.com> | 2017-02-08 09:34:56 -0800 |
commit | cc71606f57599bb34ab5462cf2fab5ba3e810d6c (patch) | |
tree | 2764b831a5dd077215c4f19cbe775e444db4fd4b /gjs/jsapi-util-root.h | |
parent | f4e5ef6f8358d17719aeeb02bfdb5ae1237c7224 (diff) | |
download | gjs-cc71606f57599bb34ab5462cf2fab5ba3e810d6c.tar.gz |
js: Refactor dual use of JS::Heap wrapper
The previous situation was that a JS::Heap wrapper was used to root a GC
thing under some conditions using JS::AddFooRoot() or the keep-alive
object, and trace or maintain a weak pointer otherwise.
This will not be possible anymore in SpiderMonkey 38. JS::AddFooRoot()
and JS::RemoveFooRoot() are going away, in favour of
JS::PersistentRootedFoo. The keep-alive object has its own problems,
because the SpiderMonkey 38 garbage collector will move GC things around,
so anything referring to a GC thing on the keep-alive object will have to
know when to update its JS::Heap wrapper when the GC thing moves.
This previous situation existed in two places.
(1) The JS::Value holding a trampoline's function. If the function was
owned by the trampoline (the normal case), then it was rooted. If the
function was a vfunc, in which case it was owned by the GObject class
prototype and the trampoline was essentially leaked, then it remained a
weak pointer.
(2) The JSObject associated with a closure. In the normal case the
object and the closure had the same lifetime and the object was rooted.
Similar to above, if the closure was a signal it was owned by the
GObject class, and traced.
For both of these places we now use GjsMaybeOwned, a wrapper that sticks
its GC thing in either a JS::PersistentRootedFoo, if the thing is
intended to be rooted, or a JS::Heap<Foo>, if it is not. If rooted, the
GjsMaybeOwned holds a weak reference to the GjsContext, and therefore
can send out a notification when the context's dispose function is run,
similar to existing functionality of the keep-alive object.
This will still need to change further after the switch to SpiderMonkey
38, since weak pointers must be updated when they are moved by the GC.
(This is technically already the case in SpiderMonkey 31, but the API
makes it difficult to do correctly, and in practice it isn't necessary.)
https://bugzilla.gnome.org/show_bug.cgi?id=776966
Diffstat (limited to 'gjs/jsapi-util-root.h')
-rw-r--r-- | gjs/jsapi-util-root.h | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/gjs/jsapi-util-root.h b/gjs/jsapi-util-root.h new file mode 100644 index 00000000..9cc12ea7 --- /dev/null +++ b/gjs/jsapi-util-root.h @@ -0,0 +1,273 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * Copyright (c) 2017 Endless Mobile, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef GJS_JSAPI_UTIL_ROOT_H +#define GJS_JSAPI_UTIL_ROOT_H + +#include <glib.h> +#include <glib-object.h> + +#include "gjs/context.h" +#include "gjs/jsapi-wrapper.h" +#include "util/log.h" + +/* jsapi-util-root.h - Utilities for dealing with the lifetime and ownership of + * JS Objects and other things that can be collected by the garbage collector + * (collectively called "GC things.") + * + * GjsMaybeOwned<T> is a multi-purpose wrapper for a GC thing of type T. You can + * wrap a thing in one of three ways: + * + * - trace the thing (tie it to the lifetime of another GC thing), + * - root the thing (keep it alive as long as the wrapper is in existence), + * - maintain a weak pointer to the thing (not keep it alive at all and have it + * possibly be finalized out from under you). + * + * To trace or maintain a weak pointer, simply assign a thing of type T to the + * GjsMaybeOwned wrapper. For tracing, you must call the trace() method when + * your other GC thing is traced. + * + * Rooting requires a JSContext so can't just assign a thing of type T. Instead + * you need to call the root() method to set up rooting. + * + * If the thing is rooted, it will be unrooted either when the GjsMaybeOwned is + * destroyed, or when the JSContext is destroyed. In the latter case, you can + * get an optional notification by passing a callback to root(). + * + * To switch between one of the three modes, you must first call reset(). This + * drops all references to any GC thing and leaves the GjsMaybeOwned in the + * same state as if it had just been constructed. + */ + +/* This struct contains operations that must be implemented differently + * depending on the type of the GC thing. Add more types as necessary. If an + * implementation is never used, it's OK to leave it out. The compiler will + * complain if it's used somewhere but not instantiated here. + */ +template<typename T> +struct GjsHeapOperation { + static void trace(JSTracer *tracer, + JS::Heap<T> *thing, + const char *name); +}; + +template<> +struct GjsHeapOperation<JSObject *> { + static void + trace(JSTracer *tracer, + JS::Heap<JSObject *> *thing, + const char *name) + { + JS_CallHeapObjectTracer(tracer, thing, name); + } +}; + +/* GjsMaybeOwned is intended only for use in heap allocation. Do not allocate it + * on the stack, and do not allocate any instances of structures that have it as + * a member on the stack either. Unfortunately we cannot enforce this at compile + * time with a private constructor; that would prevent the intended usage as a + * member of a heap-allocated struct. */ +template<typename T> +class GjsMaybeOwned { +public: + typedef void (*DestroyNotify)(JS::Handle<T> thing, void *data); + +private: + bool m_rooted; /* wrapper is in rooted mode */ + bool m_has_weakref; /* we have a weak reference to the GjsContext */ + + JSContext *m_cx; + JS::Heap<T> m_heap; /* should be untouched if in rooted mode */ + JS::PersistentRooted<T> *m_root; /* should be null if not in rooted mode */ + + DestroyNotify m_notify; + void *m_data; + + /* No-op unless GJS_VERBOSE_ENABLE_LIFECYCLE is defined to 1. */ + inline void + debug(const char *what) + { + gjs_debug_lifecycle(GJS_DEBUG_KEEP_ALIVE, "GjsMaybeOwned %p %s", this, + what); + } + + static void + on_context_destroy(void *data, + GObject *ex_context) + { + auto self = static_cast<GjsMaybeOwned<T> *>(data); + self->invalidate(); + } + + void + teardown_rooting(void) + { + debug("teardown_rooting()"); + g_assert(m_rooted); + + delete m_root; + m_root = nullptr; + m_rooted = false; + + if (!m_has_weakref) + return; + + auto gjs_cx = static_cast<GjsContext *>(JS_GetContextPrivate(m_cx)); + g_object_weak_unref(G_OBJECT(gjs_cx), on_context_destroy, this); + m_has_weakref = false; + } + + /* Called for a rooted wrapper when the JSContext is about to be destroyed. + * This calls the destroy-notify callback if one was passed to root(), and + * then removes all rooting from the object. */ + void + invalidate(void) + { + debug("invalidate()"); + g_assert(m_rooted); + + /* The weak ref is already gone because the context is dead, so no need + * to remove it. */ + m_has_weakref = false; + + /* The object is still live across this callback. Re-entry into the + * destructor from the callback should also be safe. */ + if (m_notify) + m_notify(handle(), m_data); + + /* If the callback destroyed this wrapper already, we're done. */ + if (!m_rooted) + return; + + reset(); + } + +public: + GjsMaybeOwned(void) : + m_rooted(false), + m_has_weakref(false), + m_cx(nullptr), + m_root(nullptr), + m_notify(nullptr), + m_data(nullptr) + { + debug("created"); + } + + ~GjsMaybeOwned(void) + { + debug("destroyed"); + if (m_rooted) + teardown_rooting(); + } + + /* To access the GC thing, call get(). In many cases you can just use the + * GjsMaybeOwned wrapper in place of the GC thing itself due to the implicit + * cast operator. But if you want to call methods on the GC thing, for + * example if it's a JS::Value, you have to use get(). */ + const T + get(void) const + { + return m_rooted ? m_root->get() : m_heap.get(); + } + operator const T(void) const { return get(); } + + bool + operator==(const T& other) const + { + if (m_rooted) + return m_root->get() == other; + return m_heap == other; + } + inline bool operator!=(const T& other) const { return !(*this == other); } + + /* You can get a Handle<T> if the thing is rooted, so that you can use this + * wrapper with stack rooting. However, you must not do this if the + * JSContext can be destroyed while the Handle is live. */ + JS::Handle<T> + handle(void) + { + g_assert(m_rooted); + return *m_root; + } + + /* Roots the GC thing. You must not use this if you're already using the + * wrapper to store a non-rooted GC thing. */ + void + root(JSContext *cx, + const T& thing, + DestroyNotify notify = nullptr, + void *data = nullptr) + { + debug("root()"); + g_assert(!m_rooted); + g_assert(m_heap.get() == js::GCMethods<T>::initial()); + m_rooted = true; + m_cx = cx; + m_notify = notify; + m_data = data; + m_root = new JS::PersistentRooted<T>(m_cx, thing); + + auto gjs_cx = static_cast<GjsContext *>(JS_GetContextPrivate(m_cx)); + g_assert(GJS_IS_CONTEXT(gjs_cx)); + g_object_weak_ref(G_OBJECT(gjs_cx), on_context_destroy, this); + m_has_weakref = true; + } + + /* You can only assign directly to the GjsMaybeOwned wrapper in the + * non-rooted case. */ + void + operator=(const T& thing) + { + g_assert(!m_rooted); + m_heap = thing; + } + + void + reset(void) + { + debug("reset()"); + if (!m_rooted) { + m_heap = js::GCMethods<T>::initial(); + return; + } + + teardown_rooting(); + m_cx = nullptr; + m_notify = nullptr; + m_data = nullptr; + } + + /* Tracing makes no sense in the rooted case, because JS::PersistentRooted + * already takes care of that. */ + void + trace(JSTracer *tracer, + const char *name) + { + debug("trace()"); + g_assert(!m_rooted); + GjsHeapOperation<T>::trace(tracer, &m_heap, name); + } +}; + +#endif /* GJS_JSAPI_UTIL_ROOT_H */ |