singleinstance

package
v0.1.10 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2026 License: MIT Imports: 10 Imported by: 0

README

singleinstance

Enforce a single running instance of an application and forward the arguments of later launches to the one already running, cgo-free. macOS/Linux use an flock'd lock file plus a Unix-domain socket; Windows uses a named pipe — no C toolchain, no bundled native libraries.

import "github.com/crgimenes/native/singleinstance"

inst, err := singleinstance.Acquire("com.example.app", singleinstance.Options{
    OnMessage: func(args []string) { /* a later launch passed these */ },
})
if errors.Is(err, singleinstance.ErrAlreadyRunning) {
    _ = singleinstance.Send("com.example.app", os.Args[1:]) // forward and exit
    return
}
defer inst.Release()

How it works

Acquire either becomes the primary (returns an *Instance you hold for the app's lifetime) or reports ErrAlreadyRunning. A secondary launch then calls Send to hand its arguments to the primary, which receives them on Options.OnMessage, and exits. This is the standard "focus/open-in-existing- window" pattern: the second click forwards the file/URL to the app already up.

The lock is released automatically when the primary process dies (the flock is dropped on exit; the Windows pipe instance is destroyed), so there is no stale lock to clean up.

API

Func Description
Acquire(id string, opts Options) (*Instance, error) Become the single instance for id, or get ErrAlreadyRunning.
Send(id string, args []string) error Forward args to the running instance (after ErrAlreadyRunning).
(*Instance) Release() error Drop the lock and stop listening. Idempotent.
Options.OnMessage func([]string) Called on the primary with a later launch's args.
ErrAlreadyRunning, ErrUnsupported Sentinels.

id is any stable application identifier (e.g. a reverse-DNS string); it is hashed into a bounded, safe lock/socket/pipe name. Arguments cross as a []string (JSON on the wire); no native types cross the API boundary.

Platforms

OS Lock Hand-off Status
macOS flock lock file Unix-domain socket
Linux flock lock file Unix-domain socket
Windows named pipe (FILE_FLAG_FIRST_PIPE_INSTANCE) same named pipe ✅ builds + CI

macOS and Linux share one Unix backend (//go:build unix, standard library only). Check the unsupported case with errors.Is(err, singleinstance.ErrUnsupported).

Example

A runnable demo (run it twice to see the hand-off) lives in examples/singleinstance:

go run ./examples/singleinstance            # primary
go run ./examples/singleinstance hello      # secondary, forwards "hello"

Conventions

Part of native; follows the shared shape — public API in a tag-free singleinstance.go with ErrUnsupported, a shared _unix.go backend for macOS/Linux, _windows.go, and _other.go so every GOOS builds.

Documentation

Overview

Package singleinstance enforces a single running instance of an application and hands the launch arguments of later starts to the one already running.

The typical flow: on startup call Acquire. If it succeeds you are the primary instance — hold the returned *Instance for the app's lifetime. If it returns ErrAlreadyRunning, another instance owns the lock, so forward your arguments to it with Send and exit; the primary receives them through Options.OnMessage.

inst, err := singleinstance.Acquire("com.example.app", singleinstance.Options{
	OnMessage: func(args []string) { /* a second launch passed these */ },
})
if errors.Is(err, singleinstance.ErrAlreadyRunning) {
	_ = singleinstance.Send("com.example.app", os.Args[1:])
	return
}
defer inst.Release()

Backends use only what the OS ships: flock + a Unix-domain socket on macOS/Linux (no purego), a named pipe on Windows (via purego). No cgo.

Index

Constants

This section is empty.

Variables

View Source
var ErrAlreadyRunning = errors.New("singleinstance: another instance is already running")

ErrAlreadyRunning is returned by Acquire when another instance already holds the lock for the given id.

View Source
var ErrUnsupported = errors.New("singleinstance: not supported on this platform")

ErrUnsupported is returned on a platform with no backend wired up.

Functions

func Send

func Send(id string, args []string) error

Send delivers args to the instance already running under id. Use it after Acquire returned ErrAlreadyRunning. It returns an error when no instance is listening.

Types

type Instance

type Instance struct {
	// contains filtered or unexported fields
}

Instance is a held single-instance lock. Keep it alive for as long as the application runs; Release it to let another process take over.

func Acquire

func Acquire(id string, opts Options) (*Instance, error)

Acquire tries to become the single instance identified by id (a stable application identifier such as "com.example.app"). It returns the owning *Instance when no instance is running, or ErrAlreadyRunning when one already is — in which case the caller should Send its arguments and exit.

func (*Instance) Release

func (i *Instance) Release() error

Release relinquishes the lock and stops listening for hand-offs. Safe to call more than once; later calls are no-ops that return the first result.

type Options

type Options struct {
	// OnMessage is invoked on the primary instance with the arguments a later
	// launch passed to Send. It runs on its own goroutine, so guard shared state
	// (or hand off to your UI thread). Optional — nil discards incoming messages.
	OnMessage func(args []string)
}

Options configures Acquire.

Jump to

Keyboard shortcuts

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