Skip to content

Instantly share code, notes, and snippets.

@aamaanaa
Last active January 30, 2025 11:02
Show Gist options
  • Save aamaanaa/bec0b17e6f2909527792c329692db322 to your computer and use it in GitHub Desktop.
Save aamaanaa/bec0b17e6f2909527792c329692db322 to your computer and use it in GitHub Desktop.
Golang Linux & Mac Mutex | Lock file | Only allow one instance of your go app on unix based systems (mac os, Linux)
package utils

import (
    "errors"
    "golang.org/x/sys/unix"
    "os"
)

const lockFile = "/tmp/.test.lock"
var lockFileHandle *os.File

// IsRunning checks if the program is already running by using a lock file.
// Returns true if it is locked (i.e., the program is already running), false otherwise.
func IsRunning() (isRunning bool, err error) {
    lockFileHandle, err = os.OpenFile(lockFile, os.O_CREATE|os.O_RDWR, 0666)
    if err != nil {
       return false, err
    }

    if err = unix.Flock(int(lockFileHandle.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
       if errors.Is(err, unix.EWOULDBLOCK) {
          return true, nil
       }
       return false, err
    }

    return false, nil
}

// Unlock unlocks the lock file and removes it.
// You should always defer this function after calling IsRunning.
func Unlock() error {
    if lockFileHandle == nil {
       return nil
    }
    defer lockFileHandle.Close()

    if err := unix.Flock(int(lockFileHandle.Fd()), unix.LOCK_UN); err != nil {
       return err
    }

    return os.Remove(lockFile)
}
@aamaanaa
Copy link
Author

aamaanaa commented Jan 30, 2025

Simple utils package to only allow a single instance by using a exclusive lock file of your golang app on a unix based system. This works for both Mac and Linux, altough i did not tested on mac since i dont own one. code tested on Fedora Linux.

You must not call os.Exit in the running == true block. This would not run the deffered statement utils.Unlock(); wich releases the lockfile and deletes it.

a dot before the lock files makes it by default a hidden file in the /tmp dir, wich is world writable by any program.

It requires the "golang.org/x/sys/unix", a package for low level system calls for unix based systems.

Get it with:

$ go get golang.org/x/sys/unix

Example usage:

NOTE: place it as first entry in the main() function.

func main() {

	running, err := utils.IsRunning()
	if err != nil {
		fmt.Println(err)
		return
	}

	if running == true {
		fmt.Println("Already running")
		return // do not call os.exit it would not run the defered unlock
	}

	defer func() {
		if err = utils.Unlock(); err != nil {
			fmt.Println(err)
		}
	}()

	// do your other stuff here, do not use os.exit in the main function here!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment