godaemon

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 27, 2021 License: MIT Imports: 10 Imported by: 47

README

godaemon

Daemonize Go applications with exec() instead of fork(). Read our blog post on the subject.

You can't daemonize the usual way in Go. Daemonizing is a Unix concept that requires some specific things you can't do easily in Go. But you can still accomplish the same goals if you don't mind that your program will start copies of itself several times, as opposed to using fork() the way many programmers are accustomed to doing.

It is somewhat controversial whether it's even a good idea to make programs daemonize themselves, or how to do it correctly (and whether it's even possible to do correctly in Go). Read here, here, and here for more on this topic. However, at VividCortex we do need to run one of our processes as a daemon with the usual attributes of a daemon, and we chose the approach implemented in this package.

Because of the factors mentioned in the first link just given, you should take great care when using this package's approach. It works for us, because we don't do anything like starting up goroutines in our init() functions, or other things that are perfectly legal in Go in general.

Getting Started

View the package documentation for details about how it works. Briefly, to make your program into a daemon, do the following as soon as possible in your main() function:

import (
	"github.com/VividCortex/godaemon"
)

func main() {
	godaemon.MakeDaemon(&godaemon.DaemonAttr{})
}

Use the CaptureOutput attribute if you need to capture your program's standard output and standard error streams. In that case, the function returns two valid readers (io.Reader) that you can read from the program itself. That's particularly useful for functions that write error or diagnosis messages right to the error output, which are normally lost in a daemon.

Use the Files attribute if you need to inherit open files into the daemon. This is primarily intended for avoiding race conditions when holding locks on those files (flocks). Releasing and re-acquiring locks between successive fork calls opens up the chance for another program to steal the lock. However, by declaring your file descriptors in the Files attribute, MakeDaemon() will guarantee that locks are not released throughout the whole process. Your daemon will inherit the file still holding the same locks, with no other process having intervened in between. See the package documentation for more details and sample code. (Note that you shouldn't use this feature to inherit TTY descriptors; otherwise what you get is technically not a daemon.)

Contribute

Contributions are welcome. Please open pull requests or issue reports!

License

This repository is Copyright (c) 2013 VividCortex, Inc. All rights reserved. It is licensed under the MIT license. Please see the LICENSE file for applicable license terms.

Authors

The primary author is Gustavo Kristic, with some documentation and other minor contributions by others at VividCortex.

History

An earlier version of this concept with a slightly different interface was developed internally at VividCortex.

Cats

A Go Daemon is a good thing, and so we present an angelic cat picture:

Angelic Cat

Documentation

Overview

Package godaemon runs a program as a Unix daemon.

Index

Constants

View Source
const (
	StageParent = DaemonStage(iota) // Original process
	StageChild                      // MakeDaemon() called once: first child
	StageDaemon                     // MakeDaemon() run twice: final daemon

)

Stages in the daemonizing process.

Variables

This section is empty.

Functions

func Daemonize

func Daemonize(child ...bool)

Daemonize is equivalent to MakeDaemon(&DaemonAttr{}). It is kept only for backwards API compatibility, but it's usage is otherwise discouraged. Use MakeDaemon() instead. The child parameter, previously used to tell whether to reset the environment or not (see MakeDaemon()), is currently ignored. The environment is reset in all cases.

func MakeDaemon

func MakeDaemon(attrs *DaemonAttr) (io.Reader, io.Reader, error)

MakeDaemon turns the process into a daemon. But given the lack of Go's support for fork(), MakeDaemon() is forced to run the process all over again, from the start. Hence, this should probably be your first call after main begins, unless you understand the effects of calling from somewhere else. Keep in mind that the PID changes after this function is called, given that it only returns in the child; the parent will exit without returning.

Options are provided as a DaemonAttr structure. In particular, setting the CaptureOutput member to true will make the function return two io.Reader streams to read the process' standard output and standard error, respectively. That's useful if you want to capture things you'd normally lose given the lack of console output for a daemon. Some libraries can write error conditions to standard error or make use of Go's log package, that defaults to standard error too. Having these streams allows you to capture them as required. (Note that this function takes no action whatsoever on any of the streams.)

NOTE: If you use them, make sure NOT to take one of these readers and write the data back again to standard output/error, or you'll end up with a loop. Also, note that data will be flushed on a line-by-line basis; i.e., partial lines will be buffered until an end-of-line is seen.

By using the Files member of DaemonAttr you can inherit open files that will still be open once the program is running as a daemon. This may be convenient in general, but it's primarily intended to avoid race conditions while forking, in case a lock (flock) was held on that file. Repeatedly releasing and re-locking while forking is subject to race conditions, cause a different process could lock the file in between. But locks held on files declared at DaemonAttr.Files are guaranteed NOT to be released during the whole process, and still be held by the daemon. To use this feature you should open the file(s), lock if required and then call MakeDaemon using pointers to that *os.File objects; i.e., you'd be passing **os.File objects to MakeDaemon(). However, opening the files (and locking if required) should only be attempted at the parent. (Recall that MakeDaemon() will run the code coming "before" it three times; see the explanation above.) You can filter that by calling Stage() and looking for a godaemon.StageParent result. The last call to MakeDaemon() at the daemon itself will actually *load* the *os.File objects for you; that's why you need to provide a pointer to them. So here's how you'd use it:

var (
	f   *os.File
	err error
)

if godaemon.Stage() == godaemon.StageParent {
	f, err = os.OpenFile(name, opts, perm)
	if err != nil {
		os.Exit(1)
	}
	err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX)
	if err != nil {
		os.Exit(1)
	}
}

_, _, err = godaemon.MakeDaemon(&godaemon.DaemonAttr{
	Files: []**os.File{&f},
})

// Only the daemon will reach this point, where f will be a valid descriptor
// pointing to your file "name", still holding the lock (which will have
// never been released during successive forks). You can operate on f as you
// normally would, like:
f.Close()

NOTE: Do not abuse this feature. Even though you could, it's obviously not a good idea to use this mechanism to keep a terminal device open, for instance. Otherwise, what you get is not strictly a daemon.

Daemonizing is a 3-stage process. In stage 0, the program increments the magical environment variable and starts a copy of itself that's a session leader, with its STDIN, STDOUT, and STDERR disconnected from any tty. It then exits.

In stage 1, the (new copy of) the program starts another copy that's not a session leader, and then exits.

In stage 2, the (new copy of) the program chdir's to /, then sets the umask and reestablishes the original value for the environment variable.

func Readlink(name string) (string, error)

Readlink returns the file pointed to by the given soft link, or an error of type PathError otherwise. This mimics the os.Readlink() function, but works around a bug we've seen in CentOS 5.10 (kernel 2.6.27.10 on x86_64) where the underlying OS function readlink() returns a wrong number of bytes for the result (see man readlink). Here we don't rely blindly on that value; if there's a zero byte among that number of bytes, then we keep only up to that point.

NOTE: We chose not to use os.Readlink() and then search on its result to avoid an extra overhead of converting back to []byte. The function to search for a byte over the string itself (strings.IndexByte()) is only available starting with Go 1.2. Also, we're not searching at every iteration to save some CPU time, even though that could mean extra iterations for systems affected with this bug. But it's wiser to optimize for the general case (i.e., those not affected).

Types

type DaemonAttr

type DaemonAttr struct {
	ProgramName   string      // child's os.Args[0]; copied from parent if empty
	CaptureOutput bool        // whether to capture stdout/stderr
	Files         []**os.File // files to keep open in the daemon
}

DaemonAttr describes the options that apply to daemonization

type DaemonStage

type DaemonStage int

DaemonStage tells in what stage in the process we are. See Stage().

func Stage

func Stage() DaemonStage

Stage returns the "stage of daemonizing", i.e., it allows you to know whether you're currently working in the parent, first child, or the final daemon. This is useless after the call to MakeDaemon(), cause that call will only return for the daemon stage. However, you can still use Stage() to tell whether you've daemonized or not, in case you have a running path that may exclude the call to MakeDaemon().

func (DaemonStage) String

func (s DaemonStage) String() string

String returns a humanly readable daemonization stage.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL