Giter VIP home page Giter VIP logo

Comments (24)

timcassell avatar timcassell commented on July 4, 2024 1

Aha, looks like it was fixed in #306! To confirm, I added a try/catch:

try
{
    await Promise.SwitchToForeground();
}
catch (Exception e)
{
    Debug.LogException(e);
}

And that actually logs
InvalidOperationException: SynchronizationOption.Foreground was provided, but Promise.Config.ForegroundContext was null. You should set Promise.Config.ForegroundContext at the start of your application (which may be as simple as 'Promise.Config.ForegroundContext = SynchronizationContext.Current;').

@drew-512 If you try latest master, it should be fixed. Thank you very much for the bug report, and please let me know if you hit any other issues!

from protopromise.

timcassell avatar timcassell commented on July 4, 2024

Hi Drew,

UnobservedPromiseException means a promise was not awaited or forgotten. If a deferred was left pending, it would instead be a UnhandledDeferredException.

The exception message should explain how to enable causality traces to find the culprit. You need to be in DEBUG mode and set Promise.Config.DebugCausalityTracer = Promise.TraceLevel.All.

from protopromise.

drew-512 avatar drew-512 commented on July 4, 2024

Ah, ok thanks for the correction there.

I did have Promise.TraceLevel.All set, however it's unclear what I need to look at for help. There are a ton of fields and the ones that are interesting like the stack trace are truncated by VSCode.

What and where should I look for more information?

Screen Shot 2023-10-18 at 2 28 38 PM

from protopromise.

timcassell avatar timcassell commented on July 4, 2024

Oh, it looks you even enabled developer mode! That shouldn't be necessary for you, that's really for me to debug the internal promise code. You should disable that to get a cleaner causality trace (it strips internal promise frames if it's disabled).

The information you're looking for is in the Stacktrace of the UnhandledException, in this case the one that contains the UnobservedPromiseException as its InnerException.

from protopromise.

timcassell avatar timcassell commented on July 4, 2024

I think VS has options to view the entire stacktrace if you right-click it. Otherwise, you could try grabbing the ToString() of the entire exception, which should include all of the stacktrace information.

from protopromise.

timcassell avatar timcassell commented on July 4, 2024

Also, when it's printed to the console in Unity, it should include the full stacktrace there. If it's truncated in the console, you can view the full thing in the log file (the console can open it directly from the ... dropdown).

from protopromise.

drew-512 avatar drew-512 commented on July 4, 2024

I wasn't getting any info from Unity since the exception was happening when play mode was starting (when GC was likely happening from the last play). This means any exception msgs pushed to Unity were being cleared as play mode begins. Meanwhile, VSCode / C# is still highly unimpressive with strings in the debugger. I ended up just putting in some lines that print the stack trace to a Debug.Log message. Given this somewhat likely scenario (where GC occurring right before entering play mode), perhaps an enhancement here makes sense.

Anyway, the offender suprisingly is await Promise.SwitchToForeground(), made from an async void proc that exits during app shutdown (play mode exit):

async void sessionReader(NetworkStream stream) {
    await Promise.SwitchToBackground();
    string logMsg = null;
    try {
        var hdr = new byte[TxMsg.TxHeaderSz];
        int bytesRead;                     
        
        while ((bytesRead = stream.Read(hdr, 0, TxMsg.TxHeaderSz)) == TxMsg.TxHeaderSz) {
            var tx = TxMsg.Acquire(hdr);
            if ((bytesRead = stream.Read(tx.TxStore, TxMsg.TxBodyOfs, tx.TxBodyLen)) != tx.TxBodyLen) {
                throw new Exception($"Failed to read TxMsg body: {bytesRead} != {tx.TxBodyLen}");
            }
            handleIncomingTx(tx);
        }
    } catch (Exception ex) {
        logMsg = ex.ToString();
    }

    // Cancel the writer if the reader stops
    Cancel();
    
    await Promise.SwitchToForeground();
    if (logMsg != null) {
        Logger.Warn(logMsg);
    }
    
    // Tell controller we're fully disconnected
    _client.OnConnectionClosed();
}

from protopromise.

timcassell avatar timcassell commented on July 4, 2024

I wasn't getting any info from Unity since the exception was happening when play mode was starting (when GC was likely happening from the last play). This means any exception msgs pushed to Unity were being cleared as play mode begins.

You can fix that by disabling clear on play mode.

Screen Shot 2023-10-18 at 4 35 36 PM

Anyway, the offender suprisingly is await Promise.SwitchToForeground(), made from an async void proc that exits during app shutdown (play mode exit):

I'm guessing that's because you have AppDomain reload disabled. The default exception handler ignores any further exceptions after OnApplicationQuit. Since it occurs when you start the game again, it no longer considers it to be in the quitting state, so it does log it.

I can't think of a nice way to handle that case, I'm open to suggestions.

from protopromise.

drew-512 avatar drew-512 commented on July 4, 2024

Hmm, is there any way to catch or prevent whatever is going on with that await Promise.SwitchToForeground()?

from protopromise.

timcassell avatar timcassell commented on July 4, 2024

Hmm, is there any way to catch or prevent whatever is going on with that await Promise.SwitchToForeground()?

Suffice to say, there's nothing wrong with your code, and you won't see this issue in production. This is purely a side effect of stopping and restarting code without resetting its run context (AppDomain reload here, process isolation in built player).

from protopromise.

timcassell avatar timcassell commented on July 4, 2024

Perhaps I could add an API like Promise.Manager.ResetRuntimeContext() that can be called from OnApplicationQuit or SubsystemRegistration. That would then ignore exceptions from every object created before ResetRuntimeContext was called.

from protopromise.

drew-512 avatar drew-512 commented on July 4, 2024

If it's interesting, Unity seems to be being mean to your exceptions.
Screen Shot 2023-10-18 at 5 51 17 PM

from protopromise.

timcassell avatar timcassell commented on July 4, 2024

That looks like it's from throwing the exception on a ThreadPool thread after the default exception handler is removed from OnApplicationQuit. Kinda weird that they truncate the inner exceptions, but oh well. I guess their background thread exception handler just logs warning with the exception's message, rather than its full ToString.

from protopromise.

timcassell avatar timcassell commented on July 4, 2024

So I tried to repro that code in a test case, and it passes every time I run it. I debugged it and saw the promise was marked as awaited (as it should be). The only thing I can think of is the background thread is being aborted in the middle of the await. I'm unaware of Unity aborting background threads without the AppDomain being reloaded. Are you perhaps running your own background SynchronizationContext and aborting threads yourself?

private class StrongBox<T>
{
    public T value;
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static async void RunAndSwitchContext(StrongBox<bool> box, SynchronizationContext context)
{
    await Promise.SwitchToBackground();
    box.value = true;
    await Promise.SwitchToContext(context);
    box.value = true;
}

[Test]
public void PromiseSwitchToContext_NeverExecuted_NoExceptions()
{
    // We're purposefully not completing the promise, to simulate Unity's enter/exit playmode.
    var box = new StrongBox<bool>();
    RunAndSwitchContext(box, new NeverExecutedSynchronizationContext());
    while (!box.value) { Thread.MemoryBarrier(); }
    box.value = false;
    Thread.Sleep(1000);
    Assert.False(box.value);

    TestHelper.GcCollectAndWaitForFinalizers();
}

from protopromise.

timcassell avatar timcassell commented on July 4, 2024

@drew-512 Can you confirm if aborting threads is the cause?

from protopromise.

drew-512 avatar drew-512 commented on July 4, 2024

Hi Tim,

Not aborting any threads. I start a reader and writer via await Promise.SwitchToBackground(); and only exit those threads (which I'm not regarding as aborting).

Can I test something specifically?

from protopromise.

timcassell avatar timcassell commented on July 4, 2024

So you're not setting anything in Promise.Config? Just using the default values? I'm trying to determine the root cause of the issue, and if it's not thread aborts, I can't think of what it might be.

from protopromise.

timcassell avatar timcassell commented on July 4, 2024

@drew-512 Could you possibly send me a repro project?

from protopromise.

drew-512 avatar drew-512 commented on July 4, 2024

If I'm hearing you right, you'd like a repro project of the shows a warning as in my Oct 18 post?

I have to sheepishly admit that I didn't event know about Promise.Config until your mention of it above (and so had not made any changes to that).

from protopromise.

timcassell avatar timcassell commented on July 4, 2024

If I'm hearing you right, you'd like a repro project of the shows a warning as in my Oct 18 post?

If possible, yes. I'm uncomfortable merging #303 without understanding the actual cause.

I have to sheepishly admit that I didn't event know about Promise.Config until your mention of it above (and so had not made any changes to that).

No worries, that's good to know. (Although if you had set Promise.TraceLevel.All, then you were using it. 😉)

from protopromise.

drew-512 avatar drew-512 commented on July 4, 2024

Sorry for the delay, been a tough go personally lately.

Ok, I'll use the latest release version (not branch)? What vars should I set exactly out of Promise.Config to give you the output or stack that you're looking for?

from protopromise.

timcassell avatar timcassell commented on July 4, 2024

You didn't change any defaults before so no need to do so now. I just need to be able to repro it like you had before, then I can start debugging it myself.

from protopromise.

timcassell avatar timcassell commented on July 4, 2024

Nevermind, I was able to reproduce it with this code in editor.

void Start()
{
    for (int i = 0; i < Environment.ProcessorCount; ++i)
    {
        Func().Forget();
    }
}

private async Promise Func()
{
    while (true)
    {
        await Promise.SwitchToBackground();
        Thread.Sleep(10);
        await Promise.SwitchToForeground();
    }
}

from protopromise.

drew-512 avatar drew-512 commented on July 4, 2024

Yay, nice work! Switching to main!

from protopromise.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.