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 }