skip to Main Content

Using Go lang with Linux (ubuntu).

A service (running as deamon, can be found with systemctl but not ps for example) runs a command (see code) that runs an executable (not sure if worth mentioning but – they are actually the same executable in sense of code).

The services are managed with the github.com/kardianos/service package.

As part of the code that the child service runs – i need to stop the parent service (service.control("stop")). At this oment the child process fails as well, instead of proceeding with the rest of the flow.

I tried:

  1. set session id (Setsid=true)

  2. set group id (Setpgid-true)

  3. call Release() on the underlying process after calling Start()

  4. combinations of the above (as suggested here https://groups.google.com/g/golang-nuts/c/Jx-ZsdQIMJA)

Not sure what other options i have and would love your advice.

code of the function that inits the process (note: this is a mixture of what i tried. i also tried them separately etc):

func ExecuteCommandAsync(command, args string, detach bool) error {
    logger.Notify("Running cli command:", command, args)

    cmd := exec.Command(command, args)
    if detach {
        logger.Notify("Detaching from parent process")
        cmd.SysProcAttr = &syscall.SysProcAttr{
            Setsid: true, // new session id prevents sigkill to parent kill the child
            //Setpgid: true, // new group id prevents sigkill to parent kill the child
        }
    }

    err := cmd.Start()
    if err != nil {
        logger.Error("Failed running cli command:", command, err)
        return err
    }

    if detach {
        logger.Notify("Detached from parent process - releasing child process")
        err = cmd.Process.Release()
        if err != nil {
            return err
        }
    } else {
        go func() {
            logger.Notify("Waiting for cli command to finish")
            waitErr := cmd.Wait()
            if waitErr != nil {
                logger.Error("Error waiting for cli command to finish:", waitErr)
            } else {
                logger.Notify("Command finished successfully")
            }
        }()
    }

    return nil
}

code of the function that stops the service:

func systemStopService(s service.Service) error {
    if s == nil {
        return errors.New("stopService - service is nil")
    }

    logger.Notify(s.String() + " Stopping service")
    err := service.Control(s, "stop")
    if err == nil {
        logger.Notify(s.String() + " Service stopped")
    } else if strings.Contains(err.Error(), "service does not exist") ||
        strings.Contains(err.Error(), "service has not been started") {
        logger.Notify(s.String() + err.Error())
        err = nil // not an error
    } else {
        logger.Error(s.String()+" Failed to stop service", err)
    }
    return err
}

2

Answers


  1. Chosen as BEST ANSWER

    The issue was that other than relating to the parent and being a part of its group process id - it is also maintained by the control group (cgroup) and signals are sent based on that. when the SIGHUP fails (if we handled it) - the mellow SIGKILL is activated and we have no escape.

    I had 2 solutions:

    1 - I could try to change the cgroup as part of my code (there are a few libs that support that)

    2 - find someone to run the child process instead of the parent - so they are not related any more. this was achieved using the cmd := exec.Command("systemd-run", args...) (see: https://www.freedesktop.org/software/systemd/man/latest/systemd-run.html).

    I chose the latter - as it looked cleaner - and it worked fine (other than that not all linux OSs use systemd, so they need to be tailored)


  2. The child process receives a SIGHUP signal when the process-leader process exits. If that signal is neither ignored, not handles, by default the child exist.

    You can also evade the SIGHUP by starting a new session for the daemon.
    In C you might call setsid (man 2 setsid).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search