Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
#include <flutter/plugin_registrar_windows.h>
#include <flutter/standard_method_codec.h>

#include <algorithm>
#include <functional>
#include <map>
#include <memory>

namespace {
Expand Down Expand Up @@ -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<UINT_PTR, ConnectivityStreamHandler *> retry_handlers_;

std::shared_ptr<NetworkManager> manager;
std::unique_ptr<FlEventSink> sink;
UINT_PTR retry_timer_id_ = 0;
int retry_attempts_ = 0;
};

std::map<UINT_PTR, ConnectivityStreamHandler *>
ConnectivityStreamHandler::retry_handlers_;

ConnectivityPlusWindowsPlugin::ConnectivityPlusWindowsPlugin() {
manager = std::make_shared<NetworkManager>();
manager->Init();
Expand Down Expand Up @@ -146,25 +162,92 @@ ConnectivityStreamHandler::ConnectivityStreamHandler(
std::shared_ptr<NetworkManager> 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<FlStreamHandlerError>
ConnectivityStreamHandler::OnListenInternal(
const flutter::EncodableValue *arguments,
std::unique_ptr<FlEventSink> &&events) {
sink = std::move(events);

auto callback =
std::bind(&ConnectivityStreamHandler::AddConnectivityEvent, this);

if (!manager->StartListen(callback)) {
return std::make_unique<FlStreamHandlerError>(
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<FlStreamHandlerError>(
std::to_string(manager->GetError()), "NetworkManager::StartListen",
nullptr);
}
}

AddConnectivityEvent();
Expand All @@ -174,6 +257,7 @@ ConnectivityStreamHandler::OnListenInternal(
std::unique_ptr<FlStreamHandlerError>
ConnectivityStreamHandler::OnCancelInternal(
const flutter::EncodableValue *arguments) {
CancelStartListenRetry();
manager->StopListen();
sink.reset();
return nullptr;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class NetworkManager {
IConnectionPointContainer *pCPContainer = NULL;
IConnectionPoint *pConnectPoint = NULL;
NetworkListener *pListener = NULL;
HRESULT lastError = S_OK;
};

#endif // NETWORK_MANAGER_H
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,26 @@
#include <cassert>
#include <set>

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<IUnknown *>(this);
} else if (IsEqualIID(riid, IID_INetworkListManagerEvents)) {
*ppvObject = static_cast<INetworkListManagerEvents *>(this);
} else {
hr = E_NOINTERFACE;
*ppvObject = nullptr;
return E_NOINTERFACE;
}
return hr;

AddRef();
return S_OK;
}

ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&lRef); }
Expand All @@ -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();
Expand Down Expand Up @@ -209,14 +200,25 @@ std::set<ConnectivityType> 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);
Expand All @@ -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;
Expand All @@ -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<int>(lastError); }
Loading