Question

I need to only allow one instance of my Golang executable at a time. I'm not sure how to use a Global Mutex to make sure no other instances are running.

This would be running on a Windows Machine.

Was it helpful?

Solution

I know this topic is a bit old, but I needed it recently on Windows and I'll post here how I did it in case someone else needs.

Thx to @VonC for pointing me in the right direction.

var (
    kernel32        = syscall.NewLazyDLL("kernel32.dll")
    procCreateMutex = kernel32.NewProc("CreateMutexW")
)

func CreateMutex(name string) (uintptr, error) {
    ret, _, err := procCreateMutex.Call(
        0,
        0,
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(name))),
    )
    switch int(err.(syscall.Errno)) {
    case 0:
        return ret, nil
    default:
        return ret, err
    }
}

// mutexName starting with "Global\" will work across all user sessions
_, err := CreateMutex("SomeMutexName")

I created a lib with a more complete example: https://github.com/rodolfoag/gow32

Thx!

OTHER TIPS

There doesn't seem to be a cross-platform solution (beside writing a file, and looking for that file at start time).

On Windows, this thread reports

the recommended approach (and the one that has worked great for me) is to use the CreateSemaphore function.
If the name you specify starts with "Global\", then the semaphore is unique for the entire system and a second attempt to open it will fail.

This is a kernel32 call, which has some wrapper in Go available.

kostix adds in the comments:

look at the Go source code around the pkg\syscall hierarchy -- it contains a good wealth of examples on how to call out to DLLs on Windows using syscalls (and that's how you access anything in Windows API).

That would be syscall/dll_windows.go. (And here is a gist)

The odbc package by brainman is another example of direct API calls on Windows -- possibly easier to digest.

Like api/zapi_windows.go.

You could use sockets, simple to use and will work on everything really.

package main

import (
    "fmt"
    "net"
    "os"
    "strings"
)

const (
    INSTANCE_PORT = 9292
)

func main() {
    listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", INSTANCE_PORT))
    if err != nil {
        if strings.Index(err.Error(), "in use") != -1 {
            //optionally send command line arguments to the other instance
            fmt.Fprintln(os.Stderr, "Already running.")
            return
        } else {
            panic(err)
        }
    }

    for {
        conn, err := listener.Accept()
        if err != nil {
            println("Error accept:", err.Error())
            return
        }
        go do_something_with(conn)
    }
}

You could adapt the code from tendo's python library source

what they do is for windows :

creating a file made of the executable absolute path (well it's a library, so in your case, you can just define an identifier, to prevent you from "i put the executable in 2 places")

  • For windows: trying first to remove the file if existing, and if not creating the file with os.O_CREAT | os.O_EXCL | os.O_RDWR
  • For POSIX compatible systems: trying first to remove the file if existing and if not creating the file and acquiring a lock on it using fcntl.LOCK_EX | fcntl.LOCK_NB

any failure mean the program is already running

and then you can use a defer action to remove the lock (on posix system) and delete the file

Go permit you to create both version wit a build comment to tell which file to compile depending on your OS so you have

for unix system

// +build !windows

package main

import (
    "os"
    "syscall"
)

func create_lock_file(filename string) (*os.File, error) {
    file, err := os.OpenFile(filename, os.O_WRONLY, 0666)
    if err != nil {
        return nil, err
    }
    err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
    if err != nil {
        return nil, err
    }
    return file, nil
}

for windows:

// +build !windows

package main

import (
    "os"
)

func create_lock_file(filename string) (*os.File, error) {
    if _, err := os.Stat(filename); err == nil {
        err = os.Remove(filename)
        if err != nil {
            return nil, err
        }

    }
    return os.OpenFile(filename, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666)
}

and a test

package main

import (
    "fmt"
    "time"
)

func main() {

    _, err := create_lock_file("plop.lock")
    if err != nil {
        fmt.Println("error ", err.Error())
    }

    time.Sleep(10 * time.Second)
    fmt.Println("end ")
}

I've started a library out of it that you can find here

Improvements to this answer. (I am unsure if this answer will distort the original meaning, so I have written a new answer.)

Features:

  • deprecated: StringToUTF16Ptr is deprecated. Use UTF16PtrFromString instead.
  • Add the CloseHandle so that you can cancel the CreateMutexW.
package _test

import (
    "syscall"
    "testing"
    "unsafe"
)

var (
    kernel32         = syscall.NewLazyDLL("kernel32.dll")
    procCreateMutexW = kernel32.NewProc("CreateMutexW")
    procCloseHandle  = kernel32.NewProc("CloseHandle")
)

// https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexW#return-value
func CreateMutexW(proc *syscall.LazyProc, name string) (uintptr, error) {
    if proc.Name != "CreateMutexW" {
        panic("proc.Name != CreateMutexW")
    }
    lpName, _ := syscall.UTF16PtrFromString(name) // LPCWSTR
    if handleID, _, err := proc.Call(
        0,
        0,
        uintptr(unsafe.Pointer(lpName)),
    ); err.(syscall.Errno) == 0 {
        return handleID, nil
    } else {
        return handleID, err
    }
}

// https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle?redirectedfrom=MSDN
func CloseHandle(proc *syscall.LazyProc, handle uintptr) error {
    if proc.Name != "CloseHandle" {
        panic("proc.Name != CloseHandle")
    }
    val, _, err := proc.Call(handle)
    if val == 0 {
        return err
    }
    return nil
}

func TestCreateMutexW(t *testing.T) {
    handle, err := CreateMutexW(procCreateMutexW, "hello world")
    if err != nil {
        t.Fatalf(err.Error())
    }

    _, err = CreateMutexW(procCreateMutexW, "hello world")
    if err == nil || err != syscall.ERROR_ALREADY_EXISTS {
        t.Error("should panic")
    }

    if err = CloseHandle(procCloseHandle, handle); err != nil {
        t.Error(err)
    }

    // We can create again since we have closed.
    handle, _ = CreateMutexW(procCreateMutexW, "hello world")
    if err = CloseHandle(procCloseHandle, handle); err != nil {
        t.Error(err)
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top