0

I am trying to start my own C# service as a Windows service. I installed my service using sc.exe create. I can see and modify the Service in the Services Application, but when I try to start, I am always getting the Error 1053. In my Program I am starting a new Thread which runs an infinite while loop in Execute.

static void Main(string[] args) {
    Console.WriteLine("Starting Thread");

    mainThread = new Thread(new ThreadStart(Execute));
    mainThread.Start();

    Console.WriteLine("Thread Started!");
}

public static void Execute() {
    //connect to Database

    while(true) { //while connection open
        ...
    }
}

When I run my Program manually, in a Console, Powershell and in Visual Studio, the Program runs as expected. When I try to run it via Services I get the error. I'm also getting the error when I'm running a empty Main (Main only with Console.WriteLine). I googled really much and tried extending the Timeout in Registry(ServicesPipeTimeout), Using Threads, Installing Core Framework, Reinstalling the service and being owner of the service.

EDIT: I changed everything to run as a Hosted Service. In my Main i have

await new HostBuilder()
            .ConfigureServices((hostContext, services) => { services.AddHostedService<Worker>(); })
            .Build()
            .RunAsync();

But i Still can't run it as a Windows Service, I'm getting the same Error.

I am out of Ideas, any help is highly appreciated.

Regards

lukas_702
  • 13
  • 4
  • In .NET 6.0, you should rather use `BackgroundService` to run it as windows service, refer https://learn.microsoft.com/en-us/dotnet/core/extensions/windows-service – Anand Sowmithiran Jan 11 '23 at 12:12

2 Answers2

0

What you wrote is a standard Console app. A service shall expose Start and Stop facilities to be correctly interpreted by Windows (or by SystemD equivalently).

Your core loop of the main should be hosted on a IHostedService, or a Worker service. Give a look at [here][1] to figure out what you have to do.

I give you here below a plausible Program.cs file that I wrote some time ago for a Linux service (there is actually no difference with Windows services, just remove the .UseSystemD() call).

/// <summary>
///     Remote service App for Monday Parser and Database manager.
/// </summary>
public static class Program
{
    private static readonly ILogger logger = new LoggerConfiguration()
            .ReadFrom.Configuration(MondayConfiguration.Configuration, sectionName: "AppLog")
        .CreateLogger().ForContext("Origin", "MondayService");

    /// <summary>
    ///     Helper to create hosting environment.
    /// </summary>
    /// <param name="args">
    ///     command line arguments if any- no management is occurring now.
    /// </param>
    /// <returns>
    /// </returns>
    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();
    }

Here below you find the main implementation of the service, I added a lot of XML to show you all the exception you may get when moving things around in the service and which methods should be implmented as a minimum to have the service to work.

If you remove all the variables that you don't understand, you will remain with a working skeleton of a windows service running simply a ping service. Try it.

/// <summary>
///     Main Worker class for managing the service inside SystemD.
/// </summary>
public partial class MondayService : BackgroundService
{
    /// <summary>
    ///     Initializes a new instance of the <see cref="MondayService"/> class. Std ctr.
    /// </summary>
    /// <param name="mondayhub">
    /// </param>
    /// <param name="containerServer">
    /// </param>
    public MondayService(IHubContext<MondayHub, IMondayServiceHub> mondayhub,
       IFeatureContainer containerServer)
    {
        _containerServer = containerServer;
        _dbManager = _containerServer.DbManager;
        _parser = _containerServer.Parser;
        _syslogQueue = _containerServer.SyslogQueue;
        _segmentManager = _containerServer.SegmentManager;
        _orderManager = _containerServer.OrderManager;

        while (!MondayConfiguration.SerilogFactoryReady)
        {
            Thread.Sleep(20);
        }

        // _logger = MondayConfiguration.LoggerFactory.CreateLogger("");
        _logger = new LoggerConfiguration().ReadFrom.Configuration(MondayConfiguration.Configuration, sectionName: "AppLog").CreateLogger().ForContext("Origin", "MondayService");
        _mondayHub = mondayhub;
    }

    /// <summary>
    ///     Setup activities for the Monday service.
    /// </summary>
    /// <param name="cancellationToken">
    /// </param>
    /// <returns>
    /// </returns>
    /// <exception cref="OverflowException">
    ///     <paramref><name>value</name></paramref> is less than <see cref="TimeSpan.MinValue"/>
    ///     or greater than <see cref="TimeSpan.MaxValue"/>.
    ///     -or- value is <see><cref>System.Double.PositiveInfinity</cref></see> .
    ///     -or- value is <see cref="double.NegativeInfinity"/>.
    /// </exception>
    /// <exception cref="AggregateException">
    ///     The task was canceled. The <see><cref>InnerExceptions</cref></see> collection
    ///     contains a <see cref="TaskCanceledException"/> object.
    ///     -or- An exception was thrown during the execution of the task. The
    ///      <see><cref>InnerExceptions</cref></see> collection contains information about the
    ///     exception or exceptions.
    /// </exception>
    /// <exception cref="TaskCanceledException">
    ///     The task has been canceled.
    /// </exception>
    /// <exception cref="ArgumentNullException">
    ///     The <paramref><name>function</name></paramref> parameter was <see langword="null"/>.
    /// </exception>
    /// <exception cref="ObjectDisposedException">
    ///     The <see cref="CancellationTokenSource"/> associated with <paramref
    ///     name="cancellationToken"/> was disposed.
    /// </exception>
    /// <exception cref="IOException">
    ///     destFileName already exists and overwrite is <see langword="false"/>.
    ///     -or- An I/O error has occurred, e.g. while copying the file across disk volumes.
    /// </exception>
    /// <exception cref="UnauthorizedAccessException">
    ///     The caller does not have the required permission.
    /// </exception>
    /// <exception cref="SecurityException">
    ///     The caller does not have the required permission.
    /// </exception>
    /// <exception cref="FileNotFoundException">
    ///     <paramref><name>sourceFileName</name></paramref> was not found.
    /// </exception>
    public override Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.DebugInfo().Information("MondayService: Starting StartAsync");
        var parserRenewed = false;
        if (_parser is null)
        {
            _parser = new();
            _parser.OnParserSocketDataArrived +=
                (sender, e) => _containerServer.BroadcastParserSocketDataViaSignalR(sender, e);
            parserRenewed = true;
            Task.Run(ParserSubscriptions, cancellationToken);
            _logger.DebugInfo().Information("MondayService: Instantiating again the parser inside StartAsync");
        }

        if (_dbManager is null || parserRenewed)
        {
            _dbManager = new(_parser);
            _logger.DebugInfo().Information("MondayService: Instantiating again the db manager inside StartAsync");
            _dbManager.ConnectToParserSocket();
            _dbManager.OnFilteredSocketDataArrived +=
                (sender, e) => _containerServer.BroadcastFilteredSocketDataViaSignalR(sender, e);
            if (!_tagsDataSavedOnce)
            {
                // ReSharper disable once ExceptionNotDocumented
                Tags.SaveAll();
                _tagsDataSavedOnce = true;
            }
        }

        if (_segmentManager is null)
        {
            _segmentManager = new(_parser, _dbManager);
            _segmentManager.ConnectToParserSocket(_parser);
            _segmentManager.OnSegmentClosure += (sender, e) => _containerServer.BroadcastSegmentDataViaSignalR(sender, e);
            _logger.DebugInfo().Information("MondayService: Instantiating again the segment manager inside StartAsync");
        }

        if (_orderManager is null)
        {
            _orderManager = new(_parser);
            _orderManager.OnOrderManagerEvent +=
                (sender, e) => _containerServer.BroadcastOrderManagerEventsViaSignalR(sender, e);
            _logger.DebugInfo().Information("MondayService: Instantiating again the order manager inside StartAsync");
        }

        _logger.DebugInfo().Information("MondayService: Completing StartAsync");
        return base.StartAsync(cancellationToken);
    }

    /// <summary>
    ///     Graceful shutdown and disposal of Monday service (parser and database manager comprised).
    /// </summary>
    /// <param name="cancellationToken">
    /// </param>
    /// <returns>
    /// </returns>
    /// <exception cref="IOException">
    ///     destFileName already exists and overwrite is <see langword="false"/>.
    ///     -or- An I/O error has occurred, e.g. while copying the file across disk volumes.
    /// </exception>
    /// <exception cref="UnauthorizedAccessException">
    ///     The caller does not have the required permission.
    /// </exception>
    /// <exception cref="AggregateException">
    ///     The task was canceled. The <see><cref>InnerExceptions</cref></see> collection
    ///     contains a <see cref="TaskCanceledException"/> object.
    ///     -or- An exception was thrown during the execution of the task. The
    ///      <see><cref>InnerExceptions</cref></see> collection contains information about the
    ///     exception or exceptions.
    /// </exception>
    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.DebugInfo().Information("MondayService: Starting StopAsync");
        if (!_tagsDataSavedOnce)
        {
            Tags.SaveAll();
            _tagsDataSavedOnce = true;
        }

        _logger.DebugInfo().Information("Stopping Monday Service hosted on Linux.");
        if (_parser is not null) await _parser.UnsubscribeAllAsync();
        foreach (string ex in Tags.Exchanges.ExchangeNames.ToList())
        {
            _parser?.DeactivateRest(ex);
        }

        _parser?.Dispose();
        _dbManager?.Dispose();
        _orderManager?.Dispose();
        _segmentManager?.Dispose();
        _logger.DebugInfo().Information("MondayService: Completing StopAsync");
        await base.StopAsync(cancellationToken);
    }

    /// <summary>
    ///     Core loop of the service. Here all the assets are instantiated and managed up to
    ///     final disposal. This instantiates the SignalR service and manages it.
    /// </summary>
    /// <param name="stoppingToken">
    /// </param>
    /// <returns>
    /// </returns>
    /// <exception cref="TaskCanceledException">
    ///     The task has been canceled.
    /// </exception>
    /// <exception cref="ArgumentOutOfRangeException">
    ///     <paramref><name>delay</name></paramref> represents a negative time interval other
    ///     than <see langword="TimeSpan.FromMilliseconds(-1)"/>.
    ///     -or- The <paramref><name>delay</name></paramref> argument's <see
    ///      cref="P:System.TimeSpan.TotalMilliseconds"/> property is greater than <see cref="int.MaxValue"/>.
    /// </exception>
    /// <exception cref="ObjectDisposedException">
    ///     The provided <paramref><name>cancellationToken</name></paramref> has already been disposed.
    /// </exception>
    /// <exception cref="OverflowException">
    ///     <paramref><name>value</name></paramref> is less than <see cref="TimeSpan.MinValue"/>
    ///     or greater than <see cref="TimeSpan.MaxValue"/>.
    ///     -or- <paramref><name>value</name></paramref> is <see cref="double.PositiveInfinity"/>.
    ///     -or- <paramref><name>value</name></paramref> is <see cref="double.NegativeInfinity"/>.
    /// </exception>
    /// <exception cref="IOException">
    ///     destFileName already exists and overwrite is <see langword="false"/>.
    ///     -or- An I/O error has occurred, e.g. while copying the file across disk volumes.
    /// </exception>
    /// <exception cref="UnauthorizedAccessException">
    ///     The caller does not have the required permission.
    /// </exception>
    /// <exception cref="SecurityException">
    ///     The caller does not have the required permission.
    /// </exception>
    /// <exception cref="FileNotFoundException">
    ///     <paramref><name>sourceFileName</name></paramref> was not found.
    /// </exception>
    /// <exception cref="DirectoryNotFoundException">
    ///     The specified path is invalid (for example, it is on an unmapped drive).
    /// </exception>
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.DebugInfo().Information("Monday Service starting at {Now}", DateTimeOffset.Now);
        while (!stoppingToken.IsCancellationRequested)
        {
            Task tlog = FetchLogAsync(stoppingToken);
            Task? tping = null;
            int seconds = DateTimeOffset.Now.Second;
            int minutes = DateTimeOffset.Now.Minute;

            // logging a ping every 5 minutes
            if (seconds < 5 && minutes % 1 == 0)
            {
                tping = Ping();
                if (!_tagsDataSavedOnce)
                {
                    Tags.SaveAll();
                    _tagsDataSavedOnce = true;
                }
            }

            // looping every 5 seconds
            var tLoop = Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
            if (tping is null)
            {
                await Task.WhenAll(tlog, tLoop);
            }
            else
            {
                await Task.WhenAll(tlog, tLoop, tping);
            }
        }

        _logger.DebugInfo().Information("Monday Service stopping at {Now}", DateTimeOffset.Now);
    }

    [MustUseReturnValue]
    private Task FetchLogAsync(CancellationToken stoppingToken)
    {
        var ok = true;
        while (ok)
        {
            try
            {
                ok = _containerServer.SyslogQueue.Reader.TryRead(
                    out (LogLevel lvl, string line) item);
                if (ok)
                {
                    switch (item.lvl)
                    {
                        case LogLevel.Trace:

                        case LogLevel.Debug:
                            _logger.DebugInfo().Debug("{FetchedMessage}", item.line);
                            break;

                        case LogLevel.Information:
                            _logger.DebugInfo().Information("{FetchedMessage}", item.line);
                            break;

                        case LogLevel.Warning:
                            _logger.DebugInfo().Warning("{FetchedMessage}", item.line);
                            break;

                        case LogLevel.Error:
                            _logger.DebugInfo().Error("{FetchedMessage}", item.line);
                            break;

                        case LogLevel.Critical:
                            _logger.Fatal("{FetchedMessage}", item.line);
                            break;

                        case LogLevel.None:
                            break;
                    }
                }

                if (stoppingToken.IsCancellationRequested)
                {
                    ok = false;
                }
            }
            catch
            {
                ok = false;
            }
        }

        return Task.CompletedTask;
    }

    private Task<CallResult<UpdateSubscription>> ParserSubscriptions()
    {
        Guard.Against.Null(nameof(_parser));
        return _parser!.SubscribeFromSettingsAsync();
    }

    private async Task Ping()
    {
        await _containerServer.SyslogQueue.Writer.WriteAsync((
            LogLevel.Information,
            $"Monday Service active at: {DateTime.UtcNow.ToLocalTime()}"));
    }

    /// <summary>
    ///     This is a debug utility to check whether the service creates too many ThreaPpool threads.
    /// </summary>
    public static class ProcessTracker
    {
        static ProcessTracker()
        {
        }

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

            var pid = Environment.ProcessId;
            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 ").AppendFormat("{0:X}", thread.OSThreadId).Append(':');
                        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.Append("Exception: ")
                                                            .AppendFormat("{0:X}", ex.Address)
                                                            .Append(" (").Append(ex.Type.Name)
                                                            .Append("), HRESULT=")
                                                            .AppendFormat("{0:X}", ex.HResult)
                                                            .AppendLine();
                        }

                        // 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.Append("           ").Append(frame).AppendLine();
                        }
                    }
                    catch
                    {
                        //skip to the next
                    }
                    finally
                    {
                        answer.Append(sb);
                    }
                }
            }
            answer.Append(Environment.NewLine).Append(" Total thread listed: ").Append(countThread);
            return answer.ToString();
        }
    }
}

The main core loop is pinging a queue for logging. It is just a way to let something run over the service permanently while other classes do their work (in this case they are almost all based on EAP).

Definitely, look at MSDN at first. It seems that you are missing the basics. [1]: https://learn.microsoft.com/en-us/dotnet/core/extensions/workers

  • Thank you, I changed my Program to run as A Hosted Service. I changed the code in my Main to "await new HostBuilder().ConfigureServices((hostContext, services) => { services.AddHostedService(); }).Build().RunAsync();". Running it from Console or VS works, but I still can't get it to Work as a Windows Service. I am still getting the same error. – lukas_702 Jan 12 '23 at 15:20
  • If the answer has given you enough content to get on the right path, please mark as anwered, What you have now it is likely a new issue. Just copy and paste the skeleton of the code I have given you and understand it while you cut&paste. This a good way to learn while coding if you keep the F1 button pressed almost everywhere! Ciao – AlessandroParma Jan 12 '23 at 18:33
0

I ended up writing it as a Windows Service. I simply inherited ServiceBase and wrote Start and Stop methods. The start method starts a Timer which calls a Method with the infinite loop every minute. See: Error 1053: Service did not respond in time

lukas_702
  • 13
  • 4