Running Go Programs as a Background Process

Jun 24, 2013


I have been writing Windows services in C/C++ and then in C# since 1999. Now that I am writing server based software in Go for the Linux OS I am completely lost. What is even more frustrating, is that for the first time the OS I am developing on (Mac OSX) is not the operating system I will be deploying my code on. That will be for another blog post.

I want to run my code as a background process (daemon) on my Mac. My only problem is, I have no idea how that works on the Mac OS.

I was lucky to find an open source project called service on Bitbucket by Daniel Theophanes. This code taught me how to create, install, start and stop daemons on the Mac OS. The code also supports daemons for the Linux OS and Windows.

Background Processes on the Mac OS

The Mac OS has two types of background processes, Daemons and Agents. Here is a definition for each:

A daemon is a program that runs in the background as part of the overall system (that is, it is not tied to a particular user). A daemon cannot display any GUI; more specifically, it is not allowed to connect to the window server. A web server is the perfect example of a daemon.

An agent is a process that runs in the background on behalf of a particular user. Agents are useful because they can do things that daemons can’t, like reliably access the user’s home directory or connect to the window server.

For More Information:
http://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/Introduction.html

Let’s start with how to configure a daemon in the Mac OS.


If you open up finder you will see the following folders. The LaunchDaemons folder under Library is where we we need to add a launchd .plist file. There is also a Library/LaunchDaemons folder under /System for the OS daemons.

The launchd program is the service management framework for starting, stopping and managing daemons, applications, processes, and scripts in the Mac OS. Once the kernel starts launchd, the program scans several directories including /etc for scripts and the LaunchAgents and LaunchDaemons folders in both /Library and /System/Library. Programs found in the LaunchDaemons directories are run as the root user.

Here is the version of the launchd .plist file with all the basic configuration we need:

<?xml version=‘1.0’ encoding=‘UTF-8’?>
<!DOCTYPE plist PUBLIC \“-//Apple Computer//DTD PLIST 1.0//EN\” \“http://www.apple.com/DTDs/PropertyList-1.0.dtd" >
<plist version=‘1.0’>
<dict>
<key>Label</key><string>My Service</string>
<key>ProgramArguments</key>
<array>
<string>/Users/bill/MyService/MyService</string>
</array>
<key>WorkingDirectory</key><string>/Users/bill/MyService</string>
<key>StandardOutPath</key><string>/Users/bill/MyService/My.log</string>
<key>KeepAlive</key><true/>
<key>Disabled</key><false/>
</dict>
</plist>

You can find all the different options for the .plist file here:
https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man5/launchd.plist.5.html

The ProgramArguments key is an important tag:

<key>ProgramArguments</key>
<array>
    <string>/Users/bill/MyService/MyService</string>
</array>

Here you specify the name of the program to run and any other arguments to be passed into main.

These other two tags, WorkingDirectory and StandardOutPath are real helpful too:

<key>WorkingDirectory</key><string>/Users/bill/MyService</string>
<key>StandardOutPath</key><string>/Users/bill/MyService/My.log</string>

Once we have a launchd .plist file we can use a special program called launchctl to start our program as a background process (daemon).

launchctl load /Library/LaunchDaemons/MyService.plist

The launchctl program provides service control and reporting. The load command is used to start a daemon based on the launchd .plist file. To verify that a program is running use the list command:

launchctl list

PID  Status  Label
948  -       0x7ff4a9503410.anonymous.launchctl
946  -       My Service
910  -       0x7ff4a942ce00.anonymous.bash

PID 946 was assigned to the running program, My Service. Now to stop the program from running issue an unload command:

launchctl unload /Library/LaunchDaemons/MyService.plist
launchctl list

PID  Status  Label
948  -       0x7ff4a9503410.anonymous.launchctl
910  -       0x7ff4a942ce00.anonymous.bash

Now the program has been terminated. There is some code we need to implement to handle the start and stop requests from the OS when our program is started and terminated.

OS Specific Go Coding Files

You can create Go source code files that are only compiled for the target platform you’re building.


In my LiteIDE project for Going Go you will see five Go source code files. Three of these files have the name of an environment we can build the code for, darwin (Mac), linux and windows.

Since I am building against the Mac OS, the service_linux.go and service_windows.go files are ignored by the compiler.

The compiler recognizes this naming convention by default.

This is very cool because each environment needs to do a few things differently and use different packages. As in the case of service_windows.go, the following imports are required:

“code.google.com/p/winsvc/eventlog”
“code.google.com/p/winsvc/mgr”
“code.google.com/p/winsvc/svc”

I don’t have these packages installed right now because I don’t plan to run the code on windows. It doesn’t affect building the code because service_windows.go is ignored.

There is another really cool side effect from this, I can reuse types and function names within these files since only one of these files are ever compiled with the program. This means that any code that uses this package does not have to be modified when changing environments.  Really Cool !!

Service Interfaces

Each service must implement three interfaces that provide command and control for the service.

type Service interface {
    Installer
    Controller
    Runner
}

type Installer interface {
    Install(config *Config) error
    Remove() error
}

type Controller interface {
    Start() error
    Stop() error
}

type Runner interface {
    Run(config *Config) error
}

The Installer interface provides the logic for installing and uninstalling the program as a background process on the specific OS. The Controller interface provides logic to start and stop the service from the command line. The final interface Runner is used to perform all application logic and run the program as a service when requested.

Darwin Implementation

Since this post is specific to the Mac OS I will concentrate on the implementation of the service_darwin.go code file.

The Installer interface requires the implementation of two functions, Install and Remove. As described above we need to create a launchd .plist file for the service. The best way to accomplish this is to use the text/template package.

The _InstallScript function uses a multi-line string to create the template for the launchd .plist file.

func _InstallScript() (script string) {
    return &lt;?xml version='1.0' encoding='UTF-8'?&gt;<br /> &lt;!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\" &gt;<br /> &lt;plist version='1.0'&gt;<br /> &lt;dict&gt;<br /> &nbsp; &nbsp; &lt;key&gt;Label&lt;/key&gt;&lt;string&gt;<b>{{.DisplayName}}</b>&lt;/string&gt;<br /> &nbsp; &nbsp; &lt;key&gt;ProgramArguments&lt;/key&gt;<br /> &nbsp; &nbsp; &lt;array&gt;<br /> &nbsp; &nbsp; &nbsp; &nbsp; &lt;string&gt;<b>{{.WorkingDirectory}}</b>/<b>{{.ExecutableName}}</b>&lt;/string&gt;<br /> &nbsp; &nbsp; &lt;/array&gt;<br /> &nbsp; &nbsp; &lt;key&gt;WorkingDirectory&lt;/key&gt;&lt;string&gt;<b>{{.WorkingDirectory}}</b>&lt;/string&gt;<br /> &nbsp; &nbsp; &lt;key&gt;StandardOutPath&lt;/key&gt;&lt;string&gt;<b>{{.LogLocation}}</b>/<b>{{.Name}}</b>.log&lt;/string&gt;<br /> &nbsp; &nbsp; &lt;key&gt;KeepAlive&lt;/key&gt;&lt;true/&gt;<br /> &nbsp; &nbsp; &lt;key&gt;Disabled&lt;/key&gt;&lt;false/&gt;<br /> &lt;/dict&gt;<br /> &lt;/plist&gt;
}

What is cool about multi-line strings is that the carriage return, line feeds and spaces are respected.  Since this is a template, we need to have variables that will be substituted with data.  The {{.variable_name}} convention is used to define those variables.

Here is the implementation of the Install function:

func (service *_DarwinLaunchdService) Install(config *Config) error {
    confPath := service._GetServiceFilePath()

    _, err := os.Stat(confPath)
    if err == nil {
        return fmt.Errorf(“Init already exists: %s”, confPath)
    }

    file, err := os.Create(confPath)
    if err != nil {
        return err
    }
    defer file.Close()

    parameters := struct {
        ExecutableName string
        WorkingDirectory string
        Name string
        DisplayName string
        LongDescription string
        LogLocation string
    }{
        service._Config.ExecutableName,
        service._Config.WorkingDirectory,
        service._Config.Name,
        service._Config.DisplayName,
        service._Config.LongDescription,
        service._Config.LogLocation,
    }

    template := template.Must(template.New(“launchdConfig”).Parse(_InstallScript()))
    return template.Execute(file, &parameters)
}

The _GetServiceFilePath() abstracts the location of the configuration file for each environment implementation. For Darwin the function looks like this:

func (service *_DarwinLaunchdService) _GetServiceFilePath() string {
    return fmt.Sprintf(“/Library/LaunchDaemons/%s.plist”, service._Config.Name)
}

Now the code checks if the file already exists and if it doesn’t, creates an empty file. Next we build a struct on the fly and populate it with all the parameters that we need for the template Execute function call. Notice the names of the fields match the {{.variable_name}} variables in the template.

The Execute function will process the template and then write the finished product to disk using the file handle.

The Controller interface requires two functions, Start and Stop. In the Darwin source code file the implementation is simple:

func (service *_DarwinLaunchdService) Start() error {
    confPath := service._GetServiceFilePath()

    cmd := exec.Command(“launchctl”, “load”, confPath)
    return cmd.Run()
}

func (service *_DarwinLaunchdService) Stop() error {
    confPath := service._GetServiceFilePath()

    cmd := exec.Command(“launchctl”, “unload”, confPath)
    return cmd.Run()
}

Each function executes the launchctl program the same way as we did above. This provides a convenient way to start and stop the daemon.

The final interface that needs to be implemented is Runner with one function called Run.

func (service *_DarwinLaunchdService) Run(config *Config) error {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println(”******> SERVICE PANIC:“, r)
        }
    }()

    fmt.Print(”******> Initing Service\n”)

    if config.Init != nil {
        if err := config.Init(); err != nil {
            return err
        }
    }

    fmt.Print(”******> Starting Service\n”)

    if config.Start != nil {
        if err := config.Start(); err != nil {
            return err
        }
    }

    fmt.Print(”******> Service Started\n”)

    // Create a channel to talk with the OS
    var sigChan = make(chan os.Signal, 1)
    signal.Notify(sigChan, os.Interrupt)

    // Wait for an event
    whatSig := <-sigChan

    fmt.Print(”******> Service Shutting Down\n”)

    if config.Stop != nil {
        if err := config.Stop(); err != nil {
            return err
        }
    }

    fmt.Print(”******> Service Down\n”)
    return nil
}

Run is called when the program is going to run as daemon. It first makes a call to the users onInit and onStart functions. The user is expected to perform any initialization, start their routines and then return control back.

Next the code creates a channel that will be used to communicate with the operating system. The call to signal.Notify binds the channel to receive operating system events. The code then starts an endless loop and waits until the operating system notifies the program with an event. The code is looking for any events that tell it to shutdown. Once an event to shutdown is received, the user onStop function is called and the Run function returns control back to shutdown the program.

Service Manager

Service Manager provides all the boilerplate code so Service can be easily implemented by any program. It implements the Config member function called Run.

func (config *Config) Run() {
    var err error
    config.Service, err = NewService(config)
    if err != nil {
        fmt.Printf(“%s unable to start: %s”, config.DisplayName, err)
        return
    }

    // Perform a command and then return
    if len(os.Args) > 1 {
        verb := os.Args[1]

        switch verb {
            case “install”:
                if err := service.Install(config); err != nil {
                    fmt.Println(“Failed to install:“, err)
                    return
                }

                fmt.Printf(“Service \“%s\” installed.\n”, config.DisplayName)
                return

            case “remove”:
                if err := service.Remove(); err != nil {
                    fmt.Println(“Failed to remove:“, err)
                    return
                }

                fmt.Printf(“Service \“%s\” removed.\n”, config.DisplayName)
                return

            case “debug”:
                config.Start(config)

                fmt.Println(“Starting Up In Debug Mode”)

                reader := bufio.NewReader(os.Stdin)
                reader.ReadString(’\n’)

                fmt.Println(“Shutting Down”)

                config.Stop(config)
                return

           case “start”:
               if err := service.Start(); err != nil {
                   fmt.Println(“Failed to start:“, err)
                   return
               }

               fmt.Printf(“Service \“%s\” started.\n”, config.DisplayName)
               return

           case “stop”:
               if err := service.Stop(); err != nil {
                   fmt.Println(“Failed to stop:“, err)
                   return
               }

               fmt.Printf(“Service \“%s\” stopped.\n”, config.DisplayName)
               return

           default:
               fmt.Printf(“Options for \“%s\”: (install | remove | debug | start | stop)\n”, os.Args[0])
               return
        }
    }

    // Run the service
    service.Run(config)
}

The Run function starts by creating the service object based on the configuration that is provided. Then it looks to at the command line arguments. If there is a command, it is processed and the program terminates. If the command is debug, the program is started as if it were running as a service except it does not hook into the operating system. Hitting the <enter> kill will shut down the program.

If no command line arguments are provided, the code attempts to start as a daemon by calling service.Run.

Implementing The Service

The following code shows an example of using the service:

package main

import (
    “fmt”
    “path/filepath”

    “github.com/goinggo/service/v1”
)

func main() {
    // Capture the working directory
    workingDirectory, _ := filepath.Abs(“”)

    // Create a config object to start the service
    config := service.Config{
        ExecutableName: “MyService”,
        WorkingDirectory: workingDirectory,
        Name: “MyService”,
        DisplayName: “My Service”,
        LongDescription: “My Service provides support for…”,
        LogLocation: _Straps.Strap(“baseFilePath”),

        Init: InitService,
        Start: StartService,
        Stop: StopService,
    }

    // Run any command line options or start the service
    config.Run()
}

func InitService() {
    fmt.Println(“Service Inited”)
}

func StartService() {
    fmt.Println(“Service Started”)
}

func StopService() {
    fmt.Println(“Service Stopped”)
}

The Init, Start and Stop functions must return control back to the config.Run function.

The code I have has been tested with the Mac OS. The code for linux is identical except for the script that needs to be created and installed. Also the implementation for Start and Stop uses different programs. In the near future I will test the Linux portion of the code. The Window portion requires some refactoring and will not build. If you plan to use Windows start with Daniel’s code.

Once you build the code, open a Terminal session where the binary has been created and run the different commands.

./MyService debug

./MyService install

./MyService start

./MyService stop

As always I hope the code helps you create and run your own services.


Ultimate Go Programming LiveLessons

Ultimate Go Programming LiveLessons provides an intensive, comprehensive, and idiomatic view of the Go programming language. This course focuses on both the specification and implementation of the language, including topics ranging from language syntax, design, and guidelines to concurrency, testing, and profiling. This class is perfect for anyone who wants a jump-start in learning Go or wants a more thorough understanding of the language and its internals.

Learn more

Go Training

We have taught Go to thousands of developers all around the world since 2014. There is no other company that has been doing it longer and our material has proven to help jump start developers 6 to 12 months ahead of their knowledge of Go. We know what knowledge developers need in order to be productive and efficient when writing software in Go.

Our Go, Web and Data Science classes are perfect for both experienced and beginning engineers. We start every class from the beginning and get very detailed about the internals, mechanics, specification, guidelines, best practices and design philosophies. We cover a lot about "if performance matters" with a focus on mechanical sympathy, data oriented design, decoupling and writing production software.

Learn More

To learn about Corporate training events, options and special pricing please contact:

William Kennedy
ArdanLabs (www.ardanlabs.com)
bill@ardanlabs.com