Skip to content

Commit 5a26f4e

Browse files
committed
Fix app crashing
1 parent 92a5d03 commit 5a26f4e

File tree

2 files changed

+69
-31
lines changed

2 files changed

+69
-31
lines changed

include/AudioWorker.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public slots:
7070
void emitPeaksNow();
7171

7272
bool m_showSystemSessions = false;
73+
std::atomic<bool> m_destroying{false};
7374
QTimer m_snapshotTimer;
7475
QTimer m_meterTimer;
7576

src/AudioWorker.cpp

Lines changed: 68 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
#include "win/Hr.h"
55

66
#include <QDateTime>
7+
#include <QDebug>
78
#include <QFileInfo>
89
#include <QHash>
910
#include <QMutex>
1011

12+
#include <atomic>
1113
#include <unordered_map>
1214

1315
#include <windows.h>
@@ -182,8 +184,8 @@ struct AudioWorker::Impl
182184
private:
183185
void ping()
184186
{
185-
if (m_worker)
186-
QMetaObject::invokeMethod(m_worker, [this]() { m_worker->scheduleSnapshot(); }, Qt::QueuedConnection);
187+
if (m_worker && !m_worker->m_destroying.load())
188+
QMetaObject::invokeMethod(m_worker, [this]() { if (m_worker && !m_worker->m_destroying.load()) m_worker->scheduleSnapshot(); }, Qt::QueuedConnection);
187189
}
188190

189191
std::atomic<ULONG> m_ref{1};
@@ -221,8 +223,8 @@ struct AudioWorker::Impl
221223

222224
HRESULT STDMETHODCALLTYPE OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA) override
223225
{
224-
if (m_worker)
225-
QMetaObject::invokeMethod(m_worker, [this]() { m_worker->scheduleSnapshot(); }, Qt::QueuedConnection);
226+
if (m_worker && !m_worker->m_destroying.load())
227+
QMetaObject::invokeMethod(m_worker, [this]() { if (m_worker && !m_worker->m_destroying.load()) m_worker->scheduleSnapshot(); }, Qt::QueuedConnection);
226228
return S_OK;
227229
}
228230

@@ -272,8 +274,8 @@ struct AudioWorker::Impl
272274
private:
273275
void ping()
274276
{
275-
if (m_worker)
276-
QMetaObject::invokeMethod(m_worker, [this]() { m_worker->scheduleSnapshot(); }, Qt::QueuedConnection);
277+
if (m_worker && !m_worker->m_destroying.load())
278+
QMetaObject::invokeMethod(m_worker, [this]() { if (m_worker && !m_worker->m_destroying.load()) m_worker->scheduleSnapshot(); }, Qt::QueuedConnection);
277279
}
278280

279281
std::atomic<ULONG> m_ref{1};
@@ -312,8 +314,8 @@ struct AudioWorker::Impl
312314

313315
HRESULT STDMETHODCALLTYPE OnSessionCreated(IAudioSessionControl *) override
314316
{
315-
if (m_worker)
316-
QMetaObject::invokeMethod(m_worker, [this]() { m_worker->scheduleSnapshot(); }, Qt::QueuedConnection);
317+
if (m_worker && !m_worker->m_destroying.load())
318+
QMetaObject::invokeMethod(m_worker, [this]() { if (m_worker && !m_worker->m_destroying.load()) m_worker->scheduleSnapshot(); }, Qt::QueuedConnection);
317319
return S_OK;
318320
}
319321

@@ -413,6 +415,7 @@ AudioWorker::AudioWorker(QObject *parent)
413415

414416
AudioWorker::~AudioWorker()
415417
{
418+
m_destroying.store(true);
416419
stop();
417420
delete m;
418421
m = nullptr;
@@ -446,13 +449,15 @@ void AudioWorker::stop()
446449

447450
void AudioWorker::setShowSystemSessions(bool show)
448451
{
452+
if (m_destroying.load())
453+
return;
449454
m_showSystemSessions = show;
450455
scheduleSnapshot();
451456
}
452457

453458
void AudioWorker::setDeviceVolume(const QString &deviceId, double volume01)
454459
{
455-
if (!m)
460+
if (m_destroying.load() || !m)
456461
return;
457462
auto it = m->devices.find(deviceId);
458463
if (it == m->devices.end() || !it->second.endpoint)
@@ -462,7 +467,7 @@ void AudioWorker::setDeviceVolume(const QString &deviceId, double volume01)
462467

463468
void AudioWorker::setDeviceMuted(const QString &deviceId, bool muted)
464469
{
465-
if (!m)
470+
if (m_destroying.load() || !m)
466471
return;
467472
auto it = m->devices.find(deviceId);
468473
if (it == m->devices.end() || !it->second.endpoint)
@@ -472,7 +477,7 @@ void AudioWorker::setDeviceMuted(const QString &deviceId, bool muted)
472477

473478
void AudioWorker::setSessionVolume(const QString &deviceId, quint32 pid, const QString &exePath, double volume01)
474479
{
475-
if (!m)
480+
if (m_destroying.load() || !m)
476481
return;
477482
Impl::SessionKey key{deviceId, pid, exePath};
478483
auto it = m->sessions.find(key);
@@ -483,7 +488,7 @@ void AudioWorker::setSessionVolume(const QString &deviceId, quint32 pid, const Q
483488

484489
void AudioWorker::setSessionMuted(const QString &deviceId, quint32 pid, const QString &exePath, bool muted)
485490
{
486-
if (!m)
491+
if (m_destroying.load() || !m)
487492
return;
488493
Impl::SessionKey key{deviceId, pid, exePath};
489494
auto it = m->sessions.find(key);
@@ -494,13 +499,20 @@ void AudioWorker::setSessionMuted(const QString &deviceId, quint32 pid, const QS
494499

495500
void AudioWorker::scheduleSnapshot()
496501
{
497-
if (!m_snapshotTimer.isActive())
502+
// Check if object is being destroyed or already destroyed
503+
if (m_destroying.load() || !m) {
504+
qWarning("AudioWorker::scheduleSnapshot() called after destruction - this indicates a race condition with COM callbacks");
505+
return;
506+
}
507+
// Additional safety: check if timer is still valid (parent object might be destroyed)
508+
if (!m_snapshotTimer.isActive() && m_snapshotTimer.parent() == this)
498509
m_snapshotTimer.start();
499510
}
500511

501512
void AudioWorker::emitSnapshotNow()
502513
{
503-
if (!m || !m->enumerator)
514+
// Check if object is being destroyed or already destroyed
515+
if (m_destroying.load() || !m || !m->enumerator)
504516
return;
505517

506518
// Refresh device list.
@@ -529,6 +541,10 @@ void AudioWorker::emitSnapshotNow()
529541

530542
// Rebuild COM caches each snapshot, but unregister previous callbacks to avoid dangling notifications.
531543
{
544+
// Check again before unregistering - destruction might have started
545+
if (m_destroying.load() || !m)
546+
return;
547+
532548
for (auto &kv : m->devices) {
533549
auto &dc = kv.second;
534550
if (dc.endpoint && m->endpointCb) {
@@ -555,6 +571,10 @@ void AudioWorker::emitSnapshotNow()
555571
const qint64 nowMs = QDateTime::currentMSecsSinceEpoch();
556572

557573
for (UINT i = 0; i < count; ++i) {
574+
// Check periodically during long-running operation
575+
if (m_destroying.load() || !m)
576+
return;
577+
558578
ComPtr<IMMDevice> dev;
559579
if (FAILED(coll->Item(i, dev.put())) || !dev)
560580
continue;
@@ -582,7 +602,10 @@ void AudioWorker::emitSnapshotNow()
582602
ep->GetMute(&mute);
583603
ds.volume = vol;
584604
ds.muted = (mute == TRUE);
585-
ep->RegisterControlChangeNotify(m->endpointCb.get());
605+
// Only register callback if not destroying
606+
if (!m_destroying.load() && m && m->endpointCb) {
607+
ep->RegisterControlChangeNotify(m->endpointCb.get());
608+
}
586609
dc.endpoint.attach(ep.detach());
587610
}
588611
}
@@ -592,7 +615,10 @@ void AudioWorker::emitSnapshotNow()
592615
ComPtr<IAudioSessionManager2> mgr;
593616
hr = dc.device->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, nullptr, reinterpret_cast<void **>(mgr.put()));
594617
if (SUCCEEDED(hr) && mgr) {
595-
mgr->RegisterSessionNotification(m->sessionCb.get());
618+
// Only register callback if not destroying
619+
if (!m_destroying.load() && m && m->sessionCb) {
620+
mgr->RegisterSessionNotification(m->sessionCb.get());
621+
}
596622
dc.sessionMgr.attach(mgr.detach());
597623

598624
ComPtr<IAudioSessionEnumerator> se;
@@ -676,34 +702,45 @@ void AudioWorker::emitSnapshotNow()
676702

677703
sc.lastActiveMs = lastActive;
678704
sc.state = st;
679-
auto *events = new Impl::SessionEvents(this, key);
680-
if (sc.ctrl) {
681-
// RegisterAudioSessionNotification does NOT guarantee AddRef on events across all implementations,
682-
// so we keep an explicit ref we own.
683-
events->AddRef();
684-
sc.ctrl->RegisterAudioSessionNotification(events);
685-
sc.events = events;
705+
// Only register session events if not destroying
706+
if (!m_destroying.load() && m) {
707+
auto *events = new Impl::SessionEvents(this, key);
708+
if (sc.ctrl) {
709+
// RegisterAudioSessionNotification does NOT guarantee AddRef on events across all implementations,
710+
// so we keep an explicit ref we own.
711+
events->AddRef();
712+
sc.ctrl->RegisterAudioSessionNotification(events);
713+
sc.events = events;
714+
}
715+
events->Release(); // balance initial ref
686716
}
687-
events->Release(); // balance initial ref
688-
689-
m->sessions.insert({key, std::move(sc)});
690717

691-
ds.sessions.push_back(ss);
718+
// Only store session if not destroying
719+
if (!m_destroying.load() && m) {
720+
m->sessions.insert({key, std::move(sc)});
721+
ds.sessions.push_back(ss);
722+
}
692723
}
693724
}
694725
}
695726
}
696727

697-
m->devices.emplace(id, std::move(dc));
698-
devices.push_back(ds);
728+
// Final check before storing - don't add to cache if destroying
729+
if (!m_destroying.load() && m) {
730+
m->devices.emplace(id, std::move(dc));
731+
devices.push_back(ds);
732+
}
699733
}
700734

701-
emit snapshotReady(devices);
735+
// Only emit if not destroying
736+
if (!m_destroying.load() && m)
737+
emit snapshotReady(devices);
702738
}
703739

704740
void AudioWorker::emitPeaksNow()
705741
{
706-
if (!m)
742+
// Check if object is being destroyed or already destroyed
743+
if (m_destroying.load() || !m)
707744
return;
708745

709746
QVector<SessionPeak> peaks;

0 commit comments

Comments
 (0)