UWP AppService to C++ SendRequestAsync hangs / never gets response

  c++, uwp, windows-runtime

I have 2 apps in question here, a UI app that is C# UWP and launches a backend EXE (C++, WinRT) via the LaunchFullTrustProcessForCurrentAppAsync API. The backend EXE then creates an in-process AppService connection back to the UI to communicate back and forth. The components establish the connection, when the UWP UI App calls a SendRequestAsync, the backend receives it in its RequestReceived callback. It then builds a response and returns from OnRequestReceived. But the calling UI SendRequestAsync() never returns, it hangs forever.

Code from the UWP C# App

 class FrontendAppService
    {
        private AppServiceConnection AppServiceConnection { get; set; }
        private BackgroundTaskDeferral AppServiceDeferral { get; set; }

        public event EventHandler<string> MessageReceivedEvent;

        public bool Connected 
        {
            get
            {
                return AppServiceConnection != null;
            }
        }

        private static FrontendAppService instance;
        public static FrontendAppService Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new FrontendAppService();
                }

                return instance;
            }
        }

        private FrontendAppService()
        {
            AppServiceConnection = null;
        }

        public void BackgroundActivated(IBackgroundTaskInstance taskInstance)
        {
            if (taskInstance.TriggerDetails is AppServiceTriggerDetails)
            {

                AppServiceTriggerDetails appService = taskInstance.TriggerDetails as AppServiceTriggerDetails;
                AppServiceDeferral = taskInstance.GetDeferral();
                AppServiceConnection = appService.AppServiceConnection;
                AppServiceConnection.RequestReceived += OnAppServiceRequestReceived;
                AppServiceConnection.ServiceClosed += AppServiceConnection_ServiceClosed;
            }
        }

        private void OnAppServiceRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
        }

        private void AppServiceConnection_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
        {
            AppServiceDeferral.Complete();
            AppServiceConnection = null;
        }

        public async Task<AppServiceResponse> SendRequestAsync(Windows.Foundation.Collections.ValueSet message)
        {
            return await AppServiceConnection.SendMessageAsync(message);
        }
    }

Code from the C++ Backend

struct AppServiceServerImpl : winrt::implements<AppServiceServerImpl, IInspectable>
{
private:
    Windows::ApplicationModel::AppService::AppServiceConnection connection{ nullptr };

    fire_and_forget OnServiceClosed(AppServiceConnection const&, AppServiceClosedEventArgs const&)
    {
        LOG(AixLog::Severity::info) << "AppService connection lost" << std::endl;

        auto lifetime = get_strong();

        //Close the connection reference we're holding
        if (connection != nullptr)
        {
            connection.Close();
            connection = nullptr;
        }

        co_return;
    }

    fire_and_forget OnRequestReceived(AppServiceConnection const&, AppServiceRequestReceivedEventArgs const& args)
    {
        LOG(AixLog::Severity::info) << "AppService message received" << std::endl;

        //Get a deferral so we can use an awaitable API to respond to the message
        auto messageDeferral = args.GetDeferral();

        try
        {

            ValueSet input = args.Request().Message();

            winrt::hstring action;
            winrt::hstring path;
            winrt::hstring body;
            winrt::com_array<winrt::hstring> headerNames;
            winrt::com_array<winrt::hstring> headerValues;

            // Parse out the HTTP data
            if( input.HasKey(L"action") )
                action = input.TryLookup(L"action").try_as<IReference<winrt::hstring>>().GetString();
            if (input.HasKey(L"path"))
                path = input.TryLookup(L"path").try_as<IReference<winrt::hstring>>().GetString();
            if (input.HasKey(L"body"))
                body = input.TryLookup(L"body").try_as<IReference<winrt::hstring>>().GetString();


            std::string url = "http://localhost:" + std::to_string(BackendConfig::GlobalBackendConfig.BackendApiPort) + "/" + winrt::to_string(path);
            Windows::Foundation::Uri uri{ winrt::to_hstring(url) };
            Windows::Web::Http::HttpClient httpClient{};

            std::wstring httpResponseBody;
            int httpStatusCode;
            std::wstring httpStatusText;

            // Always catch network exceptions for async methods
            try
            {
                Windows::Web::Http::HttpResponseMessage httpResponseMessage;
                Windows::Web::Http::HttpStringContent postContent{ winrt::to_hstring(body) };

                try
                {
                    if (action == L"GET")
                    {
                        httpResponseMessage = httpClient.GetAsync(uri).get();

                    }
                    else
                    {
                        LOG(AixLog::Severity::error) << "AppService unknown action received! " << winrt::to_string(action) << std::endl;
                        throw std::exception("AppService unknown action received!");
                    }

                    httpResponseBody = httpResponseMessage.Content().ReadAsStringAsync().get();
                    httpStatusCode = (int)httpResponseMessage.StatusCode();
                    httpStatusText = httpResponseMessage.ReasonPhrase();
                }
                catch (winrt::hresult_error const& ex)
                {
                    httpResponseBody = ex.message();
                    httpStatusCode = 500;
                    httpStatusText = L"Exception handling request";
                }
            }
            catch (winrt::hresult_error const& ex)
            {
                httpResponseBody = ex.message();
                httpStatusCode = 500;
                httpStatusText = L"Exception handling request";
            }

            //Create the response
            ValueSet result;
            result.Insert(L"status_code", box_value(httpStatusCode));
            result.Insert(L"status_text", box_value(httpStatusText));
            result.Insert(L"body", box_value(httpResponseBody));

            //Send the response
            auto r = co_await args.Request().SendResponseAsync(result);
            if (r != AppServiceResponseStatus::Success)
            {
                LOG(AixLog::Severity::info) << "Response send failed, status=" << (int)r << std::endl;
            }
        }
        catch (std::exception e)
        {
            LOG(AixLog::Severity::error) << "Exception dealing with a response on AppService: " << e.what() << std::endl;
        }

        LOG(AixLog::Severity::info) << "Response sent! " << std::endl;

        // Signal when complete
        try
        {
            messageDeferral.Complete();
        }
        catch( std::exception e )
        {
            LOG(AixLog::Severity::error) << "Failed too complete deferral! " << e.what() << std::endl;
        }
    }


public:

    fire_and_forget ConnectToAppServiceAsync(std::string frontendAppFamily)
    {
        auto lifetime = get_strong();
        bool connected = false;

        //Is a connection already open?
        if (connection != nullptr)
        {
            LOG(AixLog::Severity::error) << "AppService connection already exists" << std::endl;
            co_return;
        }

        //Set up a new app service connection
        connection = AppServiceConnection();
        connection.AppServiceName(L"com.frontend");
        connection.PackageFamilyName(winrt::to_hstring(frontendAppFamily));
        connection.ServiceClosed({ get_weak(), &AppServiceServerImpl::OnServiceClosed });
        connection.RequestReceived({ get_weak(), &AppServiceServerImpl::OnRequestReceived });

        while (!connected)
        {
            AppServiceConnectionStatus status = co_await connection.OpenAsync();

            //"connection" may have been nulled out while we were awaiting.
            if (connection == nullptr)
            {
                LOG(AixLog::Severity::error) << "AppService Connection was closed" << std::endl;
                co_return;
            }

            //If the new connection opened successfully we're done here
            if (status == AppServiceConnectionStatus::Success)
            {
                LOG(AixLog::Severity::info) << "AppService Connection is open" << std::endl;
                connected = true;
            }
            else
            {
                //Something went wrong. Lets figure out what it was and show the 
                //user a meaningful message
                switch (status)
                {
                case AppServiceConnectionStatus::AppNotInstalled:
                    LOG(AixLog::Severity::error) << "The app AppServicesProvider is not installed. Reinstall on this device and try again." << std::endl;
                    break;

                case AppServiceConnectionStatus::AppUnavailable:
                    LOG(AixLog::Severity::error) << "The app AppServicesProvider is not available. This could be because it is currently being updated or was installed to a removable device that is no longer available." << std::endl;
                    break;

                case AppServiceConnectionStatus::AppServiceUnavailable:
                    LOG(AixLog::Severity::error) << "The app AppServicesProvider is installed but it does not provide the app service " << connection.AppServiceName().c_str() << std::endl;
                    break;

                default:
                case AppServiceConnectionStatus::Unknown:
                    LOG(AixLog::Severity::error) << "An unknown error occurred while we were trying to open an AppServiceConnection." << std::endl;
                    break;
                }

                //Clean up before we go
                //connection.Close();
                //connection = nullptr;
            }

            Sleep(500);
        }
    }

    void CloseAppServiceAsync()
    {
        LOG(AixLog::Severity::info) << "AppService Connection is now closing" << std::endl;

        if (connection == nullptr)
        {
            LOG(AixLog::Severity::error) << "There's no open connection to close" << std::endl;
            return;
        }

        connection.Close();
        connection = nullptr;
    }
};

For the purpose of this example, it is simply copying an HTTP GET request, that all works. The console output from the backend looks something like this:

Starting AppServiceServer Server
AppService Connection is open
AppService message received
Response sent!

From the code, the C# called

var response = await FrontendAppService.Instance.SendRequestAsync(input);

This is the function that never returned. I would have expected it to return when the backend application completed the deferral in the OnRequestReceived? And ideally with the contents of the call to Request().SendResponseAsync(result)?

Source: Windows Questions C++

LEAVE A COMMENT