KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_kigit_orphan_registry.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
27
28#include <atomic>
29#include <chrono>
30#include <condition_variable>
31#include <mutex>
32#include <thread>
33
34
35BOOST_AUTO_TEST_SUITE( KiGitOrphanRegistry )
36
37
38BOOST_AUTO_TEST_CASE( JoinAllCompletesFastWork )
39{
40 KIGIT_ORPHAN_REGISTRY registry;
41 std::atomic<int> counter{ 0 };
42
43 for( int i = 0; i < 4; ++i )
44 {
45 registry.Register( "fast-" + std::to_string( i ),
46 [&counter]()
47 {
48 std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
49 counter.fetch_add( 1, std::memory_order_relaxed );
50 } );
51 }
52
53 size_t stuck = registry.JoinAll( std::chrono::seconds( 2 ) );
54
55 BOOST_CHECK_EQUAL( stuck, 0u );
56 BOOST_CHECK_EQUAL( counter.load(), 4 );
57 BOOST_CHECK_EQUAL( registry.TrackedCount(), 0u );
58}
59
60
61BOOST_AUTO_TEST_CASE( JoinAllTimesOutOnStuckWork )
62{
63 KIGIT_ORPHAN_REGISTRY registry;
64 std::mutex releaseMutex;
65 std::condition_variable releaseCv;
66 bool release = false;
67 std::atomic<bool> workerEntered{ false };
68 std::atomic<bool> workerFinished{ false };
69
70 registry.Register(
71 "stuck",
72 [&]()
73 {
74 workerEntered.store( true, std::memory_order_release );
75
76 std::unique_lock<std::mutex> lock( releaseMutex );
77 releaseCv.wait( lock, [&]() { return release; } );
78 lock.unlock();
79
80 workerFinished.store( true, std::memory_order_release );
81 } );
82
83 // Wait until the worker is actually running so we're exercising the
84 // timeout path, not the trivial "thread never started" path.
85
86 while( !workerEntered.load( std::memory_order_acquire ) )
87 std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
88
89 auto start = std::chrono::steady_clock::now();
90 size_t stuck = registry.JoinAll( std::chrono::milliseconds( 100 ) );
91 auto elapsed = std::chrono::steady_clock::now() - start;
92
93 BOOST_CHECK_EQUAL( stuck, 1u );
94 BOOST_CHECK( elapsed < std::chrono::seconds( 2 ) );
95
96 // After JoinAll the registry is empty even though the thread kept
97 // running. Release the worker now so it can exit cleanly; it was
98 // detached when JoinAll gave up on it.
99
100 {
101 std::lock_guard<std::mutex> lock( releaseMutex );
102 release = true;
103 }
104
105 releaseCv.notify_all();
106
107 // Wait for the detached worker to actually return before this test
108 // function exits. Otherwise the worker is still touching releaseMutex,
109 // releaseCv and release on the stack while the next test reuses that
110 // memory.
111
112 for( int i = 0; i < 1000 && !workerFinished.load( std::memory_order_acquire ); ++i )
113 std::this_thread::sleep_for( std::chrono::milliseconds( 5 ) );
114
115 BOOST_CHECK( workerFinished.load() );
116 BOOST_CHECK_EQUAL( registry.TrackedCount(), 0u );
117}
118
119
120BOOST_AUTO_TEST_CASE( DestructorDetachesOutstandingThreads )
121{
122 std::mutex releaseMutex;
123 std::condition_variable releaseCv;
124 bool release = false;
125 std::atomic<bool> workerEntered{ false };
126 std::atomic<bool> workerFinished{ false };
127
128 {
129 KIGIT_ORPHAN_REGISTRY registry;
130
131 registry.Register(
132 "outlive-registry",
133 [&]()
134 {
135 workerEntered.store( true, std::memory_order_release );
136
137 {
138 std::unique_lock<std::mutex> lock( releaseMutex );
139 releaseCv.wait( lock, [&]() { return release; } );
140 }
141
142 // Set workerFinished after the unique_lock has destructed so the
143 // test waiting on workerFinished cannot return while we're still
144 // touching releaseMutex on the test's stack.
145 workerFinished.store( true, std::memory_order_release );
146 } );
147
148 while( !workerEntered.load( std::memory_order_acquire ) )
149 std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
150
151 // Registry destructor runs here. It must detach the still-running
152 // worker instead of terminating the process by destroying a joinable
153 // std::thread.
154 }
155
156 {
157 std::lock_guard<std::mutex> lock( releaseMutex );
158 release = true;
159 }
160
161 releaseCv.notify_all();
162
163 // Wait for the detached worker to actually finish. Under sanitizer slowdown
164 // this can take a noticeable fraction of a second; the loop is generous
165 // enough to keep the test from leaking the worker into the next test.
166
167 for( int i = 0; i < 1000 && !workerFinished.load( std::memory_order_acquire ); ++i )
168 std::this_thread::sleep_for( std::chrono::milliseconds( 5 ) );
169
170 BOOST_CHECK( workerFinished.load() );
171}
172
173
174BOOST_AUTO_TEST_CASE( RegisterReapsFinishedEntries )
175{
176 KIGIT_ORPHAN_REGISTRY registry;
177
178 BOOST_CHECK( registry.Register( "quick-1", []() {} ) );
179
180 // Give the first worker a moment to finish before we register the
181 // second. The second Register() should reap the first.
182
183 std::this_thread::sleep_for( std::chrono::milliseconds( 25 ) );
184
185 BOOST_CHECK( registry.Register( "quick-2", []() {} ) );
186
187 size_t stuck = registry.JoinAll( std::chrono::seconds( 1 ) );
188 BOOST_CHECK_EQUAL( stuck, 0u );
189}
190
191
192BOOST_AUTO_TEST_CASE( RegisterRejectsAfterJoinAll )
193{
194 KIGIT_ORPHAN_REGISTRY registry;
195
196 // Close the registry; no outstanding work, so JoinAll returns 0.
197
198 BOOST_CHECK_EQUAL( registry.JoinAll( std::chrono::milliseconds( 1 ) ), 0u );
199
200 std::atomic<bool> workRan{ false };
201
202 bool accepted = registry.Register( "late",
203 [&workRan]() { workRan.store( true ); } );
204
205 BOOST_CHECK( !accepted );
206
207 // Work must not have been scheduled.
208
209 std::this_thread::sleep_for( std::chrono::milliseconds( 20 ) );
210 BOOST_CHECK( !workRan.load() );
211}
212
213
214BOOST_AUTO_TEST_CASE( MoveOnlyWorkIsAccepted )
215{
216 KIGIT_ORPHAN_REGISTRY registry;
217
218 auto payload = std::make_unique<int>( 42 );
219 std::atomic<int> observed{ 0 };
220
221 bool accepted = registry.Register(
222 "move-only",
223 [payload = std::move( payload ), &observed]() mutable
224 {
225 observed.store( *payload, std::memory_order_release );
226 } );
227
228 BOOST_CHECK( accepted );
229 BOOST_CHECK_EQUAL( registry.JoinAll( std::chrono::seconds( 1 ) ), 0u );
230 BOOST_CHECK_EQUAL( observed.load(), 42 );
231}
232
233
Registry of background git cleanup threads that outlive the owning project or dialog.
bool Register(const std::string &aLabel, F &&aWork)
Spawn a tracked orphan thread running aWork.
size_t JoinAll(std::chrono::milliseconds aTimeout)
Join all registered orphan threads, giving them up to aTimeout to finish cooperatively.
size_t TrackedCount() const
Return the number of tracked orphan threads that have not yet finished executing.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(JoinAllCompletesFastWork)
BOOST_CHECK_EQUAL(result, "25.4")