Skip to content

Commit c876d41

Browse files
authored
fix(connectivity_plus): use Windows list manager events (#3829)
1 parent 4ff3415 commit c876d41

3 files changed

Lines changed: 139 additions & 35 deletions

File tree

packages/connectivity_plus/connectivity_plus/windows/connectivity_plus_plugin.cpp

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
#include <flutter/plugin_registrar_windows.h>
1111
#include <flutter/standard_method_codec.h>
1212

13+
#include <algorithm>
1314
#include <functional>
15+
#include <map>
1416
#include <memory>
1517

1618
namespace {
@@ -56,10 +58,24 @@ class ConnectivityStreamHandler : public FlStreamHandler {
5658
OnCancelInternal(const flutter::EncodableValue *arguments) override;
5759

5860
private:
61+
bool TryStartListen();
62+
void ScheduleStartListenRetry();
63+
void CancelStartListenRetry();
64+
static void CALLBACK RetryTimerProc(HWND hwnd, UINT message, UINT_PTR id,
65+
DWORD time);
66+
67+
// Thread-affine (platform thread), like the stream handler itself.
68+
static std::map<UINT_PTR, ConnectivityStreamHandler *> retry_handlers_;
69+
5970
std::shared_ptr<NetworkManager> manager;
6071
std::unique_ptr<FlEventSink> sink;
72+
UINT_PTR retry_timer_id_ = 0;
73+
int retry_attempts_ = 0;
6174
};
6275

76+
std::map<UINT_PTR, ConnectivityStreamHandler *>
77+
ConnectivityStreamHandler::retry_handlers_;
78+
6379
ConnectivityPlusWindowsPlugin::ConnectivityPlusWindowsPlugin() {
6480
manager = std::make_shared<NetworkManager>();
6581
manager->Init();
@@ -146,25 +162,92 @@ ConnectivityStreamHandler::ConnectivityStreamHandler(
146162
std::shared_ptr<NetworkManager> manager)
147163
: manager(manager) {}
148164

149-
ConnectivityStreamHandler::~ConnectivityStreamHandler() {}
165+
ConnectivityStreamHandler::~ConnectivityStreamHandler() {
166+
CancelStartListenRetry();
167+
}
150168

151169
void ConnectivityStreamHandler::AddConnectivityEvent() {
152170
sink->Success(EncodeConnectivityTypes(manager->GetConnectivityTypes()));
153171
}
154172

173+
bool ConnectivityStreamHandler::TryStartListen() {
174+
auto callback =
175+
std::bind(&ConnectivityStreamHandler::AddConnectivityEvent, this);
176+
return manager->StartListen(callback);
177+
}
178+
179+
void ConnectivityStreamHandler::ScheduleStartListenRetry() {
180+
// 100ms, 200ms, 400ms, ... — enough to escape whatever input-synchronous
181+
// window the platform thread was inside.
182+
UINT delay = 100u << (std::min)(retry_attempts_, 4);
183+
retry_timer_id_ = SetTimer(nullptr, 0, delay, RetryTimerProc);
184+
if (retry_timer_id_ != 0) {
185+
retry_handlers_[retry_timer_id_] = this;
186+
}
187+
}
188+
189+
void ConnectivityStreamHandler::CancelStartListenRetry() {
190+
if (retry_timer_id_ != 0) {
191+
KillTimer(nullptr, retry_timer_id_);
192+
retry_handlers_.erase(retry_timer_id_);
193+
retry_timer_id_ = 0;
194+
}
195+
}
196+
197+
void CALLBACK ConnectivityStreamHandler::RetryTimerProc(HWND hwnd,
198+
UINT message,
199+
UINT_PTR id,
200+
DWORD time) {
201+
KillTimer(hwnd, id);
202+
auto it = retry_handlers_.find(id);
203+
if (it == retry_handlers_.end()) {
204+
return;
205+
}
206+
ConnectivityStreamHandler *self = it->second;
207+
retry_handlers_.erase(it);
208+
self->retry_timer_id_ = 0;
209+
210+
if (!self->sink) {
211+
return; // Cancelled while the retry was pending.
212+
}
213+
214+
if (self->TryStartListen()) {
215+
self->AddConnectivityEvent();
216+
return;
217+
}
218+
219+
if (self->manager->GetError() == RPC_E_CANTCALLOUT_ININPUTSYNCCALL &&
220+
++self->retry_attempts_ < 5) {
221+
self->ScheduleStartListenRetry();
222+
return;
223+
}
224+
225+
// Persistent failure: report through the sink, where Dart-side stream
226+
// onError handlers can observe it (unlike an OnListen error, which the
227+
// framework can only surface as an uncatchable FlutterError).
228+
self->sink->Error(std::to_string(self->manager->GetError()),
229+
"NetworkManager::StartListen", nullptr);
230+
}
231+
155232
std::unique_ptr<FlStreamHandlerError>
156233
ConnectivityStreamHandler::OnListenInternal(
157234
const flutter::EncodableValue *arguments,
158235
std::unique_ptr<FlEventSink> &&events) {
159236
sink = std::move(events);
160237

161-
auto callback =
162-
std::bind(&ConnectivityStreamHandler::AddConnectivityEvent, this);
163-
164-
if (!manager->StartListen(callback)) {
165-
return std::make_unique<FlStreamHandlerError>(
166-
std::to_string(manager->GetError()), "NetworkManager::StartListen",
167-
nullptr);
238+
if (!TryStartListen()) {
239+
if (manager->GetError() == RPC_E_CANTCALLOUT_ININPUTSYNCCALL) {
240+
// The platform thread is inside an input-synchronous call (e.g. a
241+
// SendMessage-driven window callback), where COM rejects the outgoing
242+
// Advise. The subscription itself is fine — retry once the message
243+
// loop spins, and still emit the initial snapshot below.
244+
retry_attempts_ = 0;
245+
ScheduleStartListenRetry();
246+
} else {
247+
return std::make_unique<FlStreamHandlerError>(
248+
std::to_string(manager->GetError()), "NetworkManager::StartListen",
249+
nullptr);
250+
}
168251
}
169252

170253
AddConnectivityEvent();
@@ -174,6 +257,7 @@ ConnectivityStreamHandler::OnListenInternal(
174257
std::unique_ptr<FlStreamHandlerError>
175258
ConnectivityStreamHandler::OnCancelInternal(
176259
const flutter::EncodableValue *arguments) {
260+
CancelStartListenRetry();
177261
manager->StopListen();
178262
sink.reset();
179263
return nullptr;

packages/connectivity_plus/connectivity_plus/windows/include/connectivity_plus/network_manager.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class NetworkManager {
4545
IConnectionPointContainer *pCPContainer = NULL;
4646
IConnectionPoint *pConnectPoint = NULL;
4747
NetworkListener *pListener = NULL;
48+
HRESULT lastError = S_OK;
4849
};
4950

5051
#endif // NETWORK_MANAGER_H

packages/connectivity_plus/connectivity_plus/windows/network_manager.cpp

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,26 @@
1818
#include <cassert>
1919
#include <set>
2020

21-
class NetworkListener final : public INetworkEvents {
21+
class NetworkListener final : public INetworkListManagerEvents {
2222
public:
2323
NetworkListener(NetworkCallback pCb) : pCallback(pCb) {}
2424

2525
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) {
26-
AddRef();
26+
if (!ppvObject) {
27+
return E_POINTER;
28+
}
2729

28-
HRESULT hr = S_OK;
2930
if (IsEqualIID(riid, IID_IUnknown)) {
30-
*ppvObject = (IUnknown *)this;
31-
} else if (IsEqualIID(riid, IID_INetworkEvents)) {
32-
*ppvObject = (INetworkEvents *)this;
31+
*ppvObject = static_cast<IUnknown *>(this);
32+
} else if (IsEqualIID(riid, IID_INetworkListManagerEvents)) {
33+
*ppvObject = static_cast<INetworkListManagerEvents *>(this);
3334
} else {
34-
hr = E_NOINTERFACE;
35+
*ppvObject = nullptr;
36+
return E_NOINTERFACE;
3537
}
36-
return hr;
38+
39+
AddRef();
40+
return S_OK;
3741
}
3842

3943
ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&lRef); }
@@ -46,24 +50,11 @@ class NetworkListener final : public INetworkEvents {
4650
return lAddend;
4751
}
4852

49-
HRESULT STDMETHODCALLTYPE NetworkAdded(GUID networkId) { return S_OK; }
50-
51-
HRESULT STDMETHODCALLTYPE
52-
NetworkConnectivityChanged(GUID networkId, NLM_CONNECTIVITY newConnectivity) {
53+
HRESULT STDMETHODCALLTYPE ConnectivityChanged(NLM_CONNECTIVITY) {
5354
Callback();
5455
return S_OK;
5556
}
5657

57-
HRESULT STDMETHODCALLTYPE NetworkDeleted(GUID networkId) { return S_OK; }
58-
59-
HRESULT STDMETHODCALLTYPE
60-
NetworkPropertyChanged(GUID networkId, NLM_NETWORK_PROPERTY_CHANGE flags) {
61-
if (flags & NLM_NETWORK_PROPERTY_CHANGE_CONNECTION) {
62-
Callback();
63-
}
64-
return S_OK;
65-
}
66-
6758
void Callback() {
6859
assert(pCallback);
6960
pCallback();
@@ -209,14 +200,25 @@ std::set<ConnectivityType> NetworkManager::GetConnectivityTypes() const {
209200
}
210201

211202
bool NetworkManager::StartListen(NetworkCallback pCallback) {
212-
if (!pCallback || pListener) {
203+
lastError = S_OK;
204+
if (!pCallback) {
205+
lastError = E_INVALIDARG;
206+
return false;
207+
}
208+
if (pListener) {
209+
lastError = HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS);
210+
return false;
211+
}
212+
if (!pNetworkListManager) {
213+
lastError = E_POINTER;
213214
return false;
214215
}
215216

216217
HRESULT hr = pNetworkListManager->QueryInterface(
217218
IID_IConnectionPointContainer, (void **)&pCPContainer);
218219
if (SUCCEEDED(hr)) {
219-
hr = pCPContainer->FindConnectionPoint(IID_INetworkEvents, &pConnectPoint);
220+
hr = pCPContainer->FindConnectionPoint(IID_INetworkListManagerEvents,
221+
&pConnectPoint);
220222
if (SUCCEEDED(hr)) {
221223
pListener = new NetworkListener(pCallback);
222224
hr = pConnectPoint->Advise((IUnknown *)pListener, &dwCookie);
@@ -225,12 +227,29 @@ bool NetworkManager::StartListen(NetworkCallback pCallback) {
225227
}
226228
}
227229
}
230+
231+
lastError = hr;
232+
if (pListener) {
233+
pListener->Release();
234+
pListener = NULL;
235+
}
236+
if (pConnectPoint) {
237+
pConnectPoint->Release();
238+
pConnectPoint = NULL;
239+
}
240+
if (pCPContainer) {
241+
pCPContainer->Release();
242+
pCPContainer = NULL;
243+
}
244+
dwCookie = 0;
228245
return false;
229246
}
230247

231248
void NetworkManager::StopListen() {
232249
if (pConnectPoint) {
233-
pConnectPoint->Unadvise(dwCookie);
250+
if (dwCookie != 0) {
251+
pConnectPoint->Unadvise(dwCookie);
252+
}
234253
pConnectPoint->Release();
235254
pConnectPoint = NULL;
236255
dwCookie = 0;
@@ -247,6 +266,6 @@ void NetworkManager::StopListen() {
247266
}
248267
}
249268

250-
bool NetworkManager::HasError() const { return GetLastError() != 0; }
269+
bool NetworkManager::HasError() const { return FAILED(lastError); }
251270

252-
int NetworkManager::GetError() const { return GetLastError(); }
271+
int NetworkManager::GetError() const { return static_cast<int>(lastError); }

0 commit comments

Comments
 (0)