-
Notifications
You must be signed in to change notification settings - Fork 1k
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
StackOverflowException after upgrading from .Net 6 to .Net 8 #40891
Comments
dotnet/runtime#79966 added the The following buggy program causes a stack overflow already in .NET 6.0. However, the resulting stack trace does not match the screen shot; it does not include the get_HasChanged accessor. using System.Threading;
using Microsoft.Extensions.Primitives;
class Program
{
static void Main(string[] args)
{
// BUG: This changeTokenProducer always returns a change token that
// already indicates a change, so ChangeToken.OnChange calls it again
// to get a new change token, and these calls overflow the stack.
ChangeToken.OnChange(
changeTokenProducer: CreateChangedChangeToken,
changeTokenConsumer: () => {});
}
static IChangeToken CreateChangedChangeToken()
{
return new CancellationChangeToken(new CancellationToken(canceled: true));
}
} The following buggy program causes a stack overflow in .NET 8.0 but not in .NET 6.0. The stack trace mostly matches the screen shot. using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Microsoft.Extensions.Primitives;
static class Program
{
static CancellationTokenSource source = new CancellationTokenSource();
static void Main(string[] args)
{
// BUG: This changeTokenProducer always returns a change token that
// already indicates a change, so ChangeToken.OnChange calls it again
// to get a new change token, and these calls overflow the stack.
ChangeToken.OnChange(
changeTokenProducer: ProduceChangeToken,
changeTokenConsumer: ConsumeChangeToken);
}
static IChangeToken ProduceChangeToken()
{
ManualChangeToken manual = new ManualChangeToken();
IChangeToken[] changeTokens =
{
manual,
};
IChangeToken result = new CompositeChangeToken(changeTokens);
manual.SetChanged();
return result;
}
static void ConsumeChangeToken()
{
}
}
// A change token that can be manually set as changed,
// but does not have active callbacks; it calls the callbacks only when
// the HasChanged property is read or a new callback is registered.
internal sealed class ManualChangeToken : IChangeToken
{
private List<Entry> callbacks = new List<Entry>();
private bool hasChanged;
public bool ActiveChangeCallbacks => false;
public bool HasChanged
{
get
{
if (!Volatile.Read(ref this.hasChanged))
{
return false;
}
this.RunCallbacks();
return true;
}
}
public IDisposable RegisterChangeCallback(Action<object?> callback, object? state)
{
Entry entry = new Entry(this, callback, state);
lock (this.callbacks)
{
this.callbacks.Add(entry);
}
_ = this.HasChanged;
return entry;
}
internal void SetChanged()
{
Volatile.Write(ref this.hasChanged, true);
}
private void RunCallbacks()
{
Debug.Assert(this.hasChanged);
while (true)
{
Entry entry;
lock (this.callbacks)
{
if (this.callbacks.Count == 0)
{
break;
}
entry = this.callbacks[this.callbacks.Count - 1];
this.callbacks.RemoveAt(this.callbacks.Count - 1);
}
entry.Invoke();
}
}
private sealed class Entry : IDisposable
{
private readonly ManualChangeToken changeToken;
private readonly Action<object?> callback;
private readonly object? state;
internal Entry(ManualChangeToken changeToken, Action<object?> callback, object? state)
{
this.changeToken = changeToken;
this.callback = callback;
this.state = state;
}
public void Dispose()
{
lock (this.changeToken.callbacks)
{
this.changeToken.callbacks.Remove(this);
}
}
internal void Invoke()
{
this.callback.Invoke(this.state);
}
}
} So, it seems your program has a change token producer that returns change tokens that have already expired. Does your program include any custom IFileProvider or IChangeToken implementation? |
Below is my Program.cs:
As you can see, we don't have any custom code related to change tokens and no custom IFileProvider or IChangeToken implementation. The OnChangeTokenFired() method is from dll decompiled code in which breakpoint can't be put directly. |
Does that get a stack overflow on .NET 8.0 in Visual Studio, even with a no-op internal class Startup
{
public void Configure() {}
} |
Below is my Startup.cs:
#if !DEBUG
#if DEBUG
Which method code do u suggest to comment out and check if stack overflow is occurring? |
I'd try removing App Insights first, but this is only a guess. I wonder if Visual Studio is injecting something buggy via HostFactoryResolver. |
I have tried removing App Insights but still issue persists. Please suggest if u have any other solution. |
AFAIK it's possible to set a breakpoint by method name even if you don't have the source code. |
I guess AssemblyNameParser does not cause the stack overflow, but recursion in other classes first consumes almost all the stack, and then something calls AssemblyNameParser which cannot work with so little stack remaining. |
Hello,
I have upgraded 5 projects in my solution from .Net 6 to .Net 8 using upgrade assistant in Visual Studio.
When I run the solution, StackOverflowException is being thrown and Swagger is not loading. Looks like onchangetokenfired() method (This method is from dll code) is being called many times resulting in infinite loop.
However, we deployed same code to Azure App Service and it is working fine. So, looks like it is working in publish mode and not in debug mode in Visual Studio in local.
While I run the solution in VS Code via command "dotnet run" it is running successfully but to debug the code I've installed C# toolkit in VS Code same exception is being thrown.
Please help in fixing this issue in Visual Studio.
The text was updated successfully, but these errors were encountered: