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