vitess.io/vitess@v0.16.2/go/vt/topo/test/lock.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package test 18 19 import ( 20 "path" 21 "testing" 22 "time" 23 24 "context" 25 26 "vitess.io/vitess/go/vt/topo" 27 28 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 29 ) 30 31 // timeUntilLockIsTaken is the time to wait until a lock is taken. 32 // We haven't found a better simpler way to guarantee a routine is stuck 33 // waiting for a topo lock than sleeping that amount. 34 var timeUntilLockIsTaken = 10 * time.Millisecond 35 36 // checkLock checks we can lock / unlock as expected. It's using a keyspace 37 // as the lock target. 38 func checkLock(t *testing.T, ts *topo.Server) { 39 ctx := context.Background() 40 if err := ts.CreateKeyspace(ctx, "test_keyspace", &topodatapb.Keyspace{}); err != nil { 41 t.Fatalf("CreateKeyspace: %v", err) 42 } 43 44 conn, err := ts.ConnForCell(context.Background(), topo.GlobalCell) 45 if err != nil { 46 t.Fatalf("ConnForCell(global) failed: %v", err) 47 } 48 49 t.Log("=== checkLockTimeout") 50 checkLockTimeout(ctx, t, conn) 51 52 t.Log("=== checkLockMissing") 53 checkLockMissing(ctx, t, conn) 54 55 t.Log("=== checkLockUnblocks") 56 checkLockUnblocks(ctx, t, conn) 57 } 58 59 func checkLockTimeout(ctx context.Context, t *testing.T, conn topo.Conn) { 60 keyspacePath := path.Join(topo.KeyspacesPath, "test_keyspace") 61 lockDescriptor, err := conn.Lock(ctx, keyspacePath, "") 62 if err != nil { 63 t.Fatalf("Lock: %v", err) 64 } 65 66 // We have the lock, list the keyspace directory. 67 // It should not contain anything, except Ephemeral files. 68 entries, err := conn.ListDir(ctx, keyspacePath, true /*full*/) 69 if err != nil { 70 t.Fatalf("Listdir(%v) failed: %v", keyspacePath, err) 71 } 72 for _, e := range entries { 73 if e.Name == "Keyspace" { 74 continue 75 } 76 if e.Ephemeral { 77 t.Logf("skipping ephemeral node %v in %v", e, keyspacePath) 78 continue 79 } 80 // Non-ephemeral entries better have only ephemeral children. 81 p := path.Join(keyspacePath, e.Name) 82 entries, err := conn.ListDir(ctx, p, true /*full*/) 83 if err != nil { 84 t.Fatalf("Listdir(%v) failed: %v", p, err) 85 } 86 for _, e := range entries { 87 if e.Ephemeral { 88 t.Logf("skipping ephemeral node %v in %v", e, p) 89 } else { 90 t.Errorf("Entry in %v has non-ephemeral DirEntry: %v", p, e) 91 } 92 } 93 } 94 95 // test we can't take the lock again 96 fastCtx, cancel := context.WithTimeout(ctx, timeUntilLockIsTaken) 97 if _, err := conn.Lock(fastCtx, keyspacePath, "again"); !topo.IsErrType(err, topo.Timeout) { 98 t.Fatalf("Lock(again): %v", err) 99 } 100 cancel() 101 102 // test we can interrupt taking the lock 103 interruptCtx, cancel := context.WithCancel(ctx) 104 go func() { 105 time.Sleep(timeUntilLockIsTaken) 106 cancel() 107 }() 108 if _, err := conn.Lock(interruptCtx, keyspacePath, "interrupted"); !topo.IsErrType(err, topo.Interrupted) { 109 t.Fatalf("Lock(interrupted): %v", err) 110 } 111 112 if err := lockDescriptor.Check(ctx); err != nil { 113 t.Errorf("Check(): %v", err) 114 } 115 116 if err := lockDescriptor.Unlock(ctx); err != nil { 117 t.Fatalf("Unlock(): %v", err) 118 } 119 120 // test we can't unlock again 121 if err := lockDescriptor.Unlock(ctx); err == nil { 122 t.Fatalf("Unlock(again) worked") 123 } 124 } 125 126 // checkLockMissing makes sure we can't lock a non-existing directory. 127 func checkLockMissing(ctx context.Context, t *testing.T, conn topo.Conn) { 128 keyspacePath := path.Join(topo.KeyspacesPath, "test_keyspace_666") 129 if _, err := conn.Lock(ctx, keyspacePath, "missing"); err == nil { 130 t.Fatalf("Lock(test_keyspace_666) worked for non-existing keyspace") 131 } 132 } 133 134 // checkLockUnblocks makes sure that a routine waiting on a lock 135 // is unblocked when another routine frees the lock 136 func checkLockUnblocks(ctx context.Context, t *testing.T, conn topo.Conn) { 137 keyspacePath := path.Join(topo.KeyspacesPath, "test_keyspace") 138 unblock := make(chan struct{}) 139 finished := make(chan struct{}) 140 141 // As soon as we're unblocked, we try to lock the keyspace. 142 go func() { 143 <-unblock 144 lockDescriptor, err := conn.Lock(ctx, keyspacePath, "unblocks") 145 if err != nil { 146 t.Errorf("Lock(test_keyspace) failed: %v", err) 147 } 148 if err = lockDescriptor.Unlock(ctx); err != nil { 149 t.Errorf("Unlock(test_keyspace): %v", err) 150 } 151 close(finished) 152 }() 153 154 // Lock the keyspace. 155 lockDescriptor2, err := conn.Lock(ctx, keyspacePath, "") 156 if err != nil { 157 t.Fatalf("Lock(test_keyspace) failed: %v", err) 158 } 159 160 // unblock the go routine so it starts waiting 161 close(unblock) 162 163 // sleep for a while so we're sure the go routine is blocking 164 time.Sleep(timeUntilLockIsTaken) 165 166 if err = lockDescriptor2.Unlock(ctx); err != nil { 167 t.Fatalf("Unlock(test_keyspace): %v", err) 168 } 169 170 timeout := time.After(10 * time.Second) 171 select { 172 case <-finished: 173 case <-timeout: 174 t.Fatalf("Unlock(test_keyspace) timed out") 175 } 176 }