@@ -84,10 +84,15 @@ ResourceManager::ResourceManager()
84
84
detection_percent = 100 ;
85
85
detection_string = " " ;
86
86
detection_is_required = false ;
87
- InitThread = nullptr ;
88
- DetectDevicesThread = nullptr ;
89
87
dynamic_detectors_processed = false ;
90
88
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 );
91
96
92
97
SetupConfigurationDirectory ();
93
98
@@ -173,7 +178,13 @@ ResourceManager::~ResourceManager()
173
178
{
174
179
Cleanup ();
175
180
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)
177
188
{
178
189
DetectDevicesThread->join ();
179
190
delete DetectDevicesThread;
@@ -805,19 +816,7 @@ void ResourceManager::Cleanup()
805
816
delete bus;
806
817
}
807
818
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 ));
821
820
}
822
821
823
822
void ResourceManager::ProcessPreDetectionHooks ()
@@ -903,7 +902,8 @@ bool ResourceManager::ProcessPreDetection()
903
902
LOG_INFO (" Initializing HID interfaces: %s" , ((hid_status == 0 ) ? " Success" : " Failed" ));
904
903
905
904
/* -------------------------------------------------*\
906
- | Start the device detection thread |
905
+ | Mark the detection as ongoing |
906
+ | So the detection thread may proceed |
907
907
\*-------------------------------------------------*/
908
908
detection_is_required = true ;
909
909
@@ -916,13 +916,8 @@ void ResourceManager::DetectDevices()
916
916
{
917
917
if (ProcessPreDetection ())
918
918
{
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 ));
926
921
}
927
922
928
923
if (!detection_enabled)
@@ -960,7 +955,7 @@ void ResourceManager::DisableDetection()
960
955
detection_enabled = false ;
961
956
}
962
957
963
- void ResourceManager::DetectDevicesThreadFunction ()
958
+ void ResourceManager::DetectDevicesCoroutine ()
964
959
{
965
960
DetectDeviceMutex.lock ();
966
961
@@ -1637,10 +1632,10 @@ void ResourceManager::Initialize(bool tryConnect, bool detectDevices, bool start
1637
1632
start_server = startServer;
1638
1633
apply_post_options = applyPostOptions;
1639
1634
1640
- InitThread = new std::thread (&ResourceManager::InitThreadFunction , this );
1635
+ RunInBackgroundThread ( std::bind (&ResourceManager::InitCoroutine , this ) );
1641
1636
}
1642
1637
1643
- void ResourceManager::InitThreadFunction ()
1638
+ void ResourceManager::InitCoroutine ()
1644
1639
{
1645
1640
if (tryAutoConnect)
1646
1641
{
@@ -1670,7 +1665,8 @@ void ResourceManager::InitThreadFunction()
1670
1665
LOG_DEBUG (" [ResourceManager] Running standalone" );
1671
1666
if (ProcessPreDetection ())
1672
1667
{
1673
- DetectDevicesThreadFunction ();
1668
+ // We are currently in a coroutine, so run detection directly with no scheduling
1669
+ DetectDevicesCoroutine ();
1674
1670
}
1675
1671
}
1676
1672
else
@@ -1703,6 +1699,74 @@ void ResourceManager::InitThreadFunction()
1703
1699
init_finished = true ;
1704
1700
}
1705
1701
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
+
1706
1770
void ResourceManager::UpdateDetectorSettings ()
1707
1771
{
1708
1772
json detector_settings;
0 commit comments