github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/container/lock_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // +build !windows
     5  
     6  package container_test
     7  
     8  import (
     9  	"fmt"
    10  	"os"
    11  	"runtime"
    12  	"sync/atomic"
    13  	"time"
    14  
    15  	"github.com/juju/testing"
    16  	jc "github.com/juju/testing/checkers"
    17  	gc "gopkg.in/check.v1"
    18  
    19  	"github.com/juju/juju/container"
    20  	coretesting "github.com/juju/juju/testing"
    21  )
    22  
    23  type flockSuite struct {
    24  	testing.IsolationSuite
    25  }
    26  
    27  var _ = gc.Suite(&flockSuite{})
    28  
    29  func (s *flockSuite) SetUpSuite(c *gc.C) {
    30  	s.IsolationSuite.SetUpSuite(c)
    31  }
    32  
    33  // This test also happens to test that locks can get created when the
    34  // lock directory doesn't exist.
    35  func (s *flockSuite) TestValidNamesLockDir(c *gc.C) {
    36  
    37  	for _, name := range []string{
    38  		"a",
    39  		"longer",
    40  		"longer-with.special-characters",
    41  	} {
    42  		dir := c.MkDir()
    43  		_, err := container.NewLock(dir, name)
    44  		c.Assert(err, jc.ErrorIsNil)
    45  	}
    46  }
    47  
    48  func (s *flockSuite) TestInvalidNames(c *gc.C) {
    49  
    50  	for _, name := range []string{
    51  		".start",
    52  		"-start",
    53  		"NoCapitals",
    54  		"no+plus",
    55  		"no/slash",
    56  		"no\\backslash",
    57  		"no$dollar",
    58  		"no:colon",
    59  	} {
    60  		dir := c.MkDir()
    61  		_, err := container.NewLock(dir, name)
    62  		c.Assert(err, gc.ErrorMatches, "Invalid lock name .*")
    63  	}
    64  }
    65  
    66  func (s *flockSuite) TestNewLockWithExistingDir(c *gc.C) {
    67  	dir := c.MkDir()
    68  	err := os.MkdirAll(dir, 0755)
    69  	c.Assert(err, jc.ErrorIsNil)
    70  	_, err = container.NewLock(dir, "special")
    71  	c.Assert(err, jc.ErrorIsNil)
    72  }
    73  
    74  func (s *flockSuite) TestLockBlocks(c *gc.C) {
    75  
    76  	dir := c.MkDir()
    77  	lock1, err := container.NewLock(dir, "testing")
    78  	c.Assert(err, jc.ErrorIsNil)
    79  	c.Assert(container.IsLocked(lock1), jc.IsFalse)
    80  	lock2, err := container.NewLock(dir, "testing")
    81  	c.Assert(err, jc.ErrorIsNil)
    82  	c.Assert(container.IsLocked(lock2), jc.IsFalse)
    83  
    84  	acquired := make(chan struct{})
    85  	err = lock1.Lock("")
    86  	c.Assert(err, jc.ErrorIsNil)
    87  	c.Assert(container.IsLocked(lock1), jc.IsTrue)
    88  
    89  	go func() {
    90  		lock2.Lock("")
    91  		c.Assert(container.IsLocked(lock2), jc.IsTrue)
    92  		acquired <- struct{}{}
    93  		close(acquired)
    94  	}()
    95  
    96  	// Waiting for something not to happen is inherently hard...
    97  	select {
    98  	case <-acquired:
    99  		c.Fatalf("Unexpected lock acquisition")
   100  	case <-time.After(coretesting.ShortWait):
   101  		// all good
   102  	}
   103  
   104  	err = lock1.Unlock()
   105  	c.Assert(err, jc.ErrorIsNil)
   106  	c.Assert(container.IsLocked(lock1), jc.IsFalse)
   107  
   108  	select {
   109  	case <-acquired:
   110  		// all good
   111  	case <-time.After(coretesting.LongWait):
   112  		c.Fatalf("Expected lock acquisition")
   113  	}
   114  }
   115  
   116  func (s *flockSuite) TestUnlock(c *gc.C) {
   117  	dir := c.MkDir()
   118  	lock, err := container.NewLock(dir, "testing")
   119  	c.Assert(err, jc.ErrorIsNil)
   120  	err = lock.Lock("test")
   121  	c.Assert(err, jc.ErrorIsNil)
   122  	c.Assert(container.IsLocked(lock), jc.IsTrue)
   123  
   124  	err = lock.Unlock()
   125  	c.Assert(err, jc.ErrorIsNil)
   126  	c.Assert(container.IsLocked(lock), jc.IsFalse)
   127  }
   128  
   129  func (s *flockSuite) TestStress(c *gc.C) {
   130  	const lockAttempts = 200
   131  	const concurrentLocks = 10
   132  
   133  	var counter = new(int64)
   134  	// Use atomics to update lockState to make sure the lock isn't held by
   135  	// someone else. A value of 1 means locked, 0 means unlocked.
   136  	var lockState = new(int32)
   137  	var done = make(chan struct{})
   138  	defer close(done)
   139  
   140  	dir := c.MkDir()
   141  
   142  	var stress = func(name string) {
   143  		defer func() { done <- struct{}{} }()
   144  		lock, err := container.NewLock(dir, "testing")
   145  		if err != nil {
   146  			c.Errorf("Failed to create a new lock")
   147  			return
   148  		}
   149  		for i := 0; i < lockAttempts; i++ {
   150  			err = lock.Lock(name)
   151  			c.Assert(err, jc.ErrorIsNil)
   152  			state := atomic.AddInt32(lockState, 1)
   153  			c.Assert(state, gc.Equals, int32(1))
   154  			// Tell the go routine scheduler to give a slice to someone else
   155  			// while we have this locked.
   156  			runtime.Gosched()
   157  			// need to decrement prior to unlock to avoid the race of someone
   158  			// else grabbing the lock before we decrement the state.
   159  			atomic.AddInt32(lockState, -1)
   160  			err = lock.Unlock()
   161  			c.Assert(err, jc.ErrorIsNil)
   162  			// increment the general counter
   163  			atomic.AddInt64(counter, 1)
   164  		}
   165  	}
   166  
   167  	for i := 0; i < concurrentLocks; i++ {
   168  		go stress(fmt.Sprintf("Lock %d", i))
   169  	}
   170  	for i := 0; i < concurrentLocks; i++ {
   171  		<-done
   172  	}
   173  	c.Assert(*counter, gc.Equals, int64(lockAttempts*concurrentLocks))
   174  }