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  }