@@ -93,10 +93,13 @@ ControllerManager::ControllerManager(UserSettingsPointer pConfig)
9393 : QObject(),
9494 m_pConfig(pConfig),
9595 // WARNING: Do not parent m_pControllerLearningEventFilter to
96- // ControllerManager because the CM is moved to its own thread and runs
97- // its own event loop.
98- m_pControllerLearningEventFilter(new ControllerLearningEventFilter()),
96+ // ControllerManager because the CM, together with all its children,
97+ // is moved to the ControllerManager thread (m_pThread) and
98+ // runs its own event loop there.
99+ m_pControllerLearningEventFilter(
100+ std::make_unique<ControllerLearningEventFilter>()),
99101 m_pollTimer(this ),
102+ m_pThread(std::make_unique<QThread>()),
100103 m_skipPoll(false ) {
101104 qRegisterMetaType<std::shared_ptr<LegacyControllerMapping>>(
102105 " std::shared_ptr<LegacyControllerMapping>" );
@@ -111,14 +114,14 @@ ControllerManager::ControllerManager(UserSettingsPointer pConfig)
111114 m_pollTimer.setInterval (kPollInterval .toIntegerMillis ());
112115 connect (&m_pollTimer, &QTimer::timeout, this , &ControllerManager::slotPollDevices);
113116
114- m_pThread = new QThread;
115- m_pThread->setObjectName (" Controller" );
117+ m_pThread->setObjectName (" ControllerManager" );
116118
117- // Moves all children (including the poll timer) to m_pThread
118- moveToThread (m_pThread);
119+ // Move the entire ControllerManager object and all its children (including
120+ // the poll timer) onto the ControllerManager thread.
121+ moveToThread (m_pThread.get ());
119122
120- // Controller processing needs to be prioritized since it can affect the
121- // audio directly, like when scratching
123+ // The ControllerManager thread is high-priority because controller input can
124+ // affect audio output directly (e.g. scratching).
122125 m_pThread->start (QThread::HighPriority);
123126
124127 connect (this , &ControllerManager::requestInitialize, this , &ControllerManager::slotInitialize);
@@ -129,77 +132,96 @@ ControllerManager::ControllerManager(UserSettingsPointer pConfig)
129132 connect (this , &ControllerManager::requestShutdown, this , &ControllerManager::slotShutdown);
130133
131134 // Signal that we should run slotInitialize once our event loop has started
132- // up.
133- emit requestInitialize (); // clazy:exclude=incorrect-emit
135+ // up. invokeMethod with QueuedConnection will post an event to our event loop,
136+ // so slotInitialize will not run until after the ControllerManager thread is fully
137+ // started and ready to process events.
138+ QMetaObject::invokeMethod (this , &ControllerManager::slotInitialize, Qt::QueuedConnection);
134139}
135140
136141ControllerManager::~ControllerManager () {
137- emit requestShutdown ();
142+ // slotShutdown() closes controllers, deletes enumerators and calls
143+ // m_pThread->quit(). We must wait for the thread to finish before our
144+ // members (m_pollTimer, m_pControllerLearningEventFilter, …) are destroyed,
145+ // because they may still be accessed by the thread's event loop.
146+ emit requestShutdown (); // clazy:exclude=incorrect-emit
138147 m_pThread->wait ();
139- delete m_pThread;
140- delete m_pControllerLearningEventFilter;
148+ // m_pThread and m_pControllerLearningEventFilter are released by unique_ptr
149+ // after this point, in reverse declaration order.
141150}
142151
143152ControllerLearningEventFilter* ControllerManager::getControllerLearningEventFilter () const {
144- return m_pControllerLearningEventFilter;
153+ return m_pControllerLearningEventFilter. get () ;
145154}
146155
147156void ControllerManager::slotInitialize () {
157+ DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY ();
148158 qDebug () << " ControllerManager:slotInitialize" ;
149159
150160 // Initialize mapping info parsers. This object is only for use in the main
151161 // thread. Do not touch it from within ControllerManager.
152- m_pMainThreadUserMappingEnumerator = QSharedPointer<MappingInfoEnumerator>(
153- new MappingInfoEnumerator (userMappingsPath (m_pConfig)));
154- m_pMainThreadSystemMappingEnumerator = QSharedPointer<MappingInfoEnumerator>(
155- new MappingInfoEnumerator (resourceMappingsPath (m_pConfig)));
162+ m_pMainThreadUserMappingEnumerator =
163+ QSharedPointer<MappingInfoEnumerator>::create (
164+ userMappingsPath (m_pConfig));
165+ m_pMainThreadSystemMappingEnumerator =
166+ QSharedPointer<MappingInfoEnumerator>::create (
167+ resourceMappingsPath (m_pConfig));
156168
157169 // Instantiate all enumerators. Enumerators can take a long time to
158170 // construct since they interact with host MIDI APIs.
171+ {
172+ auto locker = lockMutex (&m_mutex);
159173#ifdef __PORTMIDI__
160- m_enumerators.append ( new PortMidiEnumerator (m_pConfig));
174+ m_enumerators.push_back (std::make_unique< PortMidiEnumerator> (m_pConfig));
161175#endif
162176#ifdef __HSS1394__
163- m_enumerators.append ( new Hss1394Enumerator ());
177+ m_enumerators.push_back (std::make_unique< Hss1394Enumerator> ());
164178#endif
165179#ifdef __BULK__
166- m_enumerators.append ( new BulkEnumerator ());
180+ m_enumerators.push_back (std::make_unique< BulkEnumerator> ());
167181#endif
168182#ifdef __HID__
169- m_enumerators.append ( new HidEnumerator ());
183+ m_enumerators.push_back (std::make_unique< HidEnumerator> ());
170184#endif
185+ } // Mutex locker released here
171186 emit initialized ();
172187}
173188
174189void ControllerManager::slotShutdown () {
190+ DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY ();
175191 stopPolling ();
176192
177193 // Clear m_enumerators before deleting the enumerators to prevent other code
178- // paths from accessing them.
194+ // paths from accessing them during teardown .
179195 auto locker = lockMutex (&m_mutex);
180- QList< ControllerEnumerator*> enumerators = m_enumerators;
181- m_enumerators. clear ();
196+ std::vector<std::unique_ptr< ControllerEnumerator>> enumerators =
197+ std::move (m_enumerators); // m_enumerators is guaranteed empty after move
182198 locker.unlock ();
183199
184- // Delete enumerators and they'll delete their Devices
185- for (ControllerEnumerator* pEnumerator : enumerators) {
186- delete pEnumerator;
187- }
200+ // Delete enumerators (and their owned Controllers) by letting unique_ptrs
201+ // go out of scope here — no raw deletes needed.
202+ enumerators.clear ();
188203
189- // Stop the processor after the enumerators since the engines live in it
204+ // Stop the event loop after the enumerators are torn down, since the
205+ // controller scripting engines live inside the enumerators.
190206 m_pThread->quit ();
191207}
192208
193209void ControllerManager::updateControllerList () {
210+ DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY ();
194211 // NOTE: Currently this function is only called on startup. If hotplug is added, changes to the
195212 // controller list must be synchronized with dlgprefcontrollers to avoid dangling connections
196213 // and possible crashes.
197214 auto locker = lockMutex (&m_mutex);
198- if (m_enumerators.isEmpty ()) {
215+ if (m_enumerators.empty ()) {
199216 qWarning () << " updateControllerList called but no enumerators have been added!" ;
200217 return ;
201218 }
202- QList<ControllerEnumerator*> enumerators = m_enumerators;
219+ // Take a snapshot of the enumerator pointers while holding the lock.
220+ std::vector<ControllerEnumerator*> enumerators;
221+ enumerators.reserve (m_enumerators.size ());
222+ for (const auto & pEnumerator : m_enumerators) {
223+ enumerators.push_back (pEnumerator.get ());
224+ }
203225 locker.unlock ();
204226
205227 QList<Controller*> newDeviceList;
@@ -208,11 +230,12 @@ void ControllerManager::updateControllerList() {
208230 }
209231
210232 locker.relock ();
211- if (newDeviceList != m_controllers) {
212- m_controllers = newDeviceList;
213- locker.unlock ();
214- emit devicesChanged ();
233+ if (newDeviceList == m_controllers) {
234+ return ;
215235 }
236+ m_controllers = std::move (newDeviceList);
237+ locker.unlock ();
238+ emit devicesChanged ();
216239}
217240
218241QList<Controller*> ControllerManager::getControllers () const {
@@ -240,11 +263,13 @@ QList<Controller*> ControllerManager::getControllerList(bool bOutputDevices, boo
240263 return filteredDeviceList;
241264}
242265
243- QString ControllerManager::getConfiguredMappingFileForDevice (const QString& name) {
266+ QString ControllerManager::getConfiguredMappingFileForDevice (const QString& name) const {
267+ // Thread-Safe : ConfigObject<ValueType>::getValueString/get is protected by QWriteLocker
244268 return m_pConfig->getValueString (ConfigKey (kSettingsGroup , sanitizeDeviceName (name)));
245269}
246270
247271void ControllerManager::slotSetUpDevices () {
272+ DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY ();
248273 qDebug () << " ControllerManager: Setting up devices" ;
249274
250275 updateControllerList ();
@@ -311,6 +336,8 @@ void ControllerManager::slotSetUpDevices() {
311336}
312337
313338void ControllerManager::pollIfAnyControllersOpen () {
339+ // Not Thread-Safe because calls startPolling()/stopPolling()
340+ DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY ();
314341 auto locker = lockMutex (&m_mutex);
315342 QList<Controller*> controllers = m_controllers;
316343 locker.unlock ();
@@ -329,6 +356,8 @@ void ControllerManager::pollIfAnyControllersOpen() {
329356}
330357
331358void ControllerManager::startPolling () {
359+ // Not Thread-Safe because QTimer::start() must be called from the thread that owns the timer.
360+ DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY ();
332361 // Start the polling timer.
333362 if (!m_pollTimer.isActive ()) {
334363 m_pollTimer.start ();
@@ -337,11 +366,15 @@ void ControllerManager::startPolling() {
337366}
338367
339368void ControllerManager::stopPolling () {
369+ // Not Thread-Safe because QTimer::stop() must be called from the thread that owns the timer.
370+ DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY ();
340371 m_pollTimer.stop ();
341372 qDebug () << " Controller polling stopped." ;
342373}
343374
344375void ControllerManager::slotPollDevices () {
376+ // Not Thread-Safe because it accesses m_controllers and m_skipPoll without a mutex.
377+ DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY ();
345378 // Note: this function is called from a high priority thread which
346379 // may stall the GUI or may reduce the available CPU time for other
347380 // High Priority threads like caching reader or broadcasting more
@@ -384,7 +417,7 @@ void ControllerManager::slotPollDevices() {
384417}
385418
386419void ControllerManager::openController (Controller* pController) {
387- DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY ( this );
420+ DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY ( );
388421 if (!pController) {
389422 return ;
390423 }
@@ -404,7 +437,7 @@ void ControllerManager::openController(Controller* pController) {
404437}
405438
406439void ControllerManager::closeController (Controller* pController) {
407- DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY ( this );
440+ DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY ( );
408441 if (!pController) {
409442 return ;
410443 }
@@ -420,6 +453,7 @@ void ControllerManager::closeController(Controller* pController) {
420453void ControllerManager::slotApplyMapping (Controller* pController,
421454 std::shared_ptr<LegacyControllerMapping> pMapping,
422455 bool bEnabled) {
456+ DEBUG_ASSERT_THIS_QOBJECT_THREAD_AFFINITY ();
423457 VERIFY_OR_DEBUG_ASSERT (pController) {
424458 qWarning () << " slotApplyMapping got invalid controller!" ;
425459 return ;
0 commit comments