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
69 registry.Register(
70 "stuck",
71 [&]()
72 {
73 workerEntered.store( true, std::memory_order_release );
74
75 std::unique_lock<std::mutex> lock( releaseMutex );
76 releaseCv.wait( lock, [&]() { return 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 // Give the detached worker a chance to finish before the test ends.
104
105 std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) );
106
107 BOOST_CHECK_EQUAL( registry.TrackedCount(), 0u );
108}
109
110
111BOOST_AUTO_TEST_CASE( DestructorDetachesOutstandingThreads )
112{
113 std::mutex releaseMutex;
114 std::condition_variable releaseCv;
115 bool release = false;
116 std::atomic<bool> workerEntered{ false };
117 std::atomic<bool> workerFinished{ false };
118
119 {
120 KIGIT_ORPHAN_REGISTRY registry;
121
122 registry.Register(
123 "outlive-registry",
124 [&]()
125 {
126 workerEntered.store( true, std::memory_order_release );
127
128 std::unique_lock<std::mutex> lock( releaseMutex );
129 releaseCv.wait( lock, [&]() { return release; } );
130
131 workerFinished.store( true, std::memory_order_release );
132 } );
133
134 while( !workerEntered.load( std::memory_order_acquire ) )
135 std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
136
137 // Registry destructor runs here. It must detach the still-running
138 // worker instead of terminating the process by destroying a joinable
139 // std::thread.
140 }
141
142 {
143 std::lock_guard<std::mutex> lock( releaseMutex );
144 release = true;
145 }
146
147 releaseCv.notify_all();
148
149 // Poll briefly for worker completion so a slow CI host doesn't flake.
150
151 for( int i = 0; i < 200 && !workerFinished.load( std::memory_order_acquire ); ++i )
152 std::this_thread::sleep_for( std::chrono::milliseconds( 5 ) );
153
154 BOOST_CHECK( workerFinished.load() );
155}
156
157
158BOOST_AUTO_TEST_CASE( RegisterReapsFinishedEntries )
159{
160 KIGIT_ORPHAN_REGISTRY registry;
161
162 BOOST_CHECK( registry.Register( "quick-1", []() {} ) );
163
164 // Give the first worker a moment to finish before we register the
165 // second. The second Register() should reap the first.
166
167 std::this_thread::sleep_for( std::chrono::milliseconds( 25 ) );
168
169 BOOST_CHECK( registry.Register( "quick-2", []() {} ) );
170
171 size_t stuck = registry.JoinAll( std::chrono::seconds( 1 ) );
172 BOOST_CHECK_EQUAL( stuck, 0u );
173}
174
175
176BOOST_AUTO_TEST_CASE( RegisterRejectsAfterJoinAll )
177{
178 KIGIT_ORPHAN_REGISTRY registry;
179
180 // Close the registry; no outstanding work, so JoinAll returns 0.
181
182 BOOST_CHECK_EQUAL( registry.JoinAll( std::chrono::milliseconds( 1 ) ), 0u );
183
184 std::atomic<bool> workRan{ false };
185
186 bool accepted = registry.Register( "late",
187 [&workRan]() { workRan.store( true ); } );
188
189 BOOST_CHECK( !accepted );
190
191 // Work must not have been scheduled.
192
193 std::this_thread::sleep_for( std::chrono::milliseconds( 20 ) );
194 BOOST_CHECK( !workRan.load() );
195}
196
197
198BOOST_AUTO_TEST_CASE( MoveOnlyWorkIsAccepted )
199{
200 KIGIT_ORPHAN_REGISTRY registry;
201
202 auto payload = std::make_unique<int>( 42 );
203 std::atomic<int> observed{ 0 };
204
205 bool accepted = registry.Register(
206 "move-only",
207 [payload = std::move( payload ), &observed]() mutable
208 {
209 observed.store( *payload, std::memory_order_release );
210 } );
211
212 BOOST_CHECK( accepted );
213 BOOST_CHECK_EQUAL( registry.JoinAll( std::chrono::seconds( 1 ) ), 0u );
214 BOOST_CHECK_EQUAL( observed.load(), 42 );
215}
216
217
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")