Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Prototype] Implement createchannelasync for requesting channels #508

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
13 changes: 8 additions & 5 deletions dev/PushNotifications/ChannelResult.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,26 @@
#include "pch.h"
#include "ChannelResult.h"
#include "ChannelResult.g.cpp"
#include <winrt\windows.networking.pushnotifications.h>

namespace winrt::Microsoft::ProjectReunion::implementation
{
ChannelResult::ChannelResult(Windows::Networking::PushNotifications::PushNotificationChannel const& channel, winrt::hresult const& extendedError, Microsoft::ProjectReunion::ChannelStatus const& status)
ChannelResult::ChannelResult(winrt::Windows::Networking::PushNotifications::PushNotificationChannel const channel, winrt::hresult const extendedError, Microsoft::ProjectReunion::ChannelStatus const status)
{
throw hresult_not_implemented();
m_channel = channel;
m_extendedError = extendedError;
m_channelStatus = status;
}
Windows::Networking::PushNotifications::PushNotificationChannel ChannelResult::Channel()
{
throw hresult_not_implemented();
return m_channel;
}
winrt::hresult ChannelResult::ExtendedError()
{
throw hresult_not_implemented();
return m_extendedError;
}
Microsoft::ProjectReunion::ChannelStatus ChannelResult::Status()
{
throw hresult_not_implemented();
return m_channelStatus;
}
}
7 changes: 6 additions & 1 deletion dev/PushNotifications/ChannelResult.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ namespace winrt::Microsoft::ProjectReunion::implementation
{
ChannelResult() = default;

ChannelResult(Windows::Networking::PushNotifications::PushNotificationChannel const& channel, winrt::hresult const& extendedError, Microsoft::ProjectReunion::ChannelStatus const& status);
ChannelResult(Windows::Networking::PushNotifications::PushNotificationChannel const channel, winrt::hresult const extendedError, Microsoft::ProjectReunion::ChannelStatus const status);
Windows::Networking::PushNotifications::PushNotificationChannel Channel();
winrt::hresult ExtendedError();
Microsoft::ProjectReunion::ChannelStatus Status();

private:
Windows::Networking::PushNotifications::PushNotificationChannel m_channel{ nullptr };
winrt::hresult m_extendedError;
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
Microsoft::ProjectReunion::ChannelStatus m_channelStatus;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProjectReunion shouldn't be used in any symbols or code references.

What's the target namespace? Please update to Microsoft::Something::Something::ChannelStatus

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure will address this appropriately. Thanks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DrusTheAxe I'm aligned with you here, but WinUI 3 is going GA with Microsoft.ProjectReunion namespace prefix. I think the rules changed overnight?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WinUI3 is Microsoft.UI.Xaml - we just had a namespace naming discussion internally, and will publish guidance here soon. The short version is that this should become Microsoft.Windows.PushNotification.Something ... the Microsoft.ProjectReunion namespace was purely a placeholder that got out of hand from my initial commit. :) (Notably, we hadn't had a conversation about Microsoft.ProjectReunion initially; so view this as going from "no rules" to "some rules" overnight.)

That'll be part of the API design & review guidelines that get published, probably early next week at this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jon, thanks for clearing up the namespace related concerns. Once the spec is approved, I would be changing the namespace appropriately. Till then this PR would only be targetted to be part of our private branch WNP_Reunion and not the main. Thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DrusTheAxe - The spec is being done by Pavan which is currently going through the spec review process. I will be using that spec details to make changes to my code for namespaces.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not namespace. Package name

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that Mr. Gallo was in the discussion last night and approved of the guidelines I'll be publishing, yes. :)

There may be a short period of time where the guidance is being rolled out and people move their code around. I recognize that's disruptive, so we want to get it sorted before 0.8, at least. @MikeHillberg or @jevansaks can comment on the WinUI3 namespaces, but I'm pretty sure they've always been Microsoft.UI.* and will continue to be.

Maybe the confusion is that WinUI3 will be delivered via the Project Reunion vehicle that @EHO-makai and company are making steady progress on. We intend Project Reunion as a delivery mechanism for lots of interesting types in the coming months. WinRT and the framework package system we're using lets us be flexible on mapping typenames to implementation DLLs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reader context: I quickly pulled my comment after posting (asking Jon to double-check) but I was too slow; two folks replied. 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#458 - this is the spec for reference. Thanks.

sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
};
}
namespace winrt::Microsoft::ProjectReunion::factory_implementation
Expand Down
102 changes: 101 additions & 1 deletion dev/PushNotifications/PushManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,111 @@
#include "pch.h"
#include "PushManager.h"
#include "PushManager.g.cpp"
#include <winrt\windows.networking.pushnotifications.h>
#include <winerror.h>
#include <algorithm>
#include <list>
#include <iostream>
#include <ChannelResult.h>
#include <winerror.h>

using namespace winrt::Windows::Networking::PushNotifications;
using namespace winrt;

// MAX BACKOFF TIME IS 16MINUTES
#define MAX_BACKOFF_SECONDS 960
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why #define? Why not e.g. inline constexpr std::uint32_t c_maxBackoffSeconds{ 960 }; ?

Bonus points for putting that inside a namespace e.g.

namespace winrt::Windows::Networking::PushNotifications
{
inline constexpr std::uint32_t c_maxBackoffSeconds{ 960 };

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a time, maybe use the time constants and time-related functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the co_await: co_await winrt::resume_after(std::chrono::seconds(backOffTimeInSeconds)); this takes care of it here. I think this should suffice here: inline constexpr std::uint32_t c_maxBackoffSeconds{ 960 }; ?? please correct me if I am missing something here.


// Error code - platform is not connected
#define WNP_E_NOT_CONNECTED 0x880403E8L

// Error code - platform reconnecting with wns
#define WNP_E_RECONNECTING 0x880403E9L

// Error code - Current bind operation failed because another bind operation in progress
#define WNP_E_BIND_USER_BUSY 0x880403FEL
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these HRESULTs defined by winerror.h? Are they new constants not in the SDK we're currently compiling against?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonwis We have some internal error codes(not part of winerrors.h) that we need to retry on. How do we expose this in Reunion? Is there a common header that we could use or do we go with our own header?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just declare them here as you have them, but use the MAKE_HRESULT macro. No need to find or add some global common header. If consumers of the API are expected to do something interesting with them, add specific Status codes to the result object that clarify what happened.


namespace winrt::Microsoft::ProjectReunion::implementation
{
std::list<winrt::guid> PushManager::remoteIdList;
std::mutex PushManager::mtx;
std::unique_lock<std::mutex> PushManager::lock (mtx, std::defer_lock); // Avoid locking during the constructor call

bool PushManager::isChannelRequestRetryable(const winrt::hresult& hrException)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Rename to hr

There's nothing about this function that requires the parameter to have come from an exception.

{
switch (hrException)
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
{
case HRESULT_FROM_WIN32(ERROR_TIMEOUT):
// E_TIMEOUT
case WNP_E_NOT_CONNECTED:
case WPN_E_OUTSTANDING_CHANNEL_REQUEST:
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
case WNP_E_RECONNECTING
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the C++ Core Guidelines say about case fallthrough (some linters would flag it).

Simpler to avoid the switch statement entirely

return (hrException == E_TIMEOUT) ||
       (hrException == WNP_E_NOT_CONNECTED) ||
       (hrException == WPN_E_OUTSTANDING_CHANNEL_REQUEST) ||
       (hrException == WNP_E_RECONNECTING);

case WNP_E_BIND_USER_BUSY:
return true;
default:
return false;
}
}

Windows::Foundation::IAsyncOperationWithProgress<Microsoft::ProjectReunion::ChannelResult, Microsoft::ProjectReunion::ChannelResult> PushManager::CreateChannelAsync(winrt::guid remoteId)
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we verify the implications of lifetime management? What would happen if the process exited but this AsyncProgress operation is not yet complete? Is the process termination graceful?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://en.cppreference.com/w/cpp/language/coroutines - When the coroutine state is destroyed either because it terminated via co_return or uncaught exception, or because it was destroyed via its handle, it does the following:

calls the destructor of the promise object.
calls the destructors of the function parameter copies.
calls operator delete to free the memory used by the coroutine state
transfers execution back to the caller/resumer.

So the coroutines completes gracefully.

Also it is important that the caller of the coroutine API: CreateChannelAsync will have to call IAsyncOperationWithProgress.Cancel to stop the asynchronous operation before the caller process exits. We would be clearly mentioning this in the sample that we are providing to cancel the asyncoperation if you are going out of scope. Thanks.

{
throw hresult_not_implemented();
winrt::Microsoft::ProjectReunion::ChannelResult channelResult{ nullptr };
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
auto progress{ co_await winrt::get_progress_token() };

GUID remoteIdAbiGuid = reinterpret_cast<GUID&>(remoteId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remoteId is a winrt::guid. That's not the same memory layout as a GUID&

Why do you need to cast remoteId to GUID?
Can lines 57-58 be replaced by

if (remoteId == winrt::guid{})

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole PR was meant to be a prototype to see if I am able to request channel. Maybe I should have been clear with the PR heading and must have created a draft. This will change to using winrt::guid and will throw is guid is null as it is considered a critical failure :)

Copy link
Member

@DrusTheAxe DrusTheAxe Mar 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub doesn't do 'draft' PRs (not that I've found so far; do speak up if you figure out how)

@jonwis @EHO-makai @BenJKuhn do we have guidance how we should handle 'draft' code reviews? A branch lacks sharing/comment support so a PR's superior to get feedback. If I make a 3-level branching structure main->foo->foo_x and make PRs for foo_x->foo what's the review expectations? Standard review intensity for L3->L2 as L2->L1 (main)? And when the L2 is finally deemed ready to move to L1, do the same review standards apply? As a practical matter, that can lead to very large L2 PRs (harder and more costly to review), but a rubberstamp flush L2->L1 is bad if the L3->L2 review doesn't have the same strict level of review attention and intensity as going into main "because it's just a draft" etc.

There are potential differences here vs how it's handled in other environments. We should have a clear definition of expectations, or folks will do what they've done before (reasonably) assuming that's the way.

Copy link
Contributor

@riverar riverar Mar 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@DrusTheAxe DrusTheAxe Mar 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super! Thanks for the pointer

You can convert a Draft PR to not-Draft. Can you go the other way? 'downgrade' a PR to draft? I don't see it mentioned

Copy link
Contributor

@riverar riverar Mar 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have appropriate permissions, but you should see this under Reviewers area in top-right corner:
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DrusTheAxe Sure. I have converted this to a draft. I think I agree to the fact that PR review L3->L2 and L2->L1 should have the same review standards (if it is not a draft). Can we have some guidelines on the process so it makes it easier for the developers to follow that approach going ahead.

if (remoteIdAbiGuid == GUID_NULL)
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
{
channelResult = winrt::make<winrt::Microsoft::ProjectReunion::implementation::ChannelResult>(nullptr, E_INVALIDARG, ChannelStatus::CompletedFailure);
co_return channelResult;
}

lock.lock();
if (std::find(remoteIdList.begin(), remoteIdList.end(), remoteId) != remoteIdList.end())
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
{
progress(winrt::make<winrt::Microsoft::ProjectReunion::implementation::ChannelResult>(nullptr, WPN_E_OUTSTANDING_CHANNEL_REQUEST, ChannelStatus::InProgress));
channelResult = winrt::make<winrt::Microsoft::ProjectReunion::implementation::ChannelResult>(nullptr, WPN_E_OUTSTANDING_CHANNEL_REQUEST, ChannelStatus::CompletedFailure);
co_return channelResult;
}
else
{
remoteIdList.push_back(remoteId);
}
lock.unlock();

PushNotificationChannelManager channelManager{};

for (auto backOffTimeInSeconds = 30; backOffTimeInSeconds <= MAX_BACKOFF_SECONDS * 2; backOffTimeInSeconds *= 2)
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
{
try
{
auto pushChannel = co_await channelManager.CreatePushNotificationChannelForApplicationAsync();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API call will fail for Unpackaged apps. Recommendation is to have a check somewhere to see if the solution is packaged or not and maybe fail the API for unpackaged. We should fix this for V1 though when we call into Registration private APIs for unpackaged apps

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: When you call into the Private APIs for unpackaged apps, you would need to set the PFN to remoteId so that we send it across the wire. That should be supported in downlevel OS's too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bool IsPackagedProcess()
{
    UINT32 n = 0;
    const auto rc{ GetCurrentPackageFullName(&n, nullptr) };
    THROW_WIN32_ERROR_IF_MSG(HRESULT_FROM_WIN32(rc), (rc != APPMODEL_ERROR_NO_PACKAGE) && (rc != ERROR_INSUFFICIENT_BUFFER), "GetCurrentPackageFullName rc=%d", rc);
    return rc == ERROR_INSUFFICIENT_BUFFER;
}

bool IsPackagedProcess_failfast()
{
    UINT32 n = 0;
    const auto rc{ GetCurrentPackageFullName(&n, nullptr) };
    FAIL_FAST_WIN32_ERROR_IF_MSG(HRESULT_FROM_WIN32(rc), (rc != APPMODEL_ERROR_NO_PACKAGE) && (rc != ERROR_INSUFFICIENT_BUFFER), "GetCurrentPackageFullName rc=%d", rc);
    return rc == ERROR_INSUFFICIENT_BUFFER;
}


if (pushChannel != nullptr)
{
channelResult = winrt::make<winrt::Microsoft::ProjectReunion::implementation::ChannelResult>(pushChannel, S_OK, ChannelStatus::CompletedSuccess);
break;
}
}
catch (winrt::hresult_error const& ex)
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
{
if ((backOffTimeInSeconds <= MAX_BACKOFF_SECONDS) && isChannelRequestRetryable(ex.code()))
{
progress(winrt::make<winrt::Microsoft::ProjectReunion::implementation::ChannelResult>(nullptr, ex.code(), ChannelStatus::InProgressRetry));
}
else
{
channelResult = winrt::make<winrt::Microsoft::ProjectReunion::implementation::ChannelResult>(nullptr, ex.code(), ChannelStatus::CompletedFailure);
break;
}
}

co_await winrt::resume_after(std::chrono::seconds(backOffTimeInSeconds));
}

lock.lock();
remoteIdList.remove(remoteId);
lock.unlock();

co_return channelResult;

}
}
9 changes: 9 additions & 0 deletions dev/PushNotifications/PushManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

#pragma once

#include "PushManager.g.h"
#include <list>
#include <mutex>

namespace winrt::Microsoft::ProjectReunion::implementation
{
Expand All @@ -11,6 +14,12 @@ namespace winrt::Microsoft::ProjectReunion::implementation
PushManager() = default;

static Windows::Foundation::IAsyncOperationWithProgress<Microsoft::ProjectReunion::ChannelResult, Microsoft::ProjectReunion::ChannelResult> CreateChannelAsync(winrt::guid remoteId);

private:
static bool isChannelRequestRetryable(const winrt::hresult& hrException);
static std::list<winrt::guid> remoteIdList;
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
static std::mutex mtx;
sharath2727 marked this conversation as resolved.
Show resolved Hide resolved
static std::unique_lock<std::mutex> lock;
};
}
namespace winrt::Microsoft::ProjectReunion::factory_implementation
Expand Down