Help us shape the future of Go! Take the 2026 Go Developer Survey

singleton

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Mar 11, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

README

Singleton Locker - Usage Guide

This package implements a file-based singleton pattern that ensures only one instance of your application can run at a time.

Features

  • File-based locking: Uses file locks to prevent multiple instances
  • PID tracking: Writes the current process ID to the lock file
  • Pretty error messages: Shows informative messages when another instance is running
  • Stale lock detection: Detects if a lock file is from a dead process
  • Graceful shutdown: Properly releases locks on exit

Quick Start

Basic Usage
package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    // Create a lock for your application
    lockFile := getLockFilePath("my-unique-app-name")
    lock := NewInstanceLock(lockFile)

    // Try to acquire the lock or exit if another instance is running
    lock.LockOrExit()

    // Ensure the lock is released when the program exits
    defer lock.Unlock()

    fmt.Println("✓ Application started successfully!")

    // Your application logic here
    runYourApplication()
}
Advanced Usage with Error Handling
func main() {
    lockFile := getLockFilePath("my-app")
    lock := NewInstanceLock(lockFile)

    // Manual lock with error handling
    if err := lock.Lock(); err != nil {
        if err == os.ErrExist {
            fmt.Println("Another instance is already running")
            pid := lock.readPIDFromLockFile()
            fmt.Printf("Running instance PID: %d\n", pid)
        } else {
            fmt.Printf("Error acquiring lock: %v\n", err)
        }
        os.Exit(1)
    }
    defer lock.Unlock()

    // Your application code...
}

Example Output

When another instance is already running:
╔════════════════════════════════════════════════════════════╗
║                                                            ║
║          Another instance is already running!              ║
║                                                            ║
╚════════════════════════════════════════════════════════════╝

  Process ID: 12345 (running)
  Lock file: /tmp/go-lock/my-app.lock

  To stop the running instance, use: kill 12345

When lock file is stale (process not running):
╔════════════════════════════════════════════════════════════╗
║                                                            ║
║          Another instance is already running!              ║
║                                                            ║
╚════════════════════════════════════════════════════════════╝

  Process ID: 12345 (stale - process not running)
  Lock file: /tmp/go-lock/my-app.lock

  The lock file may be stale. Remove it with: rm /tmp/go-lock/my-app.lock

API Reference

NewInstanceLock(lockFilePath string) *InstanceLock

Creates a new instance lock with the specified lock file path.

Parameters:

  • lockFilePath: Absolute path to the lock file

Returns:

  • *InstanceLock: A new instance lock object
(il *InstanceLock) LockOrExit()

Tries to acquire the lock. If it fails, prints a pretty error message and exits with code 1.

This is the recommended method for most applications as it handles all error cases automatically.

(il *InstanceLock) Lock() error

Tries to acquire the lock and write the current PID to the lock file.

Returns:

  • error: nil on success, os.ErrExist if locked, or other error on failure
(il *InstanceLock) Unlock() error

Releases the lock.

Returns:

  • error: nil on success, error on failure
getLockFilePath(appName string) string

Returns the path to the lock file for the given application name.

Parameters:

  • appName: Name of your application (will be used as the lock file name)

Returns:

  • string: Absolute path to the lock file (in /tmp/go-lock/)

How It Works

  1. Lock Acquisition: When your application starts, it tries to acquire an exclusive file lock
  2. PID Writing: If successful, it writes the current process ID to the lock file
  3. Lock Checking: If the lock is held by another process, it reads the PID and checks if that process is still running
  4. Pretty Messages: Displays informative messages to help users understand what's happening
  5. Cleanup: When your application exits, the lock is automatically released via defer

Testing

To test the singleton behavior:

  1. Build the application:

    go build -o singleton
    
  2. Run the first instance in the background:

    ./singleton &
    
  3. Try to run a second instance:

    ./singleton
    

    You should see the "Another instance is already running" message.

  4. Stop the first instance:

    kill <PID>
    

Dependencies

  • github.com/gofrs/flock - Cross-platform file locking library

Install with:

go get github.com/gofrs/flock

Platform Support

This implementation works on:

  • Linux
  • macOS
  • Windows

The flock library handles platform-specific differences automatically.

Documentation

Overview

This package provides a simple way to ensure that only one instance of an application is running at a time. It uses file locking to achieve this, and it provides a nice error message if another instance is already running. The lock file is stored in the system's temporary directory, and it contains the PID of the running instance. If another instance tries to acquire the lock, it will read the PID from the lock file and check if the process is still running, providing helpful information to the user about how to stop the running instance or remove a stale lock file.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type InstanceLock

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

func NewInstanceLock

func NewInstanceLock(lockFilePath string) *InstanceLock

/ returns an InstanceLock for the given lock file path. The caller is responsible for calling LockOrExit or Lock to acquire the lock, and Unlock to release it. the lock file is custom in this constructor, so the caller can specify a custom path or use the default one generated by getLockFilePath. This allows for more flexibility in how the lock is used and where the lock file is stored.

func NewProcessLock

func NewProcessLock() *InstanceLock

func (*InstanceLock) Lock

func (il *InstanceLock) Lock() error

Lock tries to acquire the lock and write the PID. Returns error if fails.

func (*InstanceLock) LockOrExit

func (il *InstanceLock) LockOrExit()

LockOrExit tries to acquire the lock. If it fails, it reads the PID from the lock file, prints a pretty error message, and exits with code 1.

func (*InstanceLock) Unlock

func (il *InstanceLock) Unlock() error

Unlock releases the lock

Jump to

Keyboard shortcuts

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