vitess.io/vitess@v0.16.2/go/vt/topo/memorytopo/election.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 memorytopo
    18  
    19  import (
    20  	"context"
    21  	"path"
    22  
    23  	"vitess.io/vitess/go/vt/log"
    24  	"vitess.io/vitess/go/vt/topo"
    25  )
    26  
    27  // NewLeaderParticipation is part of the topo.Server interface
    28  func (c *Conn) NewLeaderParticipation(name, id string) (topo.LeaderParticipation, error) {
    29  	if c.closed {
    30  		return nil, ErrConnectionClosed
    31  	}
    32  
    33  	c.factory.mu.Lock()
    34  	defer c.factory.mu.Unlock()
    35  
    36  	// Make sure the global path exists.
    37  	electionPath := path.Join(electionsPath, name)
    38  	if n := c.factory.getOrCreatePath(c.cell, electionPath); n == nil {
    39  		return nil, topo.NewError(topo.NoNode, electionPath)
    40  	}
    41  
    42  	return &cLeaderParticipation{
    43  		c:    c,
    44  		name: name,
    45  		id:   id,
    46  		stop: make(chan struct{}),
    47  		done: make(chan struct{}),
    48  	}, nil
    49  }
    50  
    51  // cLeaderParticipation implements topo.LeaderParticipation.
    52  //
    53  // We use a directory (in global election path, with the name) with
    54  // ephemeral files in it, that contains the id.  The oldest revision
    55  // wins the election.
    56  type cLeaderParticipation struct {
    57  	// c is our memorytopo connection
    58  	c *Conn
    59  
    60  	// name is the name of this LeaderParticipation
    61  	name string
    62  
    63  	// id is the process's current id.
    64  	id string
    65  
    66  	// stop is a channel closed when Stop is called.
    67  	stop chan struct{}
    68  
    69  	// done is a channel closed when we're done processing the Stop
    70  	done chan struct{}
    71  }
    72  
    73  // WaitForLeadership is part of the topo.LeaderParticipation interface.
    74  func (mp *cLeaderParticipation) WaitForLeadership() (context.Context, error) {
    75  	if mp.c.closed {
    76  		return nil, ErrConnectionClosed
    77  	}
    78  
    79  	// If Stop was already called, mp.done is closed, so we are interrupted.
    80  	select {
    81  	case <-mp.done:
    82  		return nil, topo.NewError(topo.Interrupted, "Leadership")
    83  	default:
    84  	}
    85  
    86  	electionPath := path.Join(electionsPath, mp.name)
    87  
    88  	// We use a cancelable context here. If stop is closed,
    89  	// we just cancel that context.
    90  	lockCtx, lockCancel := context.WithCancel(context.Background())
    91  
    92  	// Try to get the primaryship, by getting a lock.
    93  	ld, err := mp.c.Lock(lockCtx, electionPath, mp.id)
    94  	if err != nil {
    95  		lockCancel()
    96  		close(mp.done)
    97  		// It can be that we were interrupted.
    98  		return nil, err
    99  	}
   100  
   101  	go func() {
   102  		<-mp.stop
   103  		if err := ld.Unlock(context.Background()); err != nil {
   104  			log.Errorf("failed to unlock LockDescriptor %v: %v", electionPath, err)
   105  		}
   106  		lockCancel()
   107  		close(mp.done)
   108  	}()
   109  
   110  	// We got the lock. Return the lockContext. If Stop() is called,
   111  	// it will cancel the lockCtx, and cancel the returned context.
   112  	return lockCtx, nil
   113  }
   114  
   115  // Stop is part of the topo.LeaderParticipation interface
   116  func (mp *cLeaderParticipation) Stop() {
   117  	close(mp.stop)
   118  	<-mp.done
   119  }
   120  
   121  // GetCurrentLeaderID is part of the topo.LeaderParticipation interface
   122  func (mp *cLeaderParticipation) GetCurrentLeaderID(ctx context.Context) (string, error) {
   123  	if mp.c.closed {
   124  		return "", ErrConnectionClosed
   125  	}
   126  
   127  	electionPath := path.Join(electionsPath, mp.name)
   128  
   129  	mp.c.factory.mu.Lock()
   130  	defer mp.c.factory.mu.Unlock()
   131  
   132  	n := mp.c.factory.nodeByPath(mp.c.cell, electionPath)
   133  	if n == nil {
   134  		return "", nil
   135  	}
   136  
   137  	return n.lockContents, nil
   138  }
   139  
   140  // WaitForNewLeader is part of the topo.LeaderParticipation interface
   141  func (mp *cLeaderParticipation) WaitForNewLeader(ctx context.Context) (<-chan string, error) {
   142  	if mp.c.closed {
   143  		return nil, ErrConnectionClosed
   144  	}
   145  
   146  	mp.c.factory.mu.Lock()
   147  	defer mp.c.factory.mu.Unlock()
   148  
   149  	electionPath := path.Join(electionsPath, mp.name)
   150  	n := mp.c.factory.nodeByPath(mp.c.cell, electionPath)
   151  	if n == nil {
   152  		return nil, topo.NewError(topo.NoNode, electionPath)
   153  	}
   154  
   155  	notifications := make(chan string, 8)
   156  	watchIndex := nextWatchIndex
   157  	nextWatchIndex++
   158  	n.watches[watchIndex] = watch{lock: notifications}
   159  
   160  	if n.lock != nil {
   161  		notifications <- n.lockContents
   162  	}
   163  
   164  	go func() {
   165  		defer close(notifications)
   166  
   167  		select {
   168  		case <-mp.stop:
   169  		case <-ctx.Done():
   170  		}
   171  
   172  		mp.c.factory.mu.Lock()
   173  		defer mp.c.factory.mu.Unlock()
   174  
   175  		n := mp.c.factory.nodeByPath(mp.c.cell, electionPath)
   176  		if n == nil {
   177  			return
   178  		}
   179  		delete(n.watches, watchIndex)
   180  	}()
   181  
   182  	return notifications, nil
   183  }