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")
}
}