Search code examples
websocketuwpdesktop-bridgemicrosoft-edge-extension

Edge extension: BackgroundTaskInstance cancels with SystemPolicy reason when DesktopBridge app tries to open WebSocket


I created an Edge browser extension which uses Native Messaging to a native app running via a Desktop Bridge technology. I used the SecureInput as a sample, which contains the Edge extension, UWP host and a Win32 Desktop Bridge app.

I need the Win32 Desktop Bridge app to connect to a web service using HTTP and WebSocket, so I added an internetClientServer and a privateNetworkClientServer capabilities to the package manifest, beside the already existed runFullTrust one.

The Win32 Desktop Bridge app activates just fine, and it is able to connect to the web server using HTTP. But as soon as it tries to open a WebSocket connection, the BackgroundTaskInstance on the UWP host receives a cancellation request with a BackgroundTaskCancellationReason.SystemPolicy as a reason, and the Desktop Bridge application closes. Unfortunately, the documentation for the BackgroundTaskCancellationReason.SystemPolicy does not explain much about true reasons of the cancellation request.

I tried to use two WebSocket classes: the System.Net.WebSockets.ClientWebSocket and the Windows.Networking.Sockets.MessageWebSocket, with the same result. No fancy code, just regular

var socket = new MessageWebSocket();
...
await socket.ConnectAsync(new Uri("wss://127.0.0.1:9001/myservice"));

The same WebSocket service endpoint is available from other WS clients, so I guess there is no server/firewall/antivirus issue here.

I also played with the CheckNetIsolation tool, adding loopback exemption for the Edge browser and for the package, with no effect. The HTTP works fine without the loopback exemption.

What may be a true reason of the task cancellation, and what can be a possible way to prevent it?


Solution

  • Ok, I resolved the issue. Thanks to this comment by Tom Shane I stumbled upon, I realized that the BackgroundTaskCancellationReason.SystemPolicy tells that the background task is closed by the system to release some system resources, and that in my case it happened because I didn't obtain a deferral in my async event handler. When the event handler yielded without a deferral, the system decided it can shut the task down. Below is a digested version of the code:

    static class Program
    {
        static AppServiceConnection connection = null;
    
        [STAThread]
        static void Main(string[] args)
        {
            Thread appServiceThread = new Thread(new ThreadStart(ThreadProc));
            appServiceThread.Start();
            Application.Run();
        }
    
        static async void ThreadProc()
        {
            try {
                connection = new AppServiceConnection();
                connection.AppServiceName = "...";
                connection.PackageFamilyName = Windows.ApplicationModel.Package.Current.Id.FamilyName;
                connection.RequestReceived += OnRequestReceived;
                connection.ServiceClosed += OnServiceClosed;
                var status = await connection.OpenAsync();
                ....
            }
            catch (Exception e) { ... }
        }
    
        private static async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
            var defer = args.GetDeferral();   // <== that was missing, rookie mistake!
            try {
                var msg = ParseMessage(args.Request.Message);
                if (msg.type.Equals("ws")) {
                    // this method was truly async
                    // and every time it yielded the issue was revealed
                    await HandleWsMessage(request, msg); 
                }
                else if (msg.type.Equals("http")) {
                    // but this method was actually synchronous despite being marked as "async" 
                    // and it never yielded, masking the issue for HTTP client
                    await HandleHttpMessage(request, msg);
                } 
            }
            catch (Exception e) { ... }
            finally {
                defer.Complete();
            }
        }
    }