My Go application works fine in interactive mode, but it fails to start correctly when run as a Windows service

Hello everybody,

I have a Go application that I want to run as a Windows service. When I execute the application directly (interactive mode), everything works as expected, including logging to a file. However, when I try to run it as a Windows service, it fails to start, and I get the following error:

StartService FAILED 1053: The service did not respond to the start or control request in a timely fashion.

What I’ve Tried

  1. Logging to a File:
  • I attempted to initialize logging to a file in the directory where the executable is located using the os.OpenFile method. This works fine in interactive mode but does not create or write to the log file when run as a service.
  1. Changing File Path for Logging:
  • I tried changing the log file path to a simpler location (like d:\Logs), but it still did not resolve the issue when running as a service.
  1. Checking Permissions:
  • I ensured that the service runs with administrator privileges. I also verified that the directory where the log file is supposed to be written exists and has the appropriate write permissions.
  1. Using sc start for Debugging:
  • I tried using sc start som.kalenderview debug to start the service in debug mode, but I encountered the same 1053 error.

The error occurs instant after starting the service.

  • The service is configured correctly with sc create and runs under an administrator account.
  • I am using the golang.org/x/sys/windows/svc package to manage the Windows service.
  • The service seems to start successfully (as indicated by the service control manager) but immediately fails with error 1053, suggesting it does not respond within the timeout period.

package main

import (
	"encoding/xml"
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"

	"golang.org/x/sys/windows/svc"
	"golang.org/x/sys/windows/svc/eventlog"

	cfg "myapp/config"
	out "myapp/output"
	rq "myapp/requestdata"
)

var (
	staticDir    = getAbsDirPath() + "/static/"
	templatesDir = staticDir + "/templates"
	templates    = template.Must(template.ParseFiles(templatesDir + "/index.html"))
	version      string
)

var config cfg.Config

type myService struct{}

func (m *myService) Execute(args []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (bool, uint32) {
	const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
	s <- svc.Status{State: svc.StartPending}

	elog, err := eventlog.Open("myapp")
	if err != nil {
		log.Fatalf("Failed to open event log: %v", err)
	}
	defer elog.Close()

	// Sende sofort die Running-Meldung, um den SCM zu informieren
	s <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
	elog.Info(1, "myapp-service started.")

	// Starte den HTTP-Server in einer separaten Goroutine, um Blockierungen zu vermeiden
	go func() {
		err := startHTTPServer(elog)
		if err != nil {
			elog.Error(1, fmt.Sprintf("Failed to start HTTP server: %v", err))
		}
	}()

	// Warte auf Stop- oder Shutdown-Befehle
loop:
	for {
		select {
		case c := <-r:
			switch c.Cmd {
			case svc.Stop, svc.Shutdown:
				elog.Info(1, "myapp-service is stopping.")
				break loop
			default:
				elog.Error(1, "Unexpected control request.")
			}
		}
	}

	s <- svc.Status{State: svc.StopPending}
	return false, 0
}

func startHTTPServer(elog *eventlog.Log) error {
	if elog != nil {
		elog.Info(1, "Starting HTTP server...")
	} else {
		log.Println("Starting HTTP server...")
	}

	http.HandleFunc("/", indexHandler)
	http.HandleFunc("/scripts/", staticHandler)
	http.HandleFunc("/css/", staticHandler)
	http.HandleFunc("/images/", staticHandler)

	err := http.ListenAndServe(":"+config.LocalPort, nil)
	if err != nil && elog != nil {
		elog.Error(1, fmt.Sprintf("Failed to start HTTP server: %v", err))
	}
	return err
}

func init() {
	// Ermittelt den Pfad zur ausführbaren Datei
	exePath, err := os.Executable()
	if err != nil {
		log.Fatalf("Failed to get executable path: %v", err)
	}

	// Bestimmt das Verzeichnis der ausführbaren Datei
	exeDir := filepath.Dir(exePath)

	// Logdateipfad in demselben Verzeichnis wie die ausführbare Datei
	logFilePath := filepath.Join(exeDir, "somkalenderview_debug.log")

	// Öffne die Logdatei und leite die Ausgaben dorthin um
	f, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Fatalf("Failed to open log file: %v", err)
	}

	log.SetOutput(f) // Setze die Log-Ausgabe auf die Datei
	log.Println("Logging initialized successfully in", logFilePath)
}


func main() {
	isInteractive, err := svc.IsAnInteractiveSession()
	if err != nil {
		log.Fatalf("failed to determine if we are running in an interactive session: %v", err)
	}

	if isInteractive {
		runInteractive()
	} else {
		runService()
	}

	var vFlag bool
	flag.BoolVar(&vFlag, "v", false, "show version")
	flag.Parse()

	if vFlag {
		println(version)
	}

	var weiter bool = true

	file, err := os.Open("config.xml")
	if err != nil {
		fmt.Println("Fehler beim Öffnen der XML-Datei:", err)
		weiter = false
	}
	defer file.Close()

	// Konfigurationsdaten aus der XML-Datei lesen
	if weiter {
		if err := xml.NewDecoder(file).Decode(&config); err != nil {
			fmt.Println("Fehler beim Lesen der XML-Datei:", err)
			weiter = false
		}
	}
}

func runService() {
	run := svc.Run
	err := run("myapp", &myService{})
	if err != nil {
		log.Fatalf("failed to run service: %v", err)
	}
}

func runInteractive() {
	log.Println("Running in interactive mode...")
	go startHTTPServer(nil) // Starte den HTTP-Server auch im interaktiven Modus
	for {
		time.Sleep(10 * time.Second)
	}
}

My service does not run even when I remove the HTTP server code. The service fails to start with error 1053 (“The service did not respond to the start or control request in a timely fashion”). I have tried running the service without any blocking operations (like the HTTP server), but it still fails with the same error.

The application runs perfectly fine in interactive mode (from the command line), including logging to a file, but it fails to start correctly when running as a Windows service.

What could be causing the service to fail even when the HTTP server code is removed?

Best regards Jens

Error 1053 usually means that a Windows service fails to start within a specified amount of time. Try this:

  1. Open the Services console. It can be opened by pressing the “Windows key + R” key combination, typing “services.msc” and pressing the “Enter” key.
  2. Find the service where the error occurred, right-click on it and select Properties.
  3. In the Properties window, click the Login tab.
  4. Make sure the “This Account” option is selected and enter the username and password for the account with administrator privileges.
  5. Click the Apply button, and then click the OK button.
  6. Restart your computer and check if the service has started successfully.

If the above steps can’t resolve the issue, try the following steps:

  1. Open the Services console.
  2. Find the service where the error occurred, right-click on it and select Properties.
  3. In the Properties window, click the Dependencies tab.
  4. Make sure that all dependent services are started.
  5. Restart your computer and check if the service has started successfully.

If the problem still persists, try uninstalling and reinstalling the service.