Sometimes, running an always-on program is important and there a lot of moving parts to move around in order to stop and restart a program.

Recently, I’ve been helping UCC Netsoc develop a Discord Bot written in Go to help their committee and their members as well as provide a clean avenue for people to seek help and technical support from their Systems Administrators. Recently, we’ve been doing a lot of development and then manually deploying to the running server with a  supervisor configuration managing everything.

The easiest method for development was to setup a way to “hot-swap” or replace the compiled binary (running process) with a new one after it was rebuilt.


import (
    "fmt"
    "os"
    "syscall"

    "github.com/go-fsnotify/fsnotify"
    "github.com/kardianos/osext"
)

// watchSelf watches for changes in the main binary and hot-swaps itself for the newly
// built binary file
func watchSelf() (chan struct{}, error) {

    // Retrieve file info for the currently running program
    file, err := osext.Executable()
    if err != nil {
        return nil, err
    }

    // Initialize and prepare a new file watcher
    fmt.Printf("watching %q\n", file)
    w, err := fsnotify.NewWatcher()
    if err != nil {
        return nil, err
    }

    done := make(chan struct{})
    go func() {
        for {
            select {
                case e := <-w.Events:
                    // Events mean changes
                    fmt.Printf("watcher received: %+v", e)

                    // Replace the running system call with a new call
                    // to our newly combined binary
                    err := syscall.Exec(file, os.Args, os.Environ())
                    if err != nil {
                        fmt.Errorf("%#v", err)
                    }

                case err := <-w.Errors:
                    // Print out errors as they occur
                    fmt.Printf("watcher error: %+v", err)

                case <-done:
                    // If we ever close the watcher, log it 
                    fmt.Printf("watcher shutting down")
                    return
            }
        }
    }()

    // Add our running file to be watched
    err = w.Add(file)
    if err != nil {
        return nil, err
    }
    return done, nil
}

Leave a Reply