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.JoinAll( std::chrono::milliseconds( 0 ) ), 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 // The detached worker no longer participates in joins.
93 BOOST_CHECK_EQUAL( registry.JoinAll( std::chrono::milliseconds( 0 ) ), 0u );
94
95 {
96 std::lock_guard<std::mutex> lock( releaseMutex );
97 release = true;
98 }
99
100 releaseCv.notify_all();
101
102 // Wait for the detached worker to actually return before this test
103 // function exits. Otherwise the worker is still touching releaseMutex,
104 // releaseCv and release on the stack while the next test reuses that
105 // memory.
106
107 for( int i = 0; i < 1000 && !workerFinished.load( std::memory_order_acquire ); ++i )
108 std::this_thread::sleep_for( std::chrono::milliseconds( 5 ) );
109
110 BOOST_CHECK( workerFinished.load() );
111}
112
113
114BOOST_AUTO_TEST_CASE( DestructorDetachesOutstandingThreads )
115{
116 std::mutex releaseMutex;
117 std::condition_variable releaseCv;
118 bool release = false;
119 std::atomic<bool> workerEntered{ false };
120 std::atomic<bool> workerFinished{ false };
121
122 {
123 KIGIT_ORPHAN_REGISTRY registry;
124
125 registry.Register(
126 "outlive-registry",
127 [&]()
128 {
129 workerEntered.store( true, std::memory_order_release );
130
131 {
132 std::unique_lock<std::mutex> lock( releaseMutex );
133 releaseCv.wait( lock, [&]() { return release; } );
134 }
135
136 // Set workerFinished after the unique_lock has destructed so the
137 // test waiting on workerFinished cannot return while we're still
138 // touching releaseMutex on the test's stack.
139 workerFinished.store( true, std::memory_order_release );
140 } );
141
142 while( !workerEntered.load( std::memory_order_acquire ) )
143 std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
144
145 // Registry destructor runs here. It must detach the still-running
146 // worker instead of terminating the process by destroying a joinable
147 // std::thread.
148 }
149
150 {
151 std::lock_guard<std::mutex> lock( releaseMutex );
152 release = true;
153 }
154
155 releaseCv.notify_all();
156
157 // Wait for the detached worker to actually finish. Under sanitizer slowdown
158 // this can take a noticeable fraction of a second; the loop is generous
159 // enough to keep the test from leaking the worker into the next test.
160
161 for( int i = 0; i < 1000 && !workerFinished.load( std::memory_order_acquire ); ++i )
162 std::this_thread::sleep_for( std::chrono::milliseconds( 5 ) );
163
164 BOOST_CHECK( workerFinished.load() );
165}
166
167
168BOOST_AUTO_TEST_CASE( RegisterReapsFinishedEntries )
169{
170 KIGIT_ORPHAN_REGISTRY registry;
171
172 BOOST_CHECK( registry.Register( "quick-1", []() {} ) );
173
174 // Give the first worker a moment to finish before we register the
175 // second. The second Register() should reap the first.
176
177 std::this_thread::sleep_for( std::chrono::milliseconds( 25 ) );
178
179 BOOST_CHECK( registry.Register( "quick-2", []() {} ) );
180
181 size_t stuck = registry.JoinAll( std::chrono::seconds( 1 ) );
182 BOOST_CHECK_EQUAL( stuck, 0u );
183}
184
185
186BOOST_AUTO_TEST_CASE( RegisterRejectsAfterJoinAll )
187{
188 KIGIT_ORPHAN_REGISTRY registry;
189
190 // Close the registry; no outstanding work, so JoinAll returns 0.
191
192 BOOST_CHECK_EQUAL( registry.JoinAll( std::chrono::milliseconds( 1 ) ), 0u );
193
194 std::atomic<bool> workRan{ false };
195
196 bool accepted = registry.Register( "late",
197 [&workRan]() { workRan.store( true ); } );
198
199 BOOST_CHECK( !accepted );
200
201 // Work must not have been scheduled.
202
203 std::this_thread::sleep_for( std::chrono::milliseconds( 20 ) );
204 BOOST_CHECK( !workRan.load() );
205}
206
207
208BOOST_AUTO_TEST_CASE( MoveOnlyWorkIsAccepted )
209{
210 KIGIT_ORPHAN_REGISTRY registry;
211
212 auto payload = std::make_unique<int>( 42 );
213 std::atomic<int> observed{ 0 };
214
215 bool accepted = registry.Register(
216 "move-only",
217 [payload = std::move( payload ), &observed]() mutable
218 {
219 observed.store( *payload, std::memory_order_release );
220 } );
221
222 BOOST_CHECK( accepted );
223 BOOST_CHECK_EQUAL( registry.JoinAll( std::chrono::seconds( 1 ) ), 0u );
224 BOOST_CHECK_EQUAL( observed.load(), 42 );
225}
226
227
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.
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")