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

Conversation

sharath2727
Copy link
Contributor

Issue:
As part of the Reunion effort, introduce retry channel request logic within the CreateChannelAsync API to be able to handle benign channel request failures on behalf of the developer.

Description:
As of today, we believe when we fail to fetch channel requests due to errors like ERROR_TIMEOUT etc.., we are throwing exceptions right away and propagating back to the user. These are benign errors and gives us the flexibility to retry channel requests and avoid throwing exceptions for such cases. So for reunion implement a retry logic to request channels on behalf of app developers when we see mentioned errors. This will reduce the number of exceptions we propagate back to the app developers and improves the success rate for channel requests for the caller.

dev/PushNotifications/PushManager.cpp Outdated Show resolved Hide resolved
dev/PushNotifications/PushManager.cpp Outdated Show resolved Hide resolved
dev/PushNotifications/PushManager.h Outdated Show resolved Hide resolved
dev/PushNotifications/ChannelResult.h Show resolved Hide resolved
private:
Windows::Networking::PushNotifications::PushNotificationChannel m_channel{ nullptr };
winrt::hresult m_extendedError;
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.

dev/PushNotifications/ChannelResult.h Outdated Show resolved Hide resolved
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.

#define WNP_E_RECONNECTING 0x880403E9L

// Error code - Current bind operation failed because another bind operation in progress
#define WNP_E_BIND_USER_BUSY 0x880403FEL
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.

dev/PushNotifications/PushManager.cpp Outdated Show resolved Hide resolved
dev/PushNotifications/PushManager.cpp Outdated Show resolved Hide resolved
winrt::Microsoft::ProjectReunion::ChannelResult channelResult{ nullptr };
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.

dev/PushNotifications/PushManager.cpp Outdated Show resolved Hide resolved
dev/PushNotifications/PushManager.h Outdated 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;
}

@sharath2727 sharath2727 changed the title Implement createchannelasync for requesting channels [Prototype] Implement createchannelasync for requesting channels Mar 4, 2021
return false;
}
}

Windows::Foundation::IAsyncOperationWithProgress<Microsoft::ProjectReunion::ChannelResult, Microsoft::ProjectReunion::ChannelResult> PushManager::CreateChannelAsync(winrt::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.

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.

@sharath2727
Copy link
Contributor Author

As the spec changed. I am closing this branch and raising a new draft PR for the new branch.

@bpulliam bpulliam deleted the user/vemancha/retrylogicchannelrequest branch February 6, 2023 13:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants