Aspnetcore: JS interop can cause unhandled server-side exception if dotNetObjectId is unknown/disposed

Created on 27 Jun 2019  路  10Comments  路  Source: dotnet/aspnetcore

@SteveSandersonMS hijacking the original post... see the comment below for a more concise statement about what we need to fix.


Describe the bug

A clear and concise description of what the bug is.
I need to process client event on server but after some timeout.
Scenario:

  1. user types in input
  2. user removes focus from input
  3. some timeout passes, if user do not return focus to input -> js code calls server method of DotNetObjectRef , created from an instance of server component (ComponentBase deriven class).

To Reproduce

Problem happens when user types something in input and click some navigation link.
In this case following happens:

  1. user types in input
  2. user clicks navigation link -> it removes focus from input
  3. previous component disposes (and disposes the DotNetObjectRef object)
  4. some timeout passes -> js code calls server method of disposed instance of server component (and disposed DotNetObjectRef object created from that disposed component)
  5. client application gets "There is no tracked object with id"... "Perhaps the DotNetObjectRef instance was already disposed." message and spots working.

Expected behavior

Since the DotNetObjectRef object and the target component are disposed operation can't be completed, but there is a way for me to ignore it, because i do not need it any more in this scenario.

Simplest project to reproduce

Counter2.razor

@page "/counter2"
@inherits SomeNameSpace.Counter2Base

<input @ref="@input" />
<span>@Text</span>

Counter2.razor.cs

using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System;

namespace SomeNameSpace
{
    public partial class Counter2Base : ComponentBase, IDisposable
    {
        public ElementRef input;
        public string Text;

        [Inject] IJSRuntime JsRuntime { get; set; }
        [Inject] IComponentContext ComponentContext { get; set; }

        object objReg;
        object ObjReg {
            get {
                if (objReg == null)
                    objReg = DotNetObjectRef.Create(this);
                return objReg;
            }
        }

        protected override void OnAfterRender()
        {
            SubscribeEvents();
        }

        public async void SubscribeEvents()
        {
            await JsRuntime.InvokeAsync<object>(
                    "exampleJsFunctions.SubscribeEvents", input, ObjReg);
        }

        [JSInvokable]
        public void LostFocus(string text)
        {
            Text = text;
            StateHasChanged();
        }

        public void Dispose()
        {
            var disposableObjReg = objReg as IDisposable;
            if(disposableObjReg != null)
                disposableObjReg.Dispose();
        }
    }
}

exampleJsInterop.js

window.exampleJsFunctions = {
    SubscribeEvents: function (element, dotnetHelper) {
        element.addEventListener('blur', function (e) {
            window.setTimeout(function () {
                dotnetHelper.invokeMethodAsync('LostFocus', e.target.value);
            }, 1000);
        });
    },
};
  1. Navigate to "counter2" page
  2. Open console dev tools in browser to see the error message
  3. type something to the input and click some navigation link except "counter2" to leave this page
  4. error, app stops operating

Possible workaround?

What is the best strategy to workaround this?

  1. I can remove the objReg disposing code (the object created by DotNetObjectRef). But who will be releasing it in this case?

  2. I can create a static collection of that objReg objects, mark them with some "ReadyToDispose" flag instead of disposing, and dispose them after some safe timeout on server.

May be something also? Please suggest.

Thank you!

Duplicate area-blazor bug

All 10 comments

If I understand correctly, the problem here is the fact that the unhandled exception causes the app to die. That is, you understand why making a call on a DotNetObjectRef that has already been disposed will lead to an exception, but you want to be able to catch and handle that exception (e.g., in your JS code) without it killing the app on the server.

If so, you're absolutely right. This is a bug we need to address. I'll update the issue title to match.

Update: I've confirmed that this kind of exception does not kill the server process. It would have been very strange if it did. It's simply an exception during the invocation of a SignalR hub method (ComponentHub.BeginInvokeDotNetFromJS), so it doesn't kill the process, but does terminate the SignalR connection.

We still need to convert this flow into something that behaves the same as if the JS-invoked method itself threw, so the called on the JS side gets back a rejected promise and can handle it if they want.

@SteveSandersonMS ,

Yes, I would be happy to handle the exception in my JS to ignore it in this case, and allow the user to proceed interating with the application without reloading page.

Thank you!

This is already fixed in Preview 7.

Preview 7 - the issue is still there.

The same scenario as above.

Get the same error:

Error: System.ArgumentException: There is no tracked object with id '1'. Perhaps the DotNetObjectRef instance was already disposed. (Parameter 'dotNetObjectId')
   at Microsoft.JSInterop.DotNetObjectRefManager.FindDotNetObject(Int64 dotNetObjectId)
   at Microsoft.JSInterop.DotNetDispatcher.BeginInvoke(String callId, String assemblyName, String methodIdentifier, Int64 dotNetObjectId, String argsJson)
   at Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost.<>c__DisplayClass45_0.<BeginInvokeDotNetFromJS>b__0()
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c.<Invoke>b__8_0(Object state)
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost.BeginInvokeDotNetFromJS(String callId, String assemblyName, String methodIdentifier, Int64 dotNetObjectId, String argsJson)

User's session is destroyed. Browser's page stops operating until reloaded.

@ZergZe Could you provide a github project showcasing the repro?

@javiercn

Github project

Project has been created by cli:

$ dotnet new blazorserverside

Version:

$ dotnet --version
3.0.100-preview7-012821

Problem:

Application stops operating. I have no chan褋e to handle and ignore the exceptions.

@ZergZe Thanks for the repro, I think there was a confusion and this didn't make it into preview7. But it is indeed fixed.

https://github.com/aspnet/Extensions/blob/master/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs#L84-L96

@javiercn Thank you very much for the clarification.
I will wait for the future update.

I still reproduce the issue in 3.0.0. @javiercn could you please tell us in which release to expect the fix?

Hi @jivanova .

It looks like you are posting on a closed issue!

We're very likely to lose track of your bug/feedback/question unless you:

  1. Open a new issue
  2. Explain very clearly what you need help with
  3. If you think you have found a bug, include a link to a github repo with a minimal repro project and detailed steps so that we can investigate the problem.
Was this page helpful?
0 / 5 - 0 ratings

Related issues

davidfowl picture davidfowl  路  126Comments

glennc picture glennc  路  117Comments

kevinchalet picture kevinchalet  路  761Comments

radenkozec picture radenkozec  路  114Comments

oliverjanik picture oliverjanik  路  91Comments