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