У меня есть служба, которая работает в Linux под SystemD, но компилируется и отлаживается в VS22 под Windows. Служба в основном является прокси-сервером для базы данных MariaDB10 в форме BackgroundWorker, обслуживающей клиентов через SignalR. Если я запускаю его в режиме восстановления в Windows, количество логических потоков остается в разумных пределах (приблизительно 20-25). См. рисунок ниже.
В Linux через несколько минут (к сожалению, я не могу дать вам больше информации... мне все еще нужно выяснить, что может измениться) количество потоков начинает постоянно увеличиваться каждую секунду.
см. рисунок здесь, прибывший уже к более чем 100 и все еще подсчитывающий:
Чтение увеличение количества текущих логических потоков / утечка стека потоков я получил подтверждение того, что CLR разрешает новые потоки, если другие не завершаются, но в настоящее время в коде нет никаких изменений при переходе с Windows на Linux.
Это HostBuilder с вызовом SystemD
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()
, которые я использую для рендеринга последовательных конечных точек API, которые касаются состояния службы и внутренних структур, но в Windows, похоже, все в порядке.
Я начинаю задаваться вопросом, не связана ли проблема с вызовом SystemD. Я хотел бы знать, что на самом деле связано с вызовом UseSystemD()
, но документации не так много.
Я только что нашел [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 построен следующим образом:
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, а в линуксе этого нет. Кто-нибудь может подсказать по этому поводу? Сервис изначально публикуется без инфраструктуры .NET, в режиме релиза, в Arch Linux64, в одном файле.
большое спасибо Алекс
Я нашел способ пропустить всю регистрацию того, что мне нужно, из простого сеанса отладки. Я не знал, что могу удаленно подключиться к процессу Systemd. Просто следуйте https://learn.microsoft.com/en-us/visualstudio/debugger/remote-debugging-dotnet-core-linux-with-ssh?view=vs-2022 для быстрого пошагового руководства. Единственным предварительным условием является то, чтобы служба находилась в режиме отладки и на хосте была установлена среда выполнения NET, но это действительно все. Извините, что не знал этого раньше.
Алекс