LoginSignup
2

More than 3 years have passed since last update.

Go言語でロックファイルを利用した多重起動防止サンプル

Last updated at Posted at 2020-01-13

Go言語のバッチ等で、ロックファイルを利用した多重起動防止のサンプル実装です。

アプリケーションルートのディレクトリ配下の/utilディレクトリに置かれる想定で、~/go/src//util/を除いたディレクトリ名のハイフン区切りをロックファイル名にしています。

ex.) ~/go/src/example/lockfile/util/lockfile.goの場合
ロックファイルは ~/go/src/example/lockfile/example-lockfile.lock

利用例

main.go
package main

import (
    "lockfile/util"
    "os"
)

func main() {
    os.Exit(execute())
}

func execute() int {
    util.Setlock()
    defer util.Unlock()

    // do process

    return 0
}

ソースコード

util/lockfile.go
package util

import (
    "errors"
    "io/ioutil"
    "os"
    "path/filepath"
    "runtime"
    "strings"
    "sync"
)

const separator string = string(filepath.Separator)
const srcDir string = "src" + separator

var lockfile string
var once = new(sync.Once)

// Setlock lock by creating lockfile
func Setlock() (string, error) {
    once.Do(func() {
        lockfile = lockfilePath()
    })
    return lockfile, lock(lockfile)
}

// Unlock unlock by removing lockfile
func Unlock() error {
    if !exists(lockfile) {
        return errors.New("not running about: " + lockfile)
    }
    return os.Remove(lockfile)
}

func lockfilePath() string {
    _, filename, _, _ := runtime.Caller(0)
    rootDir := filepath.Dir(filename)[:strings.Index(filename, separator+"util")]
    packageName := rootDir[strings.Index(rootDir, srcDir)+len(srcDir):]
    lockfileName := strings.ReplaceAll(packageName, separator, "-") + ".lock"
    return filepath.Join(rootDir, lockfileName)
}

func exists(filepath string) bool {
    _, err := os.Stat(filepath)
    return !os.IsNotExist(err)
}

func lock(lockfile string) error {
    if exists(lockfile) {
        return errors.New("already running with: " + lockfile)
    }
    return ioutil.WriteFile(lockfile, []byte(""), 0644)
}

簡易テスト

util/lockfile_test.go
package util

import (
    "io/ioutil"
    "os"
    "path/filepath"
    "runtime"
    "strings"
    "testing"
)

func TestFilename(t *testing.T) {
    lockfilePath := lockfilePath()
    if filepath.Base(lockfilePath) != "lockfile.lock" {
        t.Errorf("lockfilePath() = %s; want basename=lockfile.lock", lockfilePath)
    }
    _, filename, _, _ := runtime.Caller(0)
    rootDir := filepath.Dir(filename)[:strings.Index(filename, "/util")]
    if filepath.Dir(lockfilePath) != rootDir {
        t.Errorf("lockfilePath() = %s; want dirpath=$GOPATH/src/path/to/lockfile", lockfilePath)
    }
}

func TestSetlock(t *testing.T) {
    if lockfilePath, err := Setlock(); err != nil {
        t.Errorf("cannot lock by creating lockfile: %s", lockfilePath)
    }
    _, err := Setlock()
    if err == nil {
        t.Errorf("error shoud occurs with existing lockfile")
    }
    os.Remove(lockfilePath())
}

func TestUnlock(t *testing.T) {
    lockfilePath := lockfilePath()
    ioutil.WriteFile(lockfilePath, []byte(""), 0644)
    if err := Unlock(); err != nil {
        t.Errorf("cannot unlock by removing lockfile: %s", lockfilePath)
    }
    err := Unlock()
    if err == nil {
        t.Errorf("error shoud occurs with no lockfile")
    }
}

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2