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, see <https://www.gnu.org/licenses/>.
18 */
19
21
23
24#include <atomic>
25#include <chrono>
26#include <condition_variable>
27#include <mutex>
28#include <thread>
29
30
31BOOST_AUTO_TEST_SUITE( KiGitOrphanRegistry )
32
33
34BOOST_AUTO_TEST_CASE( JoinAllCompletesFastWork )
35{
36 KIGIT_ORPHAN_REGISTRY registry;
37 std::atomic<int> counter{ 0 };
38
39 for( int i = 0; i < 4; ++i )
40 {
41 registry.Register( "fast-" + std::to_string( i ),
42 [&counter]()
43 {
44 std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
45 counter.fetch_add( 1, std::memory_order_relaxed );
46 } );
47 }
48
49 size_t stuck = registry.JoinAll( std::chrono::seconds( 2 ) );
50
51 BOOST_CHECK_EQUAL( stuck, 0u );
52 BOOST_CHECK_EQUAL( counter.load(), 4 );
53 BOOST_CHECK_EQUAL( registry.TrackedCount(), 0u );
54}
55
56
57BOOST_AUTO_TEST_CASE( JoinAllTimesOutOnStuckWork )
58{
59 KIGIT_ORPHAN_REGISTRY registry;
60 std::mutex releaseMutex;
61 std::condition_variable releaseCv;
62 bool release = false;
63 std::atomic<bool> workerEntered{ false };
64 std::atomic<bool> workerFinished{ false };
65
66 registry.Register(
67 "stuck",
68 [&]()
69 {
70 workerEntered.store( true, std::memory_order_release );
71
72 std::unique_lock<std::mutex> lock( releaseMutex );
73 releaseCv.wait( lock, [&]() { return release; } );
74 lock.unlock();
75
76 workerFinished.store( true, std::memory_order_release );
77 } );
78
79 // Wait until the worker is actually running so we're exercising the
80 // timeout path, not the trivial "thread never started" path.
81
82 while( !workerEntered.load( std::memory_order_acquire ) )
83 std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
84
85 auto start = std::chrono::steady_clock::now();
86 size_t stuck = registry.JoinAll( std::chrono::milliseconds( 100 ) );
87 auto elapsed = std::chrono::steady_clock::now() - start;
88
89 BOOST_CHECK_EQUAL( stuck, 1u );
90 BOOST_CHECK( elapsed < std::chrono::seconds( 2 ) );
91
92 // After JoinAll the registry is empty even though the thread kept
93 // running. Release the worker now so it can exit cleanly; it was
94 // detached when JoinAll gave up on it.
95
96 {
97 std::lock_guard<std::mutex> lock( releaseMutex );
98 release = true;
99 }
100
101 releaseCv.notify_all();
102
103 // Wait for the detached worker to actually return before this test
104 // function exits. Otherwise the worker is still touching releaseMutex,
105 // releaseCv and release on the stack while the next test reuses that
106 // memory.
107
108 for( int i = 0; i < 1000 && !workerFinished.load( std::memory_order_acquire ); ++i )
109 std::this_thread::sleep_for( std::chrono::milliseconds( 5 ) );
110
111 BOOST_CHECK( workerFinished.load() );
112 BOOST_CHECK_EQUAL( registry.TrackedCount(), 0u );
113}
114
115
116BOOST_AUTO_TEST_CASE( DestructorDetachesOutstandingThreads )
117{
118 std::mutex releaseMutex;
119 std::condition_variable releaseCv;
120 bool release = false;
121 std::atomic<bool> workerEntered{ false };
122 std::atomic<bool> workerFinished{ false };
123
124 {
125 KIGIT_ORPHAN_REGISTRY registry;
126
127 registry.Register(
128 "outlive-registry",
129 [&]()
130 {
131 workerEntered.store( true, std::memory_order_release );
132
133 {
134 std::unique_lock<std::mutex> lock( releaseMutex );
135 releaseCv.wait( lock, [&]() { return release; } );
136 }
137
138 // Set workerFinished after the unique_lock has destructed so the
139 // test waiting on workerFinished cannot return while we're still
140 // touching releaseMutex on the test's stack.
141 workerFinished.store( true, std::memory_order_release );
142 } );
143
144 while( !workerEntered.load( std::memory_order_acquire ) )
145 std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
146
147 // Registry destructor runs here. It must detach the still-running
148 // worker instead of terminating the process by destroying a joinable
149 // std::thread.
150 }
151
152 {
153 std::lock_guard<std::mutex> lock( releaseMutex );
154 release = true;
155 }
156
157 releaseCv.notify_all();
158
159 // Wait for the detached worker to actually finish. Under sanitizer slowdown
160 // this can take a noticeable fraction of a second; the loop is generous
161 // enough to keep the test from leaking the worker into the next test.
162
163 for( int i = 0; i < 1000 && !workerFinished.load( std::memory_order_acquire ); ++i )
164 std::this_thread::sleep_for( std::chrono::milliseconds( 5 ) );
165
166 BOOST_CHECK( workerFinished.load() );
167}
168
169
170BOOST_AUTO_TEST_CASE( RegisterReapsFinishedEntries )
171{
172 KIGIT_ORPHAN_REGISTRY registry;
173
174 BOOST_CHECK( registry.Register( "quick-1", []() {} ) );
175
176 // Give the first worker a moment to finish before we register the
177 // second. The second Register() should reap the first.
178
179 std::this_thread::sleep_for( std::chrono::milliseconds( 25 ) );
180
181 BOOST_CHECK( registry.Register( "quick-2", []() {} ) );
182
183 size_t stuck = registry.JoinAll( std::chrono::seconds( 1 ) );
184 BOOST_CHECK_EQUAL( stuck, 0u );
185}
186
187
188BOOST_AUTO_TEST_CASE( RegisterRejectsAfterJoinAll )
189{
190 KIGIT_ORPHAN_REGISTRY registry;
191
192 // Close the registry; no outstanding work, so JoinAll returns 0.
193
194 BOOST_CHECK_EQUAL( registry.JoinAll( std::chrono::milliseconds( 1 ) ), 0u );
195
196 std::atomic<bool> workRan{ false };
197
198 bool accepted = registry.Register( "late",
199 [&workRan]() { workRan.store( true ); } );
200
201 BOOST_CHECK( !accepted );
202
203 // Work must not have been scheduled.
204
205 std::this_thread::sleep_for( std::chrono::milliseconds( 20 ) );
206 BOOST_CHECK( !workRan.load() );
207}
208
209
210BOOST_AUTO_TEST_CASE( MoveOnlyWorkIsAccepted )
211{
212 KIGIT_ORPHAN_REGISTRY registry;
213
214 auto payload = std::make_unique<int>( 42 );
215 std::atomic<int> observed{ 0 };
216
217 bool accepted = registry.Register(
218 "move-only",
219 [payload = std::move( payload ), &observed]() mutable
220 {
221 observed.store( *payload, std::memory_order_release );
222 } );
223
224 BOOST_CHECK( accepted );
225 BOOST_CHECK_EQUAL( registry.JoinAll( std::chrono::seconds( 1 ) ), 0u );
226 BOOST_CHECK_EQUAL( observed.load(), 42 );
227}
228
229
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")