Skip to content

syscall: misleading documentation for linux SysProcAttr.Pdeathsig #27505

Open
@virtuald

Description

Currently, the documentation says:

// Signal that the process will get when its parent dies (Linux only)

However, according to the prctl man page:

Warning: the "parent" in this case is considered to be the thread that created this process. In other words, the signal will be sent when that thread terminates (via, for example, pthread_exit(3)), rather than after all of the threads in the parent process terminate.

I got bit by this in a python program -- started a new program on one thread, and tried to wait for it on another thread, and the child process kept dying and it took awhile to figure out what was going on. While I haven't ran into it in go yet, because in go threads and goroutines aren't one to one, I imagine if one ran into this sort of bug it would only occur intermittently.

Thinking about it, it seems like a user might want to call runtime.LockOSThread when using this?

It's not clear to me whether the docs should have a larger warning in them -- but I think at the minimum the documentation should be updated to say 'parent thread' or 'parent goroutine' instead of just 'parent'.

Activity

dominikh

dominikh commented on Sep 5, 2018

@dominikh
Member

If I am not mistaken, the current implementation of Go never kills threads, which is why you wouldn't be able to run into this bug in Go currently. At the same time, if Go ever does start killing threads, this might hit a lot of people at once.

virtuald

virtuald commented on Sep 6, 2018

@virtuald
Author

Looked around a bit, looks like go 1.10+ will kill a thread if you lock it and the goroutine exits without unlocking it: #20395

Here's an example that shows that:

package main

import (
	"flag"
	"fmt"
	"os/exec"
	"runtime"
	"syscall"
)

func main() {
	kill := false
	flag.BoolVar(&kill, "death", false, "Set deathsig")
	flag.Parse()

	cmd := exec.Command("sleep", "1000")
	if kill == true {
		fmt.Println("Death is coming")
		cmd.SysProcAttr = &syscall.SysProcAttr{
			Setpgid:   true,
			Pdeathsig: syscall.SIGKILL,
		}
	}

	// force other goroutines to be spawned on a new thread
	runtime.LockOSThread()

	done := make(chan struct{})

	go func() {
		runtime.LockOSThread()
		err := cmd.Start()
		if err != nil {
			fmt.Println(err)
		}
		close(done)
		fmt.Println("Exit goroutine")
	}()

	<-done

	fmt.Println("Waiting")
	err := cmd.Wait()
	fmt.Println("Done", err)
}

In that example, if -death is passed as an argument the program will immediately exit. Obviously, you have to go out of your way to trigger this behavior, but presumably someone who is messing with platform-specific behavior is going to want to know about platform-specific behaviors... thus why the docs should at least hint that this is possible.

added
NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.
on Sep 6, 2018
changed the title Update documentation for linux SysProcAttr.Pdeathsig syscall: misleading documentation for linux SysProcAttr.Pdeathsig on Sep 6, 2018
virtuald

virtuald commented on Oct 22, 2020

@virtuald
Author

Circling back to this, I did eventually figure out the right way to use Pdeathsig, and I think the documentation should include some examples or discussion about this.

Here's an example of what people usually want to do when using Pdeathsig -- ensure the child dies when the parent dies. This example ensures that golang won't accidentally kill the OS thread associated with the child process.

package main

import (
	"fmt"
	"os/exec"
	"runtime"
	"syscall"
)

func main() {

	done := make(chan struct{})

	cmd := exec.Command("sleep", "1000")
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setpgid:   true,
		Pdeathsig: syscall.SIGKILL,
	}

	go func() {
		// On Linux, pdeathsig will kill the child process when the thread dies,
		// not when the process dies. runtime.LockOSThread ensures that as long
		// as this function is executing that OS thread will still be around
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()

		err := cmd.Start()
		if err != nil {
			fmt.Println(err)
		}

		fmt.Println("Child is PID", cmd.Process.Pid)
		err = cmd.Wait()
		close(done)
		fmt.Println("Child exited:", err)
	}()

	fmt.Println("Waiting for child to exit")
	<-done
	fmt.Println("Parent exiting")
}
added this to the Backlog milestone on Oct 22, 2020
gopherbot

gopherbot commented on Jun 14, 2022

@gopherbot
Contributor

Change https://go.dev/cl/412114 mentions this issue: syscall: clarify Pdeathsig documentation on Linux

17 remaining items

added a commit that references this issue on Apr 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    DocumentationIssues describing a change to documentation.NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.compiler/runtimeIssues related to the Go compiler and/or runtime.help wanted

    Type

    No type

    Projects

    • Status

      Triage Backlog

    Relationships

    None yet

    Development

    No branches or pull requests

      Participants

      @dominikh@Tasssadar@cespare@virtuald@ianlancetaylor

      Issue actions

        syscall: misleading documentation for linux SysProcAttr.Pdeathsig · Issue #27505 · golang/go