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 }