/derailleur

Derailleur is a locking package that utilizes the local filesystem.

Primary LanguageGo

Derailleur

Description

Derailleur is a locking package that utilizes the local filesystem.

How it works

The main concept of Derailleur is a wait file. Each lock contender creates a wait file in order to get a place in line for the lock. The lock contender holds the lock when its wait file is the first in the sorted list of files in the specified directory. When other wait files exist before this lock contender, then it waits for the one directly preceding it to be removed. By having contenders wait on only the contender before them, we avoid the thundering herd problem.

This idea was inspired by the Zookeeper paper.

Usage

Simple usage (no cutting in line)

// Create a derailleur instance
derailleur := derailleur.Derailleur{
	Dir: path.Join(os.TempDir(), "example"),
}

// Create a wait file for this contender
file, err := derailleur.CreateWaitFile()
if err != nil {
	log.Fatal(err)
}
// Clean up when finished
defer os.Remove(file.Name())

// WaitInLine blocks until the lock contender is the first in line.
derailleur.WaitInLine(context.Background())

Usage with cutting in line

// Create a derailleur instance
derailleur := derailleur.Derailleur{
	Dir: path.Join(os.TempDir(), "example"),
}

// Create a wait file for this contender
file, err := derailleur.CreateWaitFile()
if err != nil {
	log.Fatal(err)
}
// Clean up when finished
defer os.Remove(file.Name())

// Check if another contender has cut in line before us.
ownFileChan := make(chan error)
ownWatcher := derailleur.WaitForFile(derailleur.FilePath, ownFileChan)
defer ownWatcher.Close()
go func() {
	<-ownFileChan
	log.Fatal("The wait file of this queuer was forcibly removed. Quitting.")
}()

// Choose to wait:
//
// derailleur.WaitInLine(context.Background())
//
// or to cut in line:
//
// err := derailleur.CutInLine()
// if err != nil {
//     log.Fatal(err)
// }

Room for improvement

Derailleur has a weak point of not being able to deal with contenders that aren't able to do cleanup (e.g. if they're SIGKILL-ed). This could be solved by having timeouts for wait files. The owner of a wait file could touch the file at a certain interval. If it failed to do that for some time, then the succeeding contender would be allowed to remove the stale wait file.