Branch data Line data Source code
1 : : /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 : : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
3 : : // SPDX-FileCopyrightText: 2017 Endless Mobile, Inc.
4 : : // SPDX-FileCopyrightText: 2021 Canonical Ltd.
5 : : // SPDX-FileContributor: Authored by: Philip Chimento <philip@endlessm.com>
6 : : // SPDX-FileContributor: Philip Chimento <philip.chimento@gmail.com>
7 : : // SPDX-FileContributor: Marco Trevisan <marco.trevisan@canonical.com>
8 : :
9 : : #include <algorithm> // for find_if
10 : : #include <atomic>
11 : : #include <deque>
12 : : #include <utility> // for pair
13 : :
14 : : #include "gi/object.h"
15 : : #include "gi/toggle.h"
16 : : #include "util/log.h"
17 : :
18 : : /* No-op unless GJS_VERBOSE_ENABLE_LIFECYCLE is defined to 1. */
19 : 3301 : inline void debug(const char* did GJS_USED_VERBOSE_LIFECYCLE,
20 : : const ObjectInstance* object GJS_USED_VERBOSE_LIFECYCLE) {
21 : : gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "ToggleQueue %s %p (%s @ %p)", did,
22 : : object, object ? g_type_name(object->gtype()) : "",
23 : : object ? object->ptr() : nullptr);
24 : 3301 : }
25 : :
26 : 7177 : void ToggleQueue::lock() {
27 : 7177 : auto holding_thread = std::thread::id();
28 : 7177 : auto current_thread = std::this_thread::get_id();
29 : :
30 [ + + ]: 1479616 : while (!m_holder.compare_exchange_weak(holding_thread, current_thread,
31 : : std::memory_order_acquire)) {
32 : : // In case the current thread is holding the lock, we can just try
33 : : // again, checking if this is still true and in case continue
34 [ + + ]: 1465262 : if (holding_thread != current_thread)
35 : 1462680 : holding_thread = std::thread::id();
36 : : }
37 : :
38 : 7177 : m_holder_ref_count++;
39 : 7177 : }
40 : :
41 : 7177 : void ToggleQueue::maybe_unlock() {
42 : 7177 : g_assert(owns_lock() && "Nothing to unlock here");
43 : :
44 [ + + ]: 7177 : if (!(--m_holder_ref_count))
45 : 4595 : m_holder.store(std::thread::id(), std::memory_order_release);
46 : 7177 : }
47 : :
48 : 92 : std::deque<ToggleQueue::Item>::iterator ToggleQueue::find_operation_locked(
49 : : const ObjectInstance* obj, ToggleQueue::Direction direction) {
50 : : return std::find_if(
51 : 184 : q.begin(), q.end(), [obj, direction](const Item& item) -> bool {
52 [ + + + + ]: 57 : return item.object == obj && item.direction == direction;
53 : 184 : });
54 : : }
55 : :
56 : : std::deque<ToggleQueue::Item>::const_iterator
57 : 5850 : ToggleQueue::find_operation_locked(const ObjectInstance* obj,
58 : : ToggleQueue::Direction direction) const {
59 : : return std::find_if(
60 : 11700 : q.begin(), q.end(), [obj, direction](const Item& item) -> bool {
61 [ + + + + ]: 53 : return item.object == obj && item.direction == direction;
62 : 11700 : });
63 : : }
64 : :
65 : 742 : void ToggleQueue::handle_all_toggles(Handler handler) {
66 : 742 : g_assert(owns_lock() && "Unsafe access to queue");
67 [ + + ]: 764 : while (handle_toggle(handler))
68 : : ;
69 : 742 : }
70 : :
71 : : gboolean
72 : 21 : ToggleQueue::idle_handle_toggle(void *data)
73 : : {
74 : 21 : auto self = Locked(static_cast<ToggleQueue*>(data));
75 : 21 : self->handle_all_toggles(self->m_toggle_handler);
76 : :
77 : 42 : return G_SOURCE_REMOVE;
78 : 21 : }
79 : :
80 : : void
81 : 32 : ToggleQueue::idle_destroy_notify(void *data)
82 : : {
83 : 32 : auto self = Locked(static_cast<ToggleQueue*>(data));
84 : 32 : self->m_idle_id = 0;
85 : 32 : self->m_toggle_handler = nullptr;
86 : 32 : }
87 : :
88 : 2925 : std::pair<bool, bool> ToggleQueue::is_queued(ObjectInstance* obj) const {
89 : 2925 : g_assert(owns_lock() && "Unsafe access to queue");
90 : 2925 : bool has_toggle_down = find_operation_locked(obj, DOWN) != q.end();
91 : 2925 : bool has_toggle_up = find_operation_locked(obj, UP) != q.end();
92 : 2925 : return {has_toggle_down, has_toggle_up};
93 : : }
94 : :
95 : 2948 : std::pair<bool, bool> ToggleQueue::cancel(ObjectInstance* obj) {
96 : 2948 : debug("cancel", obj);
97 : 2948 : g_assert(owns_lock() && "Unsafe access to queue");
98 : 2948 : bool had_toggle_down = false;
99 : 2948 : bool had_toggle_up = false;
100 : :
101 [ + + ]: 2956 : for (auto it = q.begin(); it != q.end();) {
102 [ + - ]: 8 : if (it->object == obj) {
103 : 8 : had_toggle_down |= (it->direction == Direction::DOWN);
104 : 8 : had_toggle_up |= (it->direction == Direction::UP);
105 : 8 : it = q.erase(it);
106 : 8 : continue;
107 : : }
108 : 0 : it++;
109 : : }
110 : :
111 : : gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "ToggleQueue: %p (%p) was %s", obj,
112 : : obj ? obj->ptr() : nullptr,
113 : : had_toggle_down && had_toggle_up
114 : : ? "queued to toggle BOTH"
115 : : : had_toggle_down ? "queued to toggle DOWN"
116 : : : had_toggle_up ? "queued to toggle UP"
117 : : : "not queued");
118 : 2948 : return {had_toggle_down, had_toggle_up};
119 : : }
120 : :
121 : 764 : bool ToggleQueue::handle_toggle(Handler handler) {
122 : 764 : g_assert(owns_lock() && "Unsafe access to queue");
123 : :
124 [ + + ]: 764 : if (q.empty())
125 : 742 : return false;
126 : :
127 : 22 : auto const& item = q.front();
128 [ + + ]: 22 : if (item.direction == UP)
129 : 17 : debug("handle UP", item.object);
130 : : else
131 : 5 : debug("handle DOWN", item.object);
132 : :
133 : 22 : handler(item.object, item.direction);
134 : 22 : q.pop_front();
135 : :
136 : 22 : return true;
137 : : }
138 : :
139 : : void
140 : 239 : ToggleQueue::shutdown(void)
141 : : {
142 : 239 : debug("shutdown", nullptr);
143 : 239 : g_assert(((void)"Queue should have been emptied before shutting down",
144 : : q.empty()));
145 : 239 : m_shutdown = true;
146 : 239 : }
147 : :
148 : 94 : void ToggleQueue::enqueue(ObjectInstance* obj, ToggleQueue::Direction direction,
149 : : ToggleQueue::Handler handler) {
150 : 94 : g_assert(owns_lock() && "Unsafe access to queue");
151 : :
152 [ + + ]: 94 : if (G_UNLIKELY (m_shutdown)) {
153 : 4 : gjs_debug(GJS_DEBUG_GOBJECT,
154 : : "Enqueuing GObject %p to toggle %s after "
155 : : "shutdown, probably from another thread (%p).",
156 [ + - ]: 2 : obj->ptr(), direction == UP ? "UP" : "DOWN", g_thread_self());
157 : 62 : return;
158 : : }
159 : :
160 : 92 : auto other_item = find_operation_locked(obj, direction == UP ? DOWN : UP);
161 [ + + ]: 92 : if (other_item != q.end()) {
162 [ + + ]: 31 : if (direction == UP) {
163 : 5 : debug("enqueue UP, dequeuing already DOWN object", obj);
164 : : } else {
165 : 26 : debug("enqueue DOWN, dequeuing already UP object", obj);
166 : : }
167 : 31 : q.erase(other_item);
168 : 31 : return;
169 : : }
170 : :
171 : : /* Only keep an unowned reference on the object here, as if we're here, the
172 : : * JSObject wrapper has already a reference and we don't want to cause
173 : : * any weak notify in case it has lost one already in the main thread.
174 : : * So let's just save the pointer to keep track of the object till we
175 : : * don't handle this toggle.
176 : : * We rely on object's cancelling the queue in case an object gets
177 : : * finalized earlier than we've processed it.
178 : : */
179 : 61 : q.emplace_back(obj, direction);
180 : :
181 [ + + ]: 61 : if (direction == UP) {
182 : 51 : debug("enqueue UP", obj);
183 : : } else {
184 : 10 : debug("enqueue DOWN", obj);
185 : : }
186 : :
187 [ + + ]: 61 : if (m_idle_id) {
188 : 29 : g_assert(((void) "Should always enqueue with the same handler",
189 : : m_toggle_handler == handler));
190 : 29 : return;
191 : : }
192 : :
193 : 32 : m_toggle_handler = handler;
194 : 32 : m_idle_id = g_idle_add_full(G_PRIORITY_HIGH, idle_handle_toggle, this,
195 : : idle_destroy_notify);
196 : : }
|