.net linux服务中的线程异常增加

6za6bjd0  于 2022-12-01  发布在  .NET
关注(0)|答案(1)|浏览(121)

我有一个服务,它在Linux下运行在SystemD下,但在Windows下的VS 22中编译和调试。该服务主要是一个MariaDB 10数据库的代理,该数据库被塑造为一个BackgroundWorker,通过SignalR服务于客户端。如果我在Windows上以释放模式运行它,逻辑线程的数量保持在一个合理的值(大约20-25)。请参见下面的图片。

在Linux下,几分钟后(我不能给你更多的洞察力不幸的是...我仍然必须找出什么可能会改变)线程的数量开始不断增加每一秒。
请看这里图片,已经到达100多个,而且还在继续计数:

读取current logical threads increasing / thread stack is leaking i得到了确认,如果其他线程没有完成,CLR允许新线程,但当前从Windows迁移到Linux时,代码没有变化。
这是调用SystemD的HostBuilder

 public static IHostBuilder CreateWebHostBuilder(string[] args)
        {
            string curDir = MondayConfiguration.DefineCurrentDir();
            IConfigurationRoot config = new ConfigurationBuilder()
 
                // .SetBasePath(Directory.GetCurrentDirectory())
                .SetBasePath(curDir)
                .AddJsonFile("servicelocationoptions.json", optional: false, reloadOnChange: true)
#if DEBUG
                   .AddJsonFile("appSettings.Debug.json")
 
#else
                   .AddJsonFile("appSettings.json")
#endif
                   .Build();
            return Host.CreateDefaultBuilder(args)
                .UseContentRoot(curDir)
                .ConfigureAppConfiguration((_, configuration) =>
                {
                    configuration
                    .AddIniFile("appSettings.ini", optional: true, reloadOnChange: true)
#if DEBUG
                   .AddJsonFile("appSettings.Debug.json")
 
#else
                   .AddJsonFile("appSettings.json")
#endif
                    .AddJsonFile("servicelocationoptions.json", optional: false, reloadOnChange: true);
                })
 
                .UseSerilog((_, services, configuration) => configuration
                    .ReadFrom.Configuration(config, sectionName: "AppLog")// (context.Configuration)
                    .ReadFrom.Services(services)
                    .Enrich.FromLogContext()
                    .WriteTo.Console())
 
                // .UseSerilog(MondayConfiguration.Logger)
                .ConfigureServices((hostContext, services) =>
                {
                    services
                    .Configure<ServiceLocationOptions>(hostContext.Configuration.GetSection(key: nameof(ServiceLocationOptions)))
                    .Configure<HostOptions>(opts => opts.ShutdownTimeout = TimeSpan.FromSeconds(30));
                })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                    ServiceLocationOptions locationOptions = config.GetSection(nameof(ServiceLocationOptions)).Get<ServiceLocationOptions>();
                    string url = locationOptions.HttpBase + "*:" + locationOptions.Port;
                    webBuilder.UseUrls(url);
                })
                .UseSystemd();
        }

与此同时,我试图跟踪所有的Monitor.Enter(),我使用这些Monitor.Enter()来呈现串行API端点,这些端点触及服务和内部结构的状态,但在Windows中似乎一切正常。
我开始怀疑调用SystemD的问题。我想知道调用UseSystemD()真正涉及到什么,但周围没有这么多文档。我只是找到了Glenn Condron的[https://devblogs.microsoft.com/dotnet/net-core-and-systemd/](https://devblogs.microsoft.com/dotnet/net-core-and-systemd/)和MSDN上的一些快速说明。
编辑1:为了进一步调试,我创建了一个类来使用ClrMd扫描线程池。我的主服务有一个心跳(奇怪的是它被称为Ping),如下所示(不是添加到processTracker.Scan()):

private async Task Ping()
    {
        await _containerServer.SyslogQueue.Writer.WriteAsync((
            LogLevel.Information,
            $"Monday Service active at: {DateTime.UtcNow.ToLocalTime()}"));
        string processMessage = ProcessTracker.Scan();
        await _containerServer.SyslogQueue.Writer.WriteAsync((LogLevel.Information, processMessage));
        _logger.DebugInfo()
            .Information("Monday Service active at: {Now}", DateTime.UtcNow.ToLocalTime());
    }

其中processTrackes id的结构如下:

public static class ProcessTracker
    {
        static ProcessTracker()
        {
        }

        public static string Scan()
        {
            // see https://stackoverflow.com/questions/31633541/clrmd-throws-exception-when-creating-runtime/31745689#31745689
            StringBuilder sb = new();
            string answer = $"Active Threads{Environment.NewLine}";
            // Create the data target. This tells us the versions of CLR loaded in the target process.
            int countThread = 0;

            var pid = Process.GetCurrentProcess().Id;
            using (var dataTarget = DataTarget.AttachToProcess(pid, 5000, AttachFlag.Passive))
            {
                // Note I just take the first version of CLR in the process. You can loop over
                // every loaded CLR to handle the SxS case where both desktop CLR and .Net Core
                // are loaded in the process.
                ClrInfo version = dataTarget.ClrVersions[0];
                var runtime = version.CreateRuntime();
                // Walk each thread in the process.
                foreach (ClrThread thread in runtime.Threads)
                {
                    try
                    {
                        sb = new();
                        // The ClrRuntime.Threads will also report threads which have recently
                        // died, but their underlying data structures have not yet been cleaned
                        // up. This can potentially be useful in debugging (!threads displays
                        // this information with XXX displayed for their OS thread id). You
                        // cannot walk the stack of these threads though, so we skip them here.
                        if (!thread.IsAlive)
                            continue;

                        sb.Append($"Thread {thread.OSThreadId:X}:");
                        countThread++;
                        // Each thread tracks a "last thrown exception". This is the exception
                        // object which !threads prints. If that exception object is present, we
                        // will display some basic exception data here. Note that you can get
                        // the stack trace of the exception with ClrHeapException.StackTrace (we
                        // don't do that here).
                        ClrException? currException = thread.CurrentException;
                        if (currException is ClrException ex)
                            sb.AppendLine($"Exception: {ex.Address:X} ({ex.Type.Name}), HRESULT={ex.HResult:X}");

                        // Walk the stack of the thread and print output similar to !ClrStack.
                        sb.AppendLine(" ------>  Managed Call stack:");
                        var collection = thread.EnumerateStackTrace().ToList();
                        foreach (ClrStackFrame frame in collection)
                        {
                            // Note that CLRStackFrame currently only has three pieces of data:
                            // stack pointer, instruction pointer, and frame name (which comes
                            // from ToString). Future versions of this API will allow you to get
                            // the type/function/module of the method (instead of just the
                            // name). This is not yet implemented.
                            sb.AppendLine($"           {frame}");
                        }
                    }
                    catch
                    {
                        //skip to the next
                    }
                    finally
                    {
                        answer += sb.ToString();
                    }
                }
            }
            answer += $"{Environment.NewLine} Total thread listed: {countThread}";
            return answer;
        }
    }

一切都很好,在Windows中,它在某种树文本视图中打印了很多不错的信息。
关键是它在某个地方需要Kernel32.dll,而在linux中是不可用的。有人能给点提示吗?服务是在没有.NET基础设施的情况下本地发布的,在发布模式下,arch linux 64,单个文件。
非常感谢Alex

lstz6jyr

lstz6jyr1#

我发现了一种方法,可以跳过我需要从一个简单的调试会话的整个日志记录。我不知道我也可以远程连接到一个Systemd进程。只是按照https://learn.microsoft.com/en-us/visualstudio/debugger/remote-debugging-dotnet-core-linux-with-ssh?view=vs-2022的快速分步指南。唯一的先决条件是让服务处于调试模式,并在主机上安装NET运行时,但这真的是全部。抱歉,我以前不知道这一点。
亚历克斯

相关问题