launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/utils/fslock/fslock_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package fslock_test
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path"
    11  	"runtime"
    12  	"sync/atomic"
    13  	"time"
    14  
    15  	"launchpad.net/errgo/errors"
    16  	gc "launchpad.net/gocheck"
    17  	"launchpad.net/tomb"
    18  
    19  	coretesting "launchpad.net/juju-core/testing"
    20  	"launchpad.net/juju-core/testing/testbase"
    21  	"launchpad.net/juju-core/utils/fslock"
    22  )
    23  
    24  type fslockSuite struct {
    25  	testbase.LoggingSuite
    26  	lockDelay time.Duration
    27  }
    28  
    29  var _ = gc.Suite(&fslockSuite{})
    30  
    31  func (s *fslockSuite) SetUpSuite(c *gc.C) {
    32  	s.LoggingSuite.SetUpSuite(c)
    33  	s.PatchValue(&fslock.LockWaitDelay, 1*time.Millisecond)
    34  }
    35  
    36  // This test also happens to test that locks can get created when the parent
    37  // lock directory doesn't exist.
    38  func (s *fslockSuite) TestValidNamesLockDir(c *gc.C) {
    39  
    40  	for _, name := range []string{
    41  		"a",
    42  		"longer",
    43  		"longer-with.special-characters",
    44  	} {
    45  		dir := c.MkDir()
    46  		_, err := fslock.NewLock(dir, name)
    47  		c.Assert(err, gc.IsNil)
    48  	}
    49  }
    50  
    51  func (s *fslockSuite) TestInvalidNames(c *gc.C) {
    52  
    53  	for _, name := range []string{
    54  		".start",
    55  		"-start",
    56  		"NoCapitals",
    57  		"no+plus",
    58  		"no/slash",
    59  		"no\\backslash",
    60  		"no$dollar",
    61  		"no:colon",
    62  	} {
    63  		dir := c.MkDir()
    64  		_, err := fslock.NewLock(dir, name)
    65  		c.Assert(err, gc.ErrorMatches, "Invalid lock name .*")
    66  	}
    67  }
    68  
    69  func (s *fslockSuite) TestNewLockWithExistingDir(c *gc.C) {
    70  	dir := c.MkDir()
    71  	err := os.MkdirAll(dir, 0755)
    72  	c.Assert(err, gc.IsNil)
    73  	_, err = fslock.NewLock(dir, "special")
    74  	c.Assert(err, gc.IsNil)
    75  }
    76  
    77  func (s *fslockSuite) TestNewLockWithExistingFileInPlace(c *gc.C) {
    78  	dir := c.MkDir()
    79  	err := os.MkdirAll(dir, 0755)
    80  	c.Assert(err, gc.IsNil)
    81  	path := path.Join(dir, "locks")
    82  	err = ioutil.WriteFile(path, []byte("foo"), 0644)
    83  	c.Assert(err, gc.IsNil)
    84  
    85  	_, err = fslock.NewLock(path, "special")
    86  	c.Assert(err, gc.ErrorMatches, `.* not a directory`)
    87  }
    88  
    89  func (s *fslockSuite) TestIsLockHeldBasics(c *gc.C) {
    90  	dir := c.MkDir()
    91  	lock, err := fslock.NewLock(dir, "testing")
    92  	c.Assert(err, gc.IsNil)
    93  	c.Assert(lock.IsLockHeld(), gc.Equals, false)
    94  
    95  	err = lock.Lock("")
    96  	c.Assert(err, gc.IsNil)
    97  	c.Assert(lock.IsLockHeld(), gc.Equals, true)
    98  
    99  	err = lock.Unlock()
   100  	c.Assert(err, gc.IsNil)
   101  	c.Assert(lock.IsLockHeld(), gc.Equals, false)
   102  }
   103  
   104  func (s *fslockSuite) TestIsLockHeldTwoLocks(c *gc.C) {
   105  	dir := c.MkDir()
   106  	lock1, err := fslock.NewLock(dir, "testing")
   107  	c.Assert(err, gc.IsNil)
   108  	lock2, err := fslock.NewLock(dir, "testing")
   109  	c.Assert(err, gc.IsNil)
   110  
   111  	err = lock1.Lock("")
   112  	c.Assert(err, gc.IsNil)
   113  	c.Assert(lock2.IsLockHeld(), gc.Equals, false)
   114  }
   115  
   116  func (s *fslockSuite) TestLockBlocks(c *gc.C) {
   117  
   118  	dir := c.MkDir()
   119  	lock1, err := fslock.NewLock(dir, "testing")
   120  	c.Assert(err, gc.IsNil)
   121  	lock2, err := fslock.NewLock(dir, "testing")
   122  	c.Assert(err, gc.IsNil)
   123  
   124  	acquired := make(chan struct{})
   125  	err = lock1.Lock("")
   126  	c.Assert(err, gc.IsNil)
   127  
   128  	go func() {
   129  		lock2.Lock("")
   130  		acquired <- struct{}{}
   131  		close(acquired)
   132  	}()
   133  
   134  	// Waiting for something not to happen is inherently hard...
   135  	select {
   136  	case <-acquired:
   137  		c.Fatalf("Unexpected lock acquisition")
   138  	case <-time.After(coretesting.ShortWait):
   139  		// all good
   140  	}
   141  
   142  	err = lock1.Unlock()
   143  	c.Assert(err, gc.IsNil)
   144  
   145  	select {
   146  	case <-acquired:
   147  		// all good
   148  	case <-time.After(coretesting.LongWait):
   149  		c.Fatalf("Expected lock acquisition")
   150  	}
   151  
   152  	c.Assert(lock2.IsLockHeld(), gc.Equals, true)
   153  }
   154  
   155  func (s *fslockSuite) TestLockWithTimeoutUnlocked(c *gc.C) {
   156  	dir := c.MkDir()
   157  	lock, err := fslock.NewLock(dir, "testing")
   158  	c.Assert(err, gc.IsNil)
   159  
   160  	err = lock.LockWithTimeout(10*time.Millisecond, "")
   161  	c.Assert(err, gc.IsNil)
   162  }
   163  
   164  func (s *fslockSuite) TestLockWithTimeoutLocked(c *gc.C) {
   165  	dir := c.MkDir()
   166  	lock1, err := fslock.NewLock(dir, "testing")
   167  	c.Assert(err, gc.IsNil)
   168  	lock2, err := fslock.NewLock(dir, "testing")
   169  	c.Assert(err, gc.IsNil)
   170  
   171  	err = lock1.Lock("")
   172  	c.Assert(err, gc.IsNil)
   173  
   174  	err = lock2.LockWithTimeout(10*time.Millisecond, "")
   175  	c.Assert(errors.Cause(err), gc.Equals, fslock.ErrTimeout)
   176  }
   177  
   178  func (s *fslockSuite) TestUnlock(c *gc.C) {
   179  	dir := c.MkDir()
   180  	lock, err := fslock.NewLock(dir, "testing")
   181  	c.Assert(err, gc.IsNil)
   182  
   183  	err = lock.Unlock()
   184  	c.Assert(errors.Cause(err), gc.Equals, fslock.ErrLockNotHeld)
   185  }
   186  
   187  func (s *fslockSuite) TestIsLocked(c *gc.C) {
   188  	dir := c.MkDir()
   189  	lock1, err := fslock.NewLock(dir, "testing")
   190  	c.Assert(err, gc.IsNil)
   191  	lock2, err := fslock.NewLock(dir, "testing")
   192  	c.Assert(err, gc.IsNil)
   193  
   194  	err = lock1.Lock("")
   195  	c.Assert(err, gc.IsNil)
   196  
   197  	c.Assert(lock1.IsLocked(), gc.Equals, true)
   198  	c.Assert(lock2.IsLocked(), gc.Equals, true)
   199  }
   200  
   201  func (s *fslockSuite) TestBreakLock(c *gc.C) {
   202  	dir := c.MkDir()
   203  	lock1, err := fslock.NewLock(dir, "testing")
   204  	c.Assert(err, gc.IsNil)
   205  	lock2, err := fslock.NewLock(dir, "testing")
   206  	c.Assert(err, gc.IsNil)
   207  
   208  	err = lock1.Lock("")
   209  	c.Assert(err, gc.IsNil)
   210  
   211  	err = lock2.BreakLock()
   212  	c.Assert(err, gc.IsNil)
   213  	c.Assert(lock2.IsLocked(), gc.Equals, false)
   214  
   215  	// Normally locks are broken due to client crashes, not duration.
   216  	err = lock1.Unlock()
   217  	c.Assert(errors.Cause(err), gc.Equals, fslock.ErrLockNotHeld)
   218  
   219  	// Breaking a non-existant isn't an error
   220  	err = lock2.BreakLock()
   221  	c.Assert(err, gc.IsNil)
   222  }
   223  
   224  func (s *fslockSuite) TestMessage(c *gc.C) {
   225  	dir := c.MkDir()
   226  	lock, err := fslock.NewLock(dir, "testing")
   227  	c.Assert(err, gc.IsNil)
   228  	c.Assert(lock.Message(), gc.Equals, "")
   229  
   230  	err = lock.Lock("my message")
   231  	c.Assert(err, gc.IsNil)
   232  	c.Assert(lock.Message(), gc.Equals, "my message")
   233  
   234  	// Unlocking removes the message.
   235  	err = lock.Unlock()
   236  	c.Assert(err, gc.IsNil)
   237  	c.Assert(lock.Message(), gc.Equals, "")
   238  }
   239  
   240  func (s *fslockSuite) TestMessageAcrossLocks(c *gc.C) {
   241  	dir := c.MkDir()
   242  	lock1, err := fslock.NewLock(dir, "testing")
   243  	c.Assert(err, gc.IsNil)
   244  	lock2, err := fslock.NewLock(dir, "testing")
   245  	c.Assert(err, gc.IsNil)
   246  
   247  	err = lock1.Lock("very busy")
   248  	c.Assert(err, gc.IsNil)
   249  	c.Assert(lock2.Message(), gc.Equals, "very busy")
   250  }
   251  
   252  func (s *fslockSuite) TestInitialMessageWhenLocking(c *gc.C) {
   253  	dir := c.MkDir()
   254  	lock, err := fslock.NewLock(dir, "testing")
   255  	c.Assert(err, gc.IsNil)
   256  
   257  	err = lock.Lock("initial message")
   258  	c.Assert(err, gc.IsNil)
   259  	c.Assert(lock.Message(), gc.Equals, "initial message")
   260  
   261  	err = lock.Unlock()
   262  	c.Assert(err, gc.IsNil)
   263  
   264  	err = lock.LockWithTimeout(10*time.Millisecond, "initial timeout message")
   265  	c.Assert(err, gc.IsNil)
   266  	c.Assert(lock.Message(), gc.Equals, "initial timeout message")
   267  }
   268  
   269  func (s *fslockSuite) TestStress(c *gc.C) {
   270  	const lockAttempts = 200
   271  	const concurrentLocks = 10
   272  
   273  	var counter = new(int64)
   274  	// Use atomics to update lockState to make sure the lock isn't held by
   275  	// someone else. A value of 1 means locked, 0 means unlocked.
   276  	var lockState = new(int32)
   277  	var done = make(chan struct{})
   278  	defer close(done)
   279  
   280  	dir := c.MkDir()
   281  
   282  	var stress = func(name string) {
   283  		defer func() { done <- struct{}{} }()
   284  		lock, err := fslock.NewLock(dir, "testing")
   285  		if err != nil {
   286  			c.Errorf("Failed to create a new lock")
   287  			return
   288  		}
   289  		for i := 0; i < lockAttempts; i++ {
   290  			err = lock.Lock(name)
   291  			c.Assert(err, gc.IsNil)
   292  			state := atomic.AddInt32(lockState, 1)
   293  			c.Assert(state, gc.Equals, int32(1))
   294  			// Tell the go routine scheduler to give a slice to someone else
   295  			// while we have this locked.
   296  			runtime.Gosched()
   297  			// need to decrement prior to unlock to avoid the race of someone
   298  			// else grabbing the lock before we decrement the state.
   299  			atomic.AddInt32(lockState, -1)
   300  			err = lock.Unlock()
   301  			c.Assert(err, gc.IsNil)
   302  			// increment the general counter
   303  			atomic.AddInt64(counter, 1)
   304  		}
   305  	}
   306  
   307  	for i := 0; i < concurrentLocks; i++ {
   308  		go stress(fmt.Sprintf("Lock %d", i))
   309  	}
   310  	for i := 0; i < concurrentLocks; i++ {
   311  		<-done
   312  	}
   313  	c.Assert(*counter, gc.Equals, int64(lockAttempts*concurrentLocks))
   314  }
   315  
   316  func (s *fslockSuite) TestTomb(c *gc.C) {
   317  	const timeToDie = 200 * time.Millisecond
   318  	die := tomb.Tomb{}
   319  
   320  	dir := c.MkDir()
   321  	lock, err := fslock.NewLock(dir, "testing")
   322  	c.Assert(err, gc.IsNil)
   323  	// Just use one lock, and try to lock it twice.
   324  	err = lock.Lock("very busy")
   325  	c.Assert(err, gc.IsNil)
   326  
   327  	checkTomb := func() error {
   328  		select {
   329  		case <-die.Dying():
   330  			return tomb.ErrDying
   331  		default:
   332  			// no-op to fall through to return.
   333  		}
   334  		return nil
   335  	}
   336  
   337  	go func() {
   338  		time.Sleep(timeToDie)
   339  		die.Killf("time to die")
   340  	}()
   341  
   342  	err = lock.LockWithFunc("won't happen", checkTomb)
   343  	c.Assert(errors.Cause(err), gc.Equals, tomb.ErrDying)
   344  	c.Assert(lock.Message(), gc.Equals, "very busy")
   345  
   346  }