1

PID File Management

 1 year ago
source link: https://bbengfort.github.io/2017/07/pid-management/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

PID File Management

July 11, 2017 · 3 min · Benjamin Bengfort

In this discussion, I want to propose some code to perform PID file management in a Go program. When a program is backgrounded or daemonized we need some way to communicate with it in order to stop it. All active processes are assigned a unique process id by the operating system and that ID can be used to send signals to the program. Therefore a PID file:

The pid files contains the process id (a number) of a given program. For example, Apache HTTPD may write it’s main process number to a pid file - which is a regular text file, nothing more than that - and later use the information there contained to stop itself. You can also use that information (just do a cat filename.pid) to kill the process yourself, using echo filename.pid | xargs kill.

Rafael Steil

From a Go program we can use the PID to get access to the program and send a signal, such as SIGTERM - terminate the program!

import (
	"os"
	"syscall"

	"github.com/bbengfort/x/pid"
	"github.com/urfave/cli"
)

// Send a kill signal to the process defined by the PID
func stop(c *cli.Context) error {
	pid := pid.New()
	if err := pid.Load(); err != nil {
		return cli.NewExitError(err.Error(), 1)
	}

	// Get the process from the os
	proc, err := os.FindProcess(pid.PID)
	if err != nil {
		return cli.NewExitError(err.Error(), 1)
	}

	// Kill the process
	if err := proc.Signal(syscall.SIGTERM); err != nil {
		return cli.NewExitError(err.Error(), 1)
	}

	return nil
}

Using the PID file within a program requires a bit of forethought. Where do you store the PID file? Do you only allow one running instance of the program? If so, the program needs to throw an error if it starts up and a PID file exists, if not, how do you name multiple PID files? When exiting, how do you make sure that the PID file is deleted?

Some of these questions are addressed by my initial implementation of the PID file in the github.com/bbengfort/x/pid package. The stub of that implementation is as follows:

// Package PID handles process id information and enables cross-process // communication between the application and the command line client. package pid

import ( "encoding/json" "fmt" "io/ioutil" "os" "os/user" "path/filepath" )

//=========================================================================== // PID File Management //===========================================================================

// PID describes the server process and is accessed by both the server and the // command line client in order to facilitate cross-process communication. type PID struct { PID int `json:"pid"` // The process id assigned by the OS PPID int `json:"ppid"` // The parent process id }

// Path returns the best possible PID file for the current system, by first // attempting to get the user directory then resorting to /var/run on Linux // systems and elsewhere on other systems. // // Note that this method should always return a single PID path for a running // instance of the process in order to prevent confusion. func (pid *PID) Path(filename string) string { usr, err := user.Current() if err == nil { return filepath.Join(usr.HomeDir, ".run", filename) }

return filepath.Join("/", "var", "run", filename) }

// Save the PID file to disk after first determining the process ids. // NOTE: This method will fail if the PID file already exists. func (pid *PID) Save() error { var err error

// Get the currently running Process ID and Parent ID pid.PID = os.Getpid() pid.PPID = os.Getppid()

// Marshall the JSON representation data, err := json.Marshal(pid) if err != nil { return err }

path := pid.Path() // Ensure that a PID file does not exist (race possible) if _, err := os.Stat(path); os.IsNotExist(err) { // Make sure the directory exists. if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { return err }

// Write the JSON representation of the PID file to disk return ioutil.WriteFile(path, data, 0644) }

return fmt.Errorf("PID file exists already at '%s'", path) }

// Load the PID file -- used by the command line client to populate the PID. func (pid *PID) Load() error { data, err := ioutil.ReadFile(pid.Path()) if err != nil { return fmt.Errorf("no PID file exists at %s; kekahu not running?", pid.Path()) }

return json.Unmarshal(data, &pid) }

// Free the PID file (delete it) -- used by the server on shutdown to cleanup // and ensure that stray process information isn't just lying about. // Does not return an error if the PID file does not exist. func (pid *PID) Free() error { // If the PID file doesn't exist, just ignore and return. if _, err := os.Stat(pid.Path()); os.IsNotExist(err) { return nil }

// Delete the PID file return os.Remove(pid.Path()) }

This implementation stores both the PID and the parent PID (if the process forks) in the PID file in JSON format. JSON is not necessarily required, but it does make the format a bit simpler to understand and also allows the addition of other process information.

So why talk about PIDs? Well I’m writing some programs that need to be run in the background and always started up. I’m investigating systemd for Ubuntu and launchtl for OS X in order to manage the processes. But more on that in a future post.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK