Skip to content

No way to cleanly self-terminate / exit the program #411

Open
@johnmaguire

Description

@johnmaguire

I have a program which, in some cases, runs under Windows Services / Launchd / Systemd, and in other cases, is launched by a supervisor program I control. When using the supervisor program, I need a way to signal to the "service" when I want it to quit. On Linux, I can use signals, but on Windows, this is not supported, so I am using an RPC call. The problem is that I can't find a way to actually get the program to terminate besides calling os.Exit() which will not necessarily clean up all resources correctly.

I found this example which looked like you could manually call Stop from within the process: https://github.com/kardianos/service/blob/master/example/runner/runner.go#L60-L66

But when I actually tried this, the program does not exit. PoC below:

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/kardianos/service"
)

var logger service.Logger

type program struct{}

func (p *program) Start(s service.Service) error {
	fmt.Println("Service starting...")
	go p.run(s)
	return nil
}

func (p *program) run(s service.Service) {
	fmt.Println("Running work function...")
	fmt.Println("Work completed. This function has returned.")
	// Note: Even though this function returns, the program won't exit when run interactively
	p.Stop(s)
}

func (p *program) Stop(s service.Service) error {
	fmt.Println("Service stopping...")
	// Stop should not block. Return within a few seconds.
	return nil
}

func main() {
	svcConfig := &service.Config{
		Name:        "GoServiceExampleSimple",
		DisplayName: "Go Service Example",
		Description: "This is an example Go service.",
	}

	prg := &program{}
	s, err := service.New(prg, svcConfig)
	if err != nil {
		log.Fatal(err)
	}

	logger, err = s.Logger(nil)
	if err != nil {
		log.Fatal(err)
	}

	// Check if running interactively using the proper API
	isInteractive := service.Interactive()

	if isInteractive {
		fmt.Println("Running in interactive mode")
		fmt.Println("Notice: This program will NOT exit automatically after work completes")
		fmt.Println("Press Ctrl+C to exit manually")
	}

	// Add a timestamp to show when execution starts
	fmt.Printf("Program started at: %s\n", time.Now().Format(time.RFC3339))

	err = s.Run()
	if err != nil {
		logger.Error(err)
	}

	// This line will never be reached when running interactively
	fmt.Println("Program exited normally")
}

I believe it's due to waiting on this signal, which I can't trigger or access the channel of to manually close:

service/service_windows.go

Lines 397 to 401 in becf2eb

sigChan := make(chan os.Signal)
signal.Notify(sigChan, os.Interrupt)
<-sigChan

Or on macOS:

service/service_darwin.go

Lines 274 to 276 in becf2eb

var sigChan = make(chan os.Signal, 3)
signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt)
<-sigChan

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions