25#include <condition_variable>
49 std::condition_variable changed;
50 std::map<std::string, SOURCE_COUNTS> sources;
56 static REGISTRY* instance =
new REGISTRY;
61thread_local bool t_onWorker =
false;
64void eraseIfIdle( REGISTRY& aReg,
const std::string& aKey )
66 auto it = aReg.sources.find( aKey );
68 if( it != aReg.sources.end() && it->second.slots == 0 && it->second.abandoned == 0 )
69 aReg.sources.erase( it );
74bool acquireSlot(
const std::string& aKey, std::chrono::steady_clock::time_point aDeadline,
75 const std::atomic<bool>& aStopping )
77 REGISTRY& reg = registry();
78 std::unique_lock lock( reg.m );
79 bool timedOut =
false;
85 eraseIfIdle( reg, aKey );
89 SOURCE_COUNTS& counts = reg.sources[aKey];
93 eraseIfIdle( reg, aKey );
105 eraseIfIdle( reg, aKey );
109 timedOut = reg.changed.wait_until( lock, aDeadline ) == std::cv_status::timeout;
114void returnSlot(
const std::string& aKey )
116 std::lock_guard lock( registry().m );
118 --registry().sources[aKey].slots;
119 eraseIfIdle( registry(), aKey );
120 registry().changed.notify_all();
128 typedef std::vector<std::vector<DB_VALUE>>
BATCH;
132 std::condition_variable
cv;
153 HANDLE wakeEvent = CreateEventW(
nullptr, FALSE, FALSE,
nullptr );
158 CloseHandle( wakeEvent );
169 SetEvent( wakeEvent );
174 template <
typename PRED>
175 void workerWait( std::unique_lock<std::mutex>& aLock, PRED aReady )
185 MsgWaitForMultipleObjectsEx( 1, &wakeEvent, INFINITE, QS_ALLINPUT, MWMO_INPUTAVAILABLE );
188 if( waited == WAIT_FAILED )
191 cv.wait( aLock, aReady );
197 while( PeekMessageW( &
message,
nullptr, 0, 0, PM_REMOVE ) )
210 cv.wait( aLock, aReady );
218 std::lock_guard registryLock( registry().
m );
219 ++registry().sources[
key].abandoned;
220 registry().changed.notify_all();
237 static void Main( std::shared_ptr<DB_WORKER_CORE> aCore, std::function<
bool()> aInit,
238 std::function<
void()> aExit );
251 std::shared_ptr<DB_WORKER_CORE>
core;
258 return m_core.abandoned.load() ||
m_core.rejected.load();
264 std::unique_lock lock(
m_core.m );
293 m_pending.emplace_back( std::move( aRow ) );
304 std::unique_lock lock(
m_core.m );
317 std::function<
void()> aExit )
326 ready = !aInit || aInit();
334 std::lock_guard lock( core.
m );
345 std::unique_lock lock( core.
m );
357 job = std::move( core.
job );
361 std::exception_ptr
error;
376 error = std::current_exception();
383 std::lock_guard lock( core.
m );
408 std::lock_guard lock( core.
m );
414 std::lock_guard registryLock( registry().
m );
416 --registry().sources[core.
key].abandoned;
417 eraseIfIdle( registry(), core.
key );
418 registry().changed.notify_all();
427 std::function<
void()> aThreadExit,
bool aPumpMessages,
428 std::function<
void()> aOnAbandon ) :
433 m_state->onAbandon = std::move( aOnAbandon );
434 m_state->init = std::move( aThreadInit );
435 m_state->exit = std::move( aThreadExit );
441 std::shared_ptr<DB_WORKER_CORE> core;
445 std::lock_guard lock(
m_state->m );
448 core = std::move(
m_state->core );
449 thread = std::move(
m_state->thread );
454 std::lock_guard registryLock( registry().m );
457 registry().changed.notify_all();
462 bool abandoned =
false;
465 const auto deadline = std::chrono::steady_clock::now() +
m_shutdownBound;
468 std::unique_lock lock( core->m );
470 if( core->busy && !core->abandoned )
472 core->cv.wait_until( lock, deadline,
486 && !core->cv.wait_until( lock, deadline,
495 abandoned = core->abandoned;
498 if( !thread.joinable() )
517 std::lock_guard lock(
m_state->m );
525 std::lock_guard lock(
m_state->m );
542 std::string
path = aPath;
544 std::replace(
path.begin(),
path.end(),
'\\',
'/' );
546 auto startsWith = [&](
const char* aPrefix )
548 return path.rfind( aPrefix, 0 ) == 0;
551 if( startsWith(
"//?/UNC/" ) || startsWith(
"//./UNC/" ) )
552 return "//" +
path.substr( 8 );
554 if( startsWith(
"//?/" ) || startsWith(
"//./" ) )
555 return path.substr( 4 );
567 if( aWindowsPath &&
path.size() >= 2 &&
path[1] ==
':' )
569 root =
path.substr( 0, 2 );
572 if(
path.size() > 2 &&
path[2] ==
'/' )
578 else if( !
path.empty() &&
path[0] ==
'/' )
581 root = aWindowsPath &&
path.rfind(
"//", 0 ) == 0 &&
path.size() > 2 &&
path[2] !=
'/' ?
"//" :
"/";
582 start =
path.find_first_not_of(
'/' );
584 if( start == std::string::npos )
588 std::vector<std::string> parts;
590 for(
size_t pos = start; pos <=
path.size(); )
592 size_t end =
path.find(
'/', pos );
594 if(
end == std::string::npos )
597 const std::string part =
path.substr( pos,
end - pos );
601 if( !parts.empty() && parts.back() !=
".." )
603 else if( root.empty() )
604 parts.push_back( part );
606 else if( !part.empty() && part !=
"." )
608 parts.push_back( part );
614 std::string key = root;
616 for(
size_t i = 0; i < parts.size(); ++i )
617 key += ( i ?
"/" :
"" ) + parts[i];
623 if( c >=
'A' && c <=
'Z' )
624 c =
static_cast<char>( c -
'A' +
'a' );
634 namespace fs = std::filesystem;
637 constexpr bool windows =
true;
639 constexpr bool windows =
false;
643 fs::path
path = fs::absolute( fs::path( std::u8string( aPath.begin(), aPath.end() ) ), ec );
646 path = fs::path( std::u8string( aPath.begin(), aPath.end() ) );
648 const std::u8string
text =
path.generic_u8string();
667 aBound,
nullptr, true );
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;
682 auto deadline = std::chrono::steady_clock::now() + aBound;
684 const bool slot = !aCleanup && acquireSlot( key, deadline, state->stopping );
686 if( !aCleanup && !slot )
692 auto giveUpSlot = [&]()
699 if( slot && deadline - std::chrono::steady_clock::now() < aBound / 2 )
706 std::shared_ptr<DB_WORKER_CORE> core;
709 std::lock_guard stateLock( state->m );
711 if( state->stopping )
718 if( !state->core || state->core->abandoned || state->core->initFailed )
720 if( state->thread.joinable() )
722 if( state->onAbandon && state->core && state->core->abandoned )
725 state->thread.detach();
728 state->core = std::make_shared<DB_WORKER_CORE>();
729 state->core->key = key;
736 catch(
const std::system_error& )
738 state->core->initFailed =
true;
748 std::unique_lock lock( core->m );
762 core->rejected =
false;
764 core->error =
nullptr;
765 core->job = std::move( aJob );
766 core->slotHeld = slot;
774 if( core->abandoned )
780 if( !core->queue.empty() )
782 if( std::chrono::steady_clock::now() >= deadline )
790 core->queue.pop_front();
795 bool rejected = core->rejected;
797 for( std::vector<DB_VALUE>& row : batch )
799 if( rejected || core->abandoned || !aSink || !*aSink )
804 ( *aSink )( std::move( row ) );
814 if( core->abandoned )
821 deadline = std::chrono::steady_clock::now() + aBound;
825 core->rejected =
true;
834 if( core->initFailed )
836 core->hasJob =
false;
844 if( core->cv.wait_until( lock, deadline ) == std::cv_status::timeout && core->queue.empty()
845 && !core->done && !core->initFailed )
853 std::exception_ptr error = core->error;
858 std::rethrow_exception( error );
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 Emit(std::vector< DB_VALUE > &&aRow)
Queues a row for the caller's sink.
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)
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
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