From 2b614414ce95d920880765d07cbb9759699a4563 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Mon, 18 May 2026 18:56:08 +0200 Subject: [PATCH 1/2] fix(connectivity_plus): use Windows list manager events --- .../connectivity_plus/network_manager.h | 1 + .../windows/network_manager.cpp | 73 ++++++++++++------- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/packages/connectivity_plus/connectivity_plus/windows/include/connectivity_plus/network_manager.h b/packages/connectivity_plus/connectivity_plus/windows/include/connectivity_plus/network_manager.h index 19394fec82..0e29176e4e 100644 --- a/packages/connectivity_plus/connectivity_plus/windows/include/connectivity_plus/network_manager.h +++ b/packages/connectivity_plus/connectivity_plus/windows/include/connectivity_plus/network_manager.h @@ -45,6 +45,7 @@ class NetworkManager { IConnectionPointContainer *pCPContainer = NULL; IConnectionPoint *pConnectPoint = NULL; NetworkListener *pListener = NULL; + HRESULT lastError = S_OK; }; #endif // NETWORK_MANAGER_H diff --git a/packages/connectivity_plus/connectivity_plus/windows/network_manager.cpp b/packages/connectivity_plus/connectivity_plus/windows/network_manager.cpp index 31822c5590..302b5f4f5d 100644 --- a/packages/connectivity_plus/connectivity_plus/windows/network_manager.cpp +++ b/packages/connectivity_plus/connectivity_plus/windows/network_manager.cpp @@ -18,22 +18,26 @@ #include #include -class NetworkListener final : public INetworkEvents { +class NetworkListener final : public INetworkListManagerEvents { public: NetworkListener(NetworkCallback pCb) : pCallback(pCb) {} HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) { - AddRef(); + if (!ppvObject) { + return E_POINTER; + } - HRESULT hr = S_OK; if (IsEqualIID(riid, IID_IUnknown)) { - *ppvObject = (IUnknown *)this; - } else if (IsEqualIID(riid, IID_INetworkEvents)) { - *ppvObject = (INetworkEvents *)this; + *ppvObject = static_cast(this); + } else if (IsEqualIID(riid, IID_INetworkListManagerEvents)) { + *ppvObject = static_cast(this); } else { - hr = E_NOINTERFACE; + *ppvObject = nullptr; + return E_NOINTERFACE; } - return hr; + + AddRef(); + return S_OK; } ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&lRef); } @@ -46,24 +50,11 @@ class NetworkListener final : public INetworkEvents { return lAddend; } - HRESULT STDMETHODCALLTYPE NetworkAdded(GUID networkId) { return S_OK; } - - HRESULT STDMETHODCALLTYPE - NetworkConnectivityChanged(GUID networkId, NLM_CONNECTIVITY newConnectivity) { + HRESULT STDMETHODCALLTYPE ConnectivityChanged(NLM_CONNECTIVITY) { Callback(); return S_OK; } - HRESULT STDMETHODCALLTYPE NetworkDeleted(GUID networkId) { return S_OK; } - - HRESULT STDMETHODCALLTYPE - NetworkPropertyChanged(GUID networkId, NLM_NETWORK_PROPERTY_CHANGE flags) { - if (flags & NLM_NETWORK_PROPERTY_CHANGE_CONNECTION) { - Callback(); - } - return S_OK; - } - void Callback() { assert(pCallback); pCallback(); @@ -209,14 +200,25 @@ std::set NetworkManager::GetConnectivityTypes() const { } bool NetworkManager::StartListen(NetworkCallback pCallback) { - if (!pCallback || pListener) { + lastError = S_OK; + if (!pCallback) { + lastError = E_INVALIDARG; + return false; + } + if (pListener) { + lastError = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); + return false; + } + if (!pNetworkListManager) { + lastError = E_POINTER; return false; } HRESULT hr = pNetworkListManager->QueryInterface( IID_IConnectionPointContainer, (void **)&pCPContainer); if (SUCCEEDED(hr)) { - hr = pCPContainer->FindConnectionPoint(IID_INetworkEvents, &pConnectPoint); + hr = pCPContainer->FindConnectionPoint(IID_INetworkListManagerEvents, + &pConnectPoint); if (SUCCEEDED(hr)) { pListener = new NetworkListener(pCallback); hr = pConnectPoint->Advise((IUnknown *)pListener, &dwCookie); @@ -225,12 +227,29 @@ bool NetworkManager::StartListen(NetworkCallback pCallback) { } } } + + lastError = hr; + if (pListener) { + pListener->Release(); + pListener = NULL; + } + if (pConnectPoint) { + pConnectPoint->Release(); + pConnectPoint = NULL; + } + if (pCPContainer) { + pCPContainer->Release(); + pCPContainer = NULL; + } + dwCookie = 0; return false; } void NetworkManager::StopListen() { if (pConnectPoint) { - pConnectPoint->Unadvise(dwCookie); + if (dwCookie != 0) { + pConnectPoint->Unadvise(dwCookie); + } pConnectPoint->Release(); pConnectPoint = NULL; dwCookie = 0; @@ -247,6 +266,6 @@ void NetworkManager::StopListen() { } } -bool NetworkManager::HasError() const { return GetLastError() != 0; } +bool NetworkManager::HasError() const { return FAILED(lastError); } -int NetworkManager::GetError() const { return GetLastError(); } +int NetworkManager::GetError() const { return static_cast(lastError); } From 4c0d4071150481f76d753c6ce1ff325bb959b9b5 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sun, 5 Jul 2026 11:35:02 +0200 Subject: [PATCH 2/2] fix(connectivity_plus): retry Windows Advise when the platform thread is input-synchronous INetworkListManagerEvents Advise fails with RPC_E_CANTCALLOUT_ININPUTSYNCCALL when OnListen runs while the platform thread is inside an input-synchronous call (e.g. a SendMessage-driven window callback). Failing OnListen surfaces in Dart as an uncatchable FlutterError, so instead retry the subscription via a message-loop timer and report only persistent failures through the event sink, where stream onError handlers can observe them. --- .../windows/connectivity_plus_plugin.cpp | 100 ++++++++++++++++-- 1 file changed, 92 insertions(+), 8 deletions(-) diff --git a/packages/connectivity_plus/connectivity_plus/windows/connectivity_plus_plugin.cpp b/packages/connectivity_plus/connectivity_plus/windows/connectivity_plus_plugin.cpp index cb6ae1f936..76b7a26b72 100644 --- a/packages/connectivity_plus/connectivity_plus/windows/connectivity_plus_plugin.cpp +++ b/packages/connectivity_plus/connectivity_plus/windows/connectivity_plus_plugin.cpp @@ -10,7 +10,9 @@ #include #include +#include #include +#include #include namespace { @@ -56,10 +58,24 @@ class ConnectivityStreamHandler : public FlStreamHandler { OnCancelInternal(const flutter::EncodableValue *arguments) override; private: + bool TryStartListen(); + void ScheduleStartListenRetry(); + void CancelStartListenRetry(); + static void CALLBACK RetryTimerProc(HWND hwnd, UINT message, UINT_PTR id, + DWORD time); + + // Thread-affine (platform thread), like the stream handler itself. + static std::map retry_handlers_; + std::shared_ptr manager; std::unique_ptr sink; + UINT_PTR retry_timer_id_ = 0; + int retry_attempts_ = 0; }; +std::map + ConnectivityStreamHandler::retry_handlers_; + ConnectivityPlusWindowsPlugin::ConnectivityPlusWindowsPlugin() { manager = std::make_shared(); manager->Init(); @@ -146,25 +162,92 @@ ConnectivityStreamHandler::ConnectivityStreamHandler( std::shared_ptr manager) : manager(manager) {} -ConnectivityStreamHandler::~ConnectivityStreamHandler() {} +ConnectivityStreamHandler::~ConnectivityStreamHandler() { + CancelStartListenRetry(); +} void ConnectivityStreamHandler::AddConnectivityEvent() { sink->Success(EncodeConnectivityTypes(manager->GetConnectivityTypes())); } +bool ConnectivityStreamHandler::TryStartListen() { + auto callback = + std::bind(&ConnectivityStreamHandler::AddConnectivityEvent, this); + return manager->StartListen(callback); +} + +void ConnectivityStreamHandler::ScheduleStartListenRetry() { + // 100ms, 200ms, 400ms, ... — enough to escape whatever input-synchronous + // window the platform thread was inside. + UINT delay = 100u << (std::min)(retry_attempts_, 4); + retry_timer_id_ = SetTimer(nullptr, 0, delay, RetryTimerProc); + if (retry_timer_id_ != 0) { + retry_handlers_[retry_timer_id_] = this; + } +} + +void ConnectivityStreamHandler::CancelStartListenRetry() { + if (retry_timer_id_ != 0) { + KillTimer(nullptr, retry_timer_id_); + retry_handlers_.erase(retry_timer_id_); + retry_timer_id_ = 0; + } +} + +void CALLBACK ConnectivityStreamHandler::RetryTimerProc(HWND hwnd, + UINT message, + UINT_PTR id, + DWORD time) { + KillTimer(hwnd, id); + auto it = retry_handlers_.find(id); + if (it == retry_handlers_.end()) { + return; + } + ConnectivityStreamHandler *self = it->second; + retry_handlers_.erase(it); + self->retry_timer_id_ = 0; + + if (!self->sink) { + return; // Cancelled while the retry was pending. + } + + if (self->TryStartListen()) { + self->AddConnectivityEvent(); + return; + } + + if (self->manager->GetError() == RPC_E_CANTCALLOUT_ININPUTSYNCCALL && + ++self->retry_attempts_ < 5) { + self->ScheduleStartListenRetry(); + return; + } + + // Persistent failure: report through the sink, where Dart-side stream + // onError handlers can observe it (unlike an OnListen error, which the + // framework can only surface as an uncatchable FlutterError). + self->sink->Error(std::to_string(self->manager->GetError()), + "NetworkManager::StartListen", nullptr); +} + std::unique_ptr ConnectivityStreamHandler::OnListenInternal( const flutter::EncodableValue *arguments, std::unique_ptr &&events) { sink = std::move(events); - auto callback = - std::bind(&ConnectivityStreamHandler::AddConnectivityEvent, this); - - if (!manager->StartListen(callback)) { - return std::make_unique( - std::to_string(manager->GetError()), "NetworkManager::StartListen", - nullptr); + if (!TryStartListen()) { + if (manager->GetError() == RPC_E_CANTCALLOUT_ININPUTSYNCCALL) { + // The platform thread is inside an input-synchronous call (e.g. a + // SendMessage-driven window callback), where COM rejects the outgoing + // Advise. The subscription itself is fine — retry once the message + // loop spins, and still emit the initial snapshot below. + retry_attempts_ = 0; + ScheduleStartListenRetry(); + } else { + return std::make_unique( + std::to_string(manager->GetError()), "NetworkManager::StartListen", + nullptr); + } } AddConnectivityEvent(); @@ -174,6 +257,7 @@ ConnectivityStreamHandler::OnListenInternal( std::unique_ptr ConnectivityStreamHandler::OnCancelInternal( const flutter::EncodableValue *arguments) { + CancelStartListenRetry(); manager->StopListen(); sink.reset(); return nullptr;