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