Skip to content

Commit d1f90e1

Browse files
k1-801CalcProgrammer1
authored andcommitted
[Review needed] Fix Rescan Crash on MacOS
1 parent 20ae2d3 commit d1f90e1

File tree

2 files changed

+107
-32
lines changed

2 files changed

+107
-32
lines changed

ResourceManager.cpp

Lines changed: 92 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,15 @@ ResourceManager::ResourceManager()
8484
detection_percent = 100;
8585
detection_string = "";
8686
detection_is_required = false;
87-
InitThread = nullptr;
88-
DetectDevicesThread = nullptr;
8987
dynamic_detectors_processed = false;
9088
init_finished = false;
89+
background_thread_running = true;
90+
91+
/*-------------------------------------------------------------------------*\
92+
| Start the background detection thread in advance; it will be suspended |
93+
| until necessary |
94+
\*-------------------------------------------------------------------------*/
95+
DetectDevicesThread = new std::thread(&ResourceManager::BackgroundThreadFunction, this);
9196

9297
SetupConfigurationDirectory();
9398

@@ -173,7 +178,13 @@ ResourceManager::~ResourceManager()
173178
{
174179
Cleanup();
175180

176-
if(InitThread)
181+
// Mark the background detection thread as not running
182+
// And then wake it up so it knows that it has to stop
183+
background_thread_running = false;
184+
BackgroundFunctionStartTrigger.notify_one();
185+
186+
// Stop the background thread
187+
if(DetectDevicesThread)
177188
{
178189
DetectDevicesThread->join();
179190
delete DetectDevicesThread;
@@ -805,19 +816,7 @@ void ResourceManager::Cleanup()
805816
delete bus;
806817
}
807818

808-
/*-------------------------------------------------*\
809-
| Cleanup HID interface |
810-
\*-------------------------------------------------*/
811-
int hid_status = hid_exit();
812-
813-
LOG_DEBUG("Closing HID interfaces: %s", ((hid_status == 0) ? "Success" : "Failed"));
814-
815-
if(DetectDevicesThread)
816-
{
817-
DetectDevicesThread->join();
818-
delete DetectDevicesThread;
819-
DetectDevicesThread = nullptr;
820-
}
819+
RunInBackgroundThread(std::bind(&ResourceManager::HidExitCoroutine, this));
821820
}
822821

823822
void ResourceManager::ProcessPreDetectionHooks()
@@ -903,7 +902,8 @@ bool ResourceManager::ProcessPreDetection()
903902
LOG_INFO("Initializing HID interfaces: %s", ((hid_status == 0) ? "Success" : "Failed"));
904903

905904
/*-------------------------------------------------*\
906-
| Start the device detection thread |
905+
| Mark the detection as ongoing |
906+
| So the detection thread may proceed |
907907
\*-------------------------------------------------*/
908908
detection_is_required = true;
909909

@@ -916,13 +916,8 @@ void ResourceManager::DetectDevices()
916916
{
917917
if(ProcessPreDetection())
918918
{
919-
DetectDevicesThread = new std::thread(&ResourceManager::DetectDevicesThreadFunction, this);
920-
921-
/*-------------------------------------------------*\
922-
| Release the current thread to allow detection |
923-
| thread to start |
924-
\*-------------------------------------------------*/
925-
std::this_thread::sleep_for(1ms);
919+
// Run the detection coroutine
920+
RunInBackgroundThread(std::bind(&ResourceManager::DetectDevicesCoroutine, this));
926921
}
927922

928923
if(!detection_enabled)
@@ -960,7 +955,7 @@ void ResourceManager::DisableDetection()
960955
detection_enabled = false;
961956
}
962957

963-
void ResourceManager::DetectDevicesThreadFunction()
958+
void ResourceManager::DetectDevicesCoroutine()
964959
{
965960
DetectDeviceMutex.lock();
966961

@@ -1637,10 +1632,10 @@ void ResourceManager::Initialize(bool tryConnect, bool detectDevices, bool start
16371632
start_server = startServer;
16381633
apply_post_options = applyPostOptions;
16391634

1640-
InitThread = new std::thread(&ResourceManager::InitThreadFunction, this);
1635+
RunInBackgroundThread(std::bind(&ResourceManager::InitCoroutine, this));
16411636
}
16421637

1643-
void ResourceManager::InitThreadFunction()
1638+
void ResourceManager::InitCoroutine()
16441639
{
16451640
if(tryAutoConnect)
16461641
{
@@ -1670,7 +1665,8 @@ void ResourceManager::InitThreadFunction()
16701665
LOG_DEBUG("[ResourceManager] Running standalone");
16711666
if(ProcessPreDetection())
16721667
{
1673-
DetectDevicesThreadFunction();
1668+
// We are currently in a coroutine, so run detection directly with no scheduling
1669+
DetectDevicesCoroutine();
16741670
}
16751671
}
16761672
else
@@ -1703,6 +1699,74 @@ void ResourceManager::InitThreadFunction()
17031699
init_finished = true;
17041700
}
17051701

1702+
void ResourceManager::HidExitCoroutine()
1703+
{
1704+
/*-------------------------------------------------*\
1705+
| Cleanup HID interface |
1706+
| WARNING: may not be ran from any other thread!!! |
1707+
\*-------------------------------------------------*/
1708+
int hid_status = hid_exit();
1709+
1710+
LOG_DEBUG("Closing HID interfaces: %s", ((hid_status == 0) ? "Success" : "Failed"));
1711+
}
1712+
1713+
void ResourceManager::RunInBackgroundThread(std::function<void()> coroutine)
1714+
{
1715+
if(std::this_thread::get_id() == DetectDevicesThread->get_id())
1716+
{
1717+
// We are already in the background thread - don't schedule the call, run it immediately
1718+
coroutine();
1719+
}
1720+
else
1721+
{
1722+
BackgroundThreadStateMutex.lock();
1723+
if(ScheduledBackgroundFunction != nullptr)
1724+
{
1725+
LOG_WARNING("Detection coroutine: assigned a new coroutine when one was already scheduled - probably two rescan events sent at once");
1726+
}
1727+
ScheduledBackgroundFunction = coroutine;
1728+
BackgroundThreadStateMutex.unlock();
1729+
BackgroundFunctionStartTrigger.notify_one();
1730+
}
1731+
}
1732+
1733+
void ResourceManager::BackgroundThreadFunction()
1734+
{
1735+
// The background thread that runs scheduled coroutines when applicable
1736+
// Stays asleep if nothing is scheduled
1737+
// NOTE: this thread owns the HIDAPI library internal objects on MacOS
1738+
// hid_init and hid_exit may not be called outside of this thread
1739+
// calling hid_exit outside of this thread WILL cause an immediate CRASH on MacOS
1740+
// BackgroundThreadStateMutex will be UNLOCKED as long as the thread is suspended
1741+
// It locks automatically when any coroutine is running
1742+
// However, it seems to be necessary to be separate from the DeviceDetectionMutex, even though their states are nearly identical
1743+
1744+
std::unique_lock lock(BackgroundThreadStateMutex);
1745+
while(background_thread_running)
1746+
{
1747+
if(ScheduledBackgroundFunction)
1748+
{
1749+
std::function<void()> coroutine = nullptr;
1750+
std::swap(ScheduledBackgroundFunction, coroutine);
1751+
try
1752+
{
1753+
coroutine();
1754+
}
1755+
catch(std::exception& e)
1756+
{
1757+
LOG_ERROR("Unhandled exception in coroutine; e.what(): %s", e.what());
1758+
}
1759+
catch(...)
1760+
{
1761+
LOG_ERROR("Unhandled exception in coroutine");
1762+
}
1763+
}
1764+
// This line will cause the thread to suspend until the condition variable is triggered
1765+
// NOTE: it may be subject to "spurious wakeups"
1766+
BackgroundFunctionStartTrigger.wait(lock);
1767+
}
1768+
}
1769+
17061770
void ResourceManager::UpdateDetectorSettings()
17071771
{
17081772
json detector_settings;

ResourceManager.h

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,14 +227,22 @@ class ResourceManager: public ResourceManagerInterface
227227
void WaitForDeviceDetection();
228228

229229
private:
230-
void DetectDevicesThreadFunction();
231230
void UpdateDetectorSettings();
232231
void SetupConfigurationDirectory();
233232
bool AttemptLocalConnection();
234-
void InitThreadFunction();
235233
bool ProcessPreDetection();
236234
void ProcessPostDetection();
237235
bool IsAnyDimmDetectorEnabled(json &detector_settings);
236+
void RunInBackgroundThread(std::function<void()>);
237+
void BackgroundThreadFunction();
238+
239+
/*-------------------------------------------------------------------------------------*\
240+
| Functions that must be run in the background thread |
241+
| These are not related to STL coroutines, yet this name is the most convenient |
242+
\*-------------------------------------------------------------------------------------*/
243+
void InitCoroutine();
244+
void DetectDevicesCoroutine();
245+
void HidExitCoroutine();
238246

239247
/*-------------------------------------------------------------------------------------*\
240248
| Static pointer to shared instance of ResourceManager |
@@ -318,10 +326,13 @@ class ResourceManager: public ResourceManagerInterface
318326
/*-------------------------------------------------------------------------------------*\
319327
| Detection Thread and Detection State |
320328
\*-------------------------------------------------------------------------------------*/
321-
std::thread * DetectDevicesThread; // Used for rescan
322-
std::thread * InitThread; // Used for initial scan, initial network scan, server startup
329+
std::thread * DetectDevicesThread;
323330
std::mutex DetectDeviceMutex;
331+
std::function<void()> ScheduledBackgroundFunction;
332+
std::mutex BackgroundThreadStateMutex;
333+
std::condition_variable BackgroundFunctionStartTrigger; // NOTE: wakes up the background detection thread
324334

335+
std::atomic<bool> background_thread_running;
325336
std::atomic<bool> detection_is_required;
326337
std::atomic<unsigned int> detection_percent;
327338
std::atomic<unsigned int> detection_prev_size;

0 commit comments

Comments
 (0)