KiCad PCB EDA Suite
Loading...
Searching...
No Matches
db_worker_thread.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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
20#include "db_worker_thread.h"
21
23
24#include <atomic>
25#include <condition_variable>
26#include <deque>
27#include <exception>
28#include <algorithm>
29#include <filesystem>
30#include <map>
31#include <mutex>
32
33#ifdef _WIN32
34#include <windows.h>
35#endif
36
37
38namespace
39{
40struct SOURCE_COUNTS
41{
42 size_t slots = 0;
43 size_t abandoned = 0;
44};
45
46struct REGISTRY
47{
48 std::mutex m;
49 std::condition_variable changed;
50 std::map<std::string, SOURCE_COUNTS> sources;
51};
52
53// Never destroyed, since an abandoned thread may return after static destruction has begun
54REGISTRY& registry()
55{
56 static REGISTRY* instance = new REGISTRY;
57
58 return *instance;
59}
60
61thread_local bool t_onWorker = false;
62
63
64void eraseIfIdle( REGISTRY& aReg, const std::string& aKey )
65{
66 auto it = aReg.sources.find( aKey );
67
68 if( it != aReg.sources.end() && it->second.slots == 0 && it->second.abandoned == 0 )
69 aReg.sources.erase( it );
70}
71
72
73// Waits until aDeadline for a slot while healthy jobs hold them, and refuses at once when abandoned jobs do
74bool acquireSlot( const std::string& aKey, std::chrono::steady_clock::time_point aDeadline,
75 const std::atomic<bool>& aStopping )
76{
77 REGISTRY& reg = registry();
78 std::unique_lock lock( reg.m );
79 bool timedOut = false;
80
81 for( ;; )
82 {
83 if( aStopping )
84 {
85 eraseIfIdle( reg, aKey );
86 return false;
87 }
88
89 SOURCE_COUNTS& counts = reg.sources[aKey];
90
91 if( counts.abandoned >= DB_WORKER_THREAD::MAX_ABANDONED )
92 {
93 eraseIfIdle( reg, aKey );
94 return false;
95 }
96
97 if( counts.slots < DB_WORKER_THREAD::MAX_ABANDONED )
98 {
99 ++counts.slots;
100 return true;
101 }
102
103 if( timedOut )
104 {
105 eraseIfIdle( reg, aKey );
106 return false;
107 }
108
109 timedOut = reg.changed.wait_until( lock, aDeadline ) == std::cv_status::timeout;
110 }
111}
112
113
114void returnSlot( const std::string& aKey )
115{
116 std::lock_guard lock( registry().m );
117
118 --registry().sources[aKey].slots;
119 eraseIfIdle( registry(), aKey );
120 registry().changed.notify_all();
121}
122} // namespace
123
124
127{
128 typedef std::vector<std::vector<DB_VALUE>> BATCH;
129
130 std::string key;
131 std::mutex m;
132 std::condition_variable cv;
133
134 std::function<void( DB_JOB_CONTEXT& )> job;
135 bool hasJob = false;
136 bool busy = false;
137 bool done = false;
138 bool stop = false;
139 std::atomic<bool> initFailed{ false };
140 std::atomic<bool> abandoned{ false };
141 std::atomic<bool> rejected{ false };
142 bool exited = false;
143
144 bool slotHeld = false;
145 std::deque<BATCH> queue;
146 size_t pushed = 0;
147 size_t consumed = 0;
148 std::exception_ptr error;
149
150 bool pumpMessages = false;
151
152#ifdef _WIN32
153 HANDLE wakeEvent = CreateEventW( nullptr, FALSE, FALSE, nullptr );
154
156 {
157 if( wakeEvent )
158 CloseHandle( wakeEvent );
159 }
160#endif
161
163 void wake()
164 {
165 cv.notify_all();
166
167#ifdef _WIN32
168 if( pumpMessages && wakeEvent )
169 SetEvent( wakeEvent );
170#endif
171 }
172
174 template <typename PRED>
175 void workerWait( std::unique_lock<std::mutex>& aLock, PRED aReady )
176 {
177#ifdef _WIN32
178 if( pumpMessages && wakeEvent )
179 {
180 while( !aReady() )
181 {
182 aLock.unlock();
183
184 const DWORD waited =
185 MsgWaitForMultipleObjectsEx( 1, &wakeEvent, INFINITE, QS_ALLINPUT, MWMO_INPUTAVAILABLE );
186
187 // A failed wait would spin, so fall back to the plain condition wait
188 if( waited == WAIT_FAILED )
189 {
190 aLock.lock();
191 cv.wait( aLock, aReady );
192 return;
193 }
194
195 MSG message;
196
197 while( PeekMessageW( &message, nullptr, 0, 0, PM_REMOVE ) )
198 {
199 TranslateMessage( &message );
200 DispatchMessageW( &message );
201 }
202
203 aLock.lock();
204 }
205
206 return;
207 }
208#endif
209
210 cv.wait( aLock, aReady );
211 }
212
214 void abandon()
215 {
216 if( !abandoned.exchange( true ) )
217 {
218 std::lock_guard registryLock( registry().m );
219 ++registry().sources[key].abandoned;
220 registry().changed.notify_all();
221 }
222
223 wake();
224 }
225
228 {
229 if( !slotHeld )
230 return;
231
232 slotHeld = false;
233 returnSlot( key );
234 }
235
237 static void Main( std::shared_ptr<DB_WORKER_CORE> aCore, std::function<bool()> aInit,
238 std::function<void()> aExit );
239};
240
241
244{
245 std::mutex m;
246 std::atomic<bool> stopping{ false };
247 std::function<bool()> init;
248 std::function<void()> exit;
249 std::function<void()> onAbandon;
250
251 std::shared_ptr<DB_WORKER_CORE> core;
252 std::thread thread;
253};
254
255
257{
258 return m_core.abandoned.load() || m_core.rejected.load();
259}
260
261
263{
264 std::unique_lock lock( m_core.m );
265
266 m_core.workerWait( lock,
267 [&]()
268 {
269 return m_core.queue.size() < MAX_QUEUED_BATCHES || Cancelled();
270 } );
271
272 if( Cancelled() )
273 {
274 m_pending.clear();
275 return false;
276 }
277
278 m_core.queue.push_back( std::move( m_pending ) );
279 m_pending.clear();
280 m_pending.reserve( BATCH_ROWS );
281 ++m_core.pushed;
282 m_core.wake();
283
284 return true;
285}
286
287
288bool DB_JOB_CONTEXT::Emit( std::vector<DB_VALUE>&& aRow )
289{
290 if( Cancelled() )
291 return false;
292
293 m_pending.emplace_back( std::move( aRow ) );
294
295 return m_pending.size() < BATCH_ROWS || pushBatch();
296}
297
298
300{
301 if( !m_pending.empty() && !pushBatch() )
302 return false;
303
304 std::unique_lock lock( m_core.m );
305
306 m_core.workerWait( lock,
307 [&]()
308 {
309 return m_core.consumed == m_core.pushed || Cancelled();
310 } );
311
312 return !Cancelled();
313}
314
315
316void DB_WORKER_CORE::Main( std::shared_ptr<DB_WORKER_CORE> aCore, std::function<bool()> aInit,
317 std::function<void()> aExit )
318{
319 DB_WORKER_CORE& core = *aCore;
320 bool ready = true;
321
322 t_onWorker = true;
323
324 try
325 {
326 ready = !aInit || aInit();
327 }
328 catch( ... )
329 {
330 ready = false;
331 }
332
333 {
334 std::lock_guard lock( core.m );
335
336 core.initFailed = !ready;
337 core.wake();
338 }
339
340 while( ready )
341 {
342 std::function<void( DB_JOB_CONTEXT& )> job;
343
344 {
345 std::unique_lock lock( core.m );
346
347 core.workerWait( lock,
348 [&]()
349 {
350 return core.hasJob || core.stop || core.abandoned;
351 } );
352
353 // A job given up on before it began has no one waiting for it
354 if( !core.hasJob || core.abandoned )
355 break;
356
357 job = std::move( core.job );
358 core.hasJob = false;
359 }
360
361 std::exception_ptr error;
362
363 {
364 DB_JOB_CONTEXT context( core );
365
366 try
367 {
368 job( context );
369
370 // Rows a careless job left unflushed are still the caller's
371 if( !context.m_pending.empty() )
372 context.pushBatch();
373 }
374 catch( ... )
375 {
376 error = std::current_exception();
377 }
378 }
379
380 // Whatever the job owns is freed here, before the caller can see the job as finished
381 job = nullptr;
382
383 std::lock_guard lock( core.m );
384
385 core.error = error;
386 core.done = true;
387 core.busy = false;
388
389 // An abandoned job keeps its slot until the thread has returned
390 if( !core.abandoned )
391 core.releaseSlot();
392
393 core.wake();
394
395 if( core.abandoned )
396 break;
397 }
398
399 try
400 {
401 if( ready && aExit )
402 aExit();
403 }
404 catch( ... )
405 {
406 }
407
408 std::lock_guard lock( core.m );
409
410 core.releaseSlot();
411
412 if( core.abandoned )
413 {
414 std::lock_guard registryLock( registry().m );
415
416 --registry().sources[core.key].abandoned;
417 eraseIfIdle( registry(), core.key );
418 registry().changed.notify_all();
419 }
420
421 core.exited = true;
422 core.wake();
423}
424
425
426DB_WORKER_THREAD::DB_WORKER_THREAD( std::string aSourceKey, std::function<bool()> aThreadInit,
427 std::function<void()> aThreadExit, bool aPumpMessages,
428 std::function<void()> aOnAbandon ) :
429 m_key( std::move( aSourceKey ) ),
430 m_pumpMessages( aPumpMessages ),
431 m_state( std::make_shared<DB_WORKER_STATE>() )
432{
433 m_state->onAbandon = std::move( aOnAbandon );
434 m_state->init = std::move( aThreadInit );
435 m_state->exit = std::move( aThreadExit );
436}
437
438
440{
441 std::shared_ptr<DB_WORKER_CORE> core;
442 std::thread thread;
443
444 {
445 std::lock_guard lock( m_state->m );
446
447 m_state->stopping = true;
448 core = std::move( m_state->core );
449 thread = std::move( m_state->thread );
450 }
451
452 // Callers waiting for a slot re-check the flag under the registry lock, so this wake cannot be lost
453 {
454 std::lock_guard registryLock( registry().m );
455 }
456
457 registry().changed.notify_all();
458
459 if( !core )
460 return;
461
462 bool abandoned = false;
463
464 // One deadline covers the job and the exit hook, which can block on a lock held elsewhere
465 const auto deadline = std::chrono::steady_clock::now() + m_shutdownBound;
466
467 {
468 std::unique_lock lock( core->m );
469
470 if( core->busy && !core->abandoned )
471 {
472 core->cv.wait_until( lock, deadline,
473 [&]()
474 {
475 return !core->busy;
476 } );
477
478 if( core->busy )
479 core->abandon();
480 }
481
482 core->stop = true;
483 core->wake();
484
485 if( !core->abandoned
486 && !core->cv.wait_until( lock, deadline,
487 [&]()
488 {
489 return core->exited;
490 } ) )
491 {
492 core->abandon();
493 }
494
495 abandoned = core->abandoned;
496 }
497
498 if( !thread.joinable() )
499 return;
500
501 if( abandoned )
502 {
503 if( m_state->onAbandon )
504 m_state->onAbandon();
505
506 thread.detach();
507 }
508 else
509 {
510 thread.join();
511 }
512}
513
514
516{
517 std::lock_guard lock( m_state->m );
518
519 return !m_state->core || ( !m_state->core->abandoned && !m_state->core->initFailed );
520}
521
522
524{
525 std::lock_guard lock( m_state->m );
526
527 return m_state->core && !m_state->core->abandoned && !m_state->core->initFailed;
528}
529
530
531std::chrono::milliseconds DB_WORKER_THREAD::CallerBound( int aTimeoutSeconds )
532{
533 return std::chrono::seconds( DatabaseFileWaitSeconds( aTimeoutSeconds ) );
534}
535
536
537std::string DB_WORKER_THREAD::NormalizePath( const std::string& aPath, bool aWindowsPath )
538{
539 if( !aWindowsPath )
540 return aPath;
541
542 std::string path = aPath;
543
544 std::replace( path.begin(), path.end(), '\\', '/' );
545
546 auto startsWith = [&]( const char* aPrefix )
547 {
548 return path.rfind( aPrefix, 0 ) == 0;
549 };
550
551 if( startsWith( "//?/UNC/" ) || startsWith( "//./UNC/" ) )
552 return "//" + path.substr( 8 );
553
554 if( startsWith( "//?/" ) || startsWith( "//./" ) )
555 return path.substr( 4 );
556
557 return path;
558}
559
560
561std::string DB_WORKER_THREAD::SourceKeyFor( const std::string& aPath, bool aWindowsPath )
562{
563 const std::string path = NormalizePath( aPath, aWindowsPath );
564 std::string root;
565 size_t start = 0;
566
567 if( aWindowsPath && path.size() >= 2 && path[1] == ':' )
568 {
569 root = path.substr( 0, 2 );
570 start = 2;
571
572 if( path.size() > 2 && path[2] == '/' )
573 {
574 root += '/';
575 start = 3;
576 }
577 }
578 else if( !path.empty() && path[0] == '/' )
579 {
580 // A leading double slash is a UNC root on Windows and the same root as one slash elsewhere
581 root = aWindowsPath && path.rfind( "//", 0 ) == 0 && path.size() > 2 && path[2] != '/' ? "//" : "/";
582 start = path.find_first_not_of( '/' );
583
584 if( start == std::string::npos )
585 start = path.size();
586 }
587
588 std::vector<std::string> parts;
589
590 for( size_t pos = start; pos <= path.size(); )
591 {
592 size_t end = path.find( '/', pos );
593
594 if( end == std::string::npos )
595 end = path.size();
596
597 const std::string part = path.substr( pos, end - pos );
598
599 if( part == ".." )
600 {
601 if( !parts.empty() && parts.back() != ".." )
602 parts.pop_back();
603 else if( root.empty() )
604 parts.push_back( part );
605 }
606 else if( !part.empty() && part != "." )
607 {
608 parts.push_back( part );
609 }
610
611 pos = end + 1;
612 }
613
614 std::string key = root;
615
616 for( size_t i = 0; i < parts.size(); ++i )
617 key += ( i ? "/" : "" ) + parts[i];
618
619 if( aWindowsPath )
620 {
621 for( char& c : key )
622 {
623 if( c >= 'A' && c <= 'Z' )
624 c = static_cast<char>( c - 'A' + 'a' );
625 }
626 }
627
628 return key;
629}
630
631
632std::string DB_WORKER_THREAD::SourceKeyFor( const std::string& aPath )
633{
634 namespace fs = std::filesystem;
635
636#ifdef _WIN32
637 constexpr bool windows = true;
638#else
639 constexpr bool windows = false;
640#endif
641
642 std::error_code ec;
643 fs::path path = fs::absolute( fs::path( std::u8string( aPath.begin(), aPath.end() ) ), ec );
644
645 if( ec )
646 path = fs::path( std::u8string( aPath.begin(), aPath.end() ) );
647
648 const std::u8string text = path.generic_u8string();
649
650 return SourceKeyFor( std::string( text.begin(), text.end() ), windows );
651}
652
653
655{
656 return t_onWorker;
657}
658
659
660bool DB_WORKER_THREAD::RunCleanup( std::function<void()> aCleanup, std::chrono::milliseconds aBound )
661{
662 return runJob(
663 [cleanup = std::move( aCleanup )]( DB_JOB_CONTEXT& )
664 {
665 cleanup();
666 },
667 aBound, nullptr, true );
668}
669
670
671bool DB_WORKER_THREAD::runJob( std::function<void( DB_JOB_CONTEXT& )> aJob, std::chrono::milliseconds aBound,
672 const DB_ROW_SINK* aSink, bool aCleanup )
673{
674 // The destructor can end the admission wait, so after it only these local copies are used
675 const std::shared_ptr<DB_WORKER_STATE> state = m_state;
676 const std::shared_ptr<OUTCOME> outcome = m_outcome;
677 const std::string key = m_key;
678
679 *outcome = OUTCOME::OK;
680
681 // One deadline covers admission and the job, and only a delivered batch moves it
682 auto deadline = std::chrono::steady_clock::now() + aBound;
683
684 const bool slot = !aCleanup && acquireSlot( key, deadline, state->stopping );
685
686 if( !aCleanup && !slot )
687 {
688 *outcome = state->stopping ? OUTCOME::UNAVAILABLE : OUTCOME::REFUSED;
689 return false;
690 }
691
692 auto giveUpSlot = [&]()
693 {
694 if( slot )
695 returnSlot( key );
696 };
697
698 // Waiting for a slot spent the bound, so a job started now would be abandoned almost at once
699 if( slot && deadline - std::chrono::steady_clock::now() < aBound / 2 )
700 {
701 giveUpSlot();
702 *outcome = OUTCOME::TOO_LATE;
703 return false;
704 }
705
706 std::shared_ptr<DB_WORKER_CORE> core;
707
708 {
709 std::lock_guard stateLock( state->m );
710
711 if( state->stopping )
712 {
713 giveUpSlot();
714 *outcome = OUTCOME::UNAVAILABLE;
715 return false;
716 }
717
718 if( !state->core || state->core->abandoned || state->core->initFailed )
719 {
720 if( state->thread.joinable() )
721 {
722 if( state->onAbandon && state->core && state->core->abandoned )
723 state->onAbandon();
724
725 state->thread.detach();
726 }
727
728 state->core = std::make_shared<DB_WORKER_CORE>();
729 state->core->key = key;
730 state->core->pumpMessages = m_pumpMessages;
731
732 try
733 {
734 state->thread = std::thread( &DB_WORKER_CORE::Main, state->core, state->init, state->exit );
735 }
736 catch( const std::system_error& )
737 {
738 state->core->initFailed = true;
739 giveUpSlot();
740 *outcome = OUTCOME::UNAVAILABLE;
741 return false;
742 }
743 }
744
745 core = state->core;
746 }
747
748 std::unique_lock lock( core->m );
749
750 // The owner can have stopped the worker since the snapshot, and no thread would take the job
751 if( core->stop )
752 {
753 lock.unlock();
754 giveUpSlot();
755 *outcome = OUTCOME::UNAVAILABLE;
756 return false;
757 }
758
759 core->queue.clear();
760 core->pushed = 0;
761 core->consumed = 0;
762 core->rejected = false;
763 core->done = false;
764 core->error = nullptr;
765 core->job = std::move( aJob );
766 core->slotHeld = slot;
767 core->hasJob = true;
768 core->busy = true;
769 core->wake();
770
771 for( ;; )
772 {
773 // Giving up wins over everything the job may have finished meanwhile
774 if( core->abandoned )
775 {
776 *outcome = OUTCOME::TIMED_OUT;
777 return false;
778 }
779
780 if( !core->queue.empty() )
781 {
782 if( std::chrono::steady_clock::now() >= deadline )
783 {
784 core->abandon();
785 *outcome = OUTCOME::TIMED_OUT;
786 return false;
787 }
788
789 DB_WORKER_CORE::BATCH batch = std::move( core->queue.front() );
790 core->queue.pop_front();
791 core->wake();
792
793 lock.unlock();
794
795 bool rejected = core->rejected;
796
797 for( std::vector<DB_VALUE>& row : batch )
798 {
799 if( rejected || core->abandoned || !aSink || !*aSink )
800 break;
801
802 try
803 {
804 ( *aSink )( std::move( row ) );
805 }
806 catch( ... )
807 {
808 rejected = true;
809 }
810 }
811
812 lock.lock();
813
814 if( core->abandoned )
815 {
816 *outcome = OUTCOME::TIMED_OUT;
817 return false;
818 }
819
820 // Each delivered batch is progress, so the bound runs from here and excludes the sink
821 deadline = std::chrono::steady_clock::now() + aBound;
822 ++core->consumed;
823
824 if( rejected )
825 core->rejected = true;
826
827 core->wake();
828 continue;
829 }
830
831 if( core->done )
832 break;
833
834 if( core->initFailed )
835 {
836 core->hasJob = false;
837 core->busy = false;
838 core->job = nullptr;
839 core->releaseSlot();
840 *outcome = OUTCOME::UNAVAILABLE;
841 return false;
842 }
843
844 if( core->cv.wait_until( lock, deadline ) == std::cv_status::timeout && core->queue.empty()
845 && !core->done && !core->initFailed )
846 {
847 core->abandon();
848 *outcome = OUTCOME::TIMED_OUT;
849 return false;
850 }
851 }
852
853 std::exception_ptr error = core->error;
854
855 lock.unlock();
856
857 if( error )
858 std::rethrow_exception( error );
859
860 return true;
861}
What a job running on a DB_WORKER_THREAD uses to hand rows to the caller.
static constexpr size_t BATCH_ROWS
static constexpr size_t MAX_QUEUED_BATCHES
bool Flush()
Delivers the rows still held back and waits until the caller's sink has taken every one.
bool Cancelled() const
bool Emit(std::vector< DB_VALUE > &&aRow)
Queues a row for the caller's sink.
DB_WORKER_CORE & m_core
std::vector< std::vector< DB_VALUE > > m_pending
bool HasLiveThread() const
~DB_WORKER_THREAD()
Waits up to the shutdown bound for a running job, then abandons it.
static constexpr size_t MAX_ABANDONED
std::chrono::milliseconds m_shutdownBound
bool runJob(std::function< void(DB_JOB_CONTEXT &)> aJob, std::chrono::milliseconds aBound, const DB_ROW_SINK *aSink, bool aCleanup)
@ UNAVAILABLE
the worker thread could not start
@ REFUSED
two abandoned jobs hold the source, or no slot freed within the bound
@ TOO_LATE
a slot freed with under half the bound left, so nothing was started
@ TIMED_OUT
no progress within the bound, so the job was abandoned
static std::chrono::milliseconds CallerBound(int aTimeoutSeconds)
std::shared_ptr< OUTCOME > m_outcome
bool RunCleanup(std::function< void()> aCleanup, std::chrono::milliseconds aBound)
Runs aCleanup on a worker thread for work that must not happen on the caller, such as closing a conne...
static std::string SourceKeyFor(const std::string &aPath)
std::shared_ptr< DB_WORKER_STATE > m_state
static std::string NormalizePath(const std::string &aPath, bool aWindowsPath)
Rewrites the spellings of one file that Windows treats alike into one form, without touching the file...
DB_WORKER_THREAD(std::string aSourceKey, std::function< bool()> aThreadInit=nullptr, std::function< void()> aThreadExit=nullptr, bool aPumpMessages=false, std::function< void()> aOnAbandon=nullptr)
static bool OnWorkerThread()
std::function< void(std::vector< DB_VALUE > &&)> DB_ROW_SINK
Receives one row at a time, in the order of the labels the read produced.
int DatabaseFileWaitSeconds(int aTimeoutSeconds)
std::string message
STL namespace.
State shared by the owner and the worker thread; the thread keeps it alive after the owner is gone.
void wake()
Wakes the owner and the worker, which may be waiting in either kind of wait.
static void Main(std::shared_ptr< DB_WORKER_CORE > aCore, std::function< bool()> aInit, std::function< void()> aExit)
The thread body; owns aCore until the thread ends.
std::atomic< bool > rejected
std::deque< BATCH > queue
std::atomic< bool > abandoned
bool pumpMessages
Windows only, the worker dispatches window messages whenever it waits.
bool exited
Set once the exit hook has returned.
std::function< void(DB_JOB_CONTEXT &)> job
void abandon()
Marks the running job as given up on, with m held by the caller.
std::condition_variable cv
std::atomic< bool > initFailed
std::exception_ptr error
void releaseSlot()
Returns the job's admission slot, with m held by the caller.
void workerWait(std::unique_lock< std::mutex > &aLock, PRED aReady)
Waits on the worker thread for aReady with m held, dispatching window messages meanwhile if asked to.
std::vector< std::vector< DB_VALUE > > BATCH
The part of a worker that outlives its owner while a caller is still inside Run.
std::function< void()> onAbandon
std::function< bool()> init
std::atomic< bool > stopping
std::function< void()> exit
std::shared_ptr< DB_WORKER_CORE > core
std::string path
VECTOR2I end
typedef HANDLE