github.com/hashicorp/vault/sdk@v0.13.0/physical/inmem/inmem_ha.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package inmem
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  
    10  	log "github.com/hashicorp/go-hclog"
    11  	"github.com/hashicorp/vault/sdk/physical"
    12  )
    13  
    14  type InmemHABackend struct {
    15  	physical.Backend
    16  	locks  map[string]string
    17  	l      *sync.Mutex
    18  	cond   *sync.Cond
    19  	logger log.Logger
    20  }
    21  
    22  type TransactionalInmemHABackend struct {
    23  	physical.Transactional
    24  	InmemHABackend
    25  }
    26  
    27  // NewInmemHA constructs a new in-memory HA backend. This is only for testing.
    28  func NewInmemHA(_ map[string]string, logger log.Logger) (physical.Backend, error) {
    29  	be, err := NewInmem(nil, logger)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  
    34  	in := &InmemHABackend{
    35  		Backend: be,
    36  		locks:   make(map[string]string),
    37  		logger:  logger,
    38  		l:       new(sync.Mutex),
    39  	}
    40  	in.cond = sync.NewCond(in.l)
    41  	return in, nil
    42  }
    43  
    44  func NewTransactionalInmemHA(_ map[string]string, logger log.Logger) (physical.Backend, error) {
    45  	transInmem, err := NewTransactionalInmem(nil, logger)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	inmemHA := InmemHABackend{
    50  		Backend: transInmem,
    51  		locks:   make(map[string]string),
    52  		logger:  logger,
    53  		l:       new(sync.Mutex),
    54  	}
    55  
    56  	in := &TransactionalInmemHABackend{
    57  		InmemHABackend: inmemHA,
    58  		Transactional:  transInmem.(physical.Transactional),
    59  	}
    60  	in.cond = sync.NewCond(in.l)
    61  	return in, nil
    62  }
    63  
    64  // LockWith is used for mutual exclusion based on the given key.
    65  func (i *InmemHABackend) LockWith(key, value string) (physical.Lock, error) {
    66  	l := &InmemLock{
    67  		in:    i,
    68  		key:   key,
    69  		value: value,
    70  	}
    71  	return l, nil
    72  }
    73  
    74  // LockMapSize is used in some tests to determine whether this backend has ever
    75  // been used for HA purposes rather than simply for storage
    76  func (i *InmemHABackend) LockMapSize() int {
    77  	return len(i.locks)
    78  }
    79  
    80  // HAEnabled indicates whether the HA functionality should be exposed.
    81  // Currently always returns true.
    82  func (i *InmemHABackend) HAEnabled() bool {
    83  	return true
    84  }
    85  
    86  // InmemLock is an in-memory Lock implementation for the HABackend
    87  type InmemLock struct {
    88  	in    *InmemHABackend
    89  	key   string
    90  	value string
    91  
    92  	held     bool
    93  	leaderCh chan struct{}
    94  	l        sync.Mutex
    95  }
    96  
    97  func (i *InmemLock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
    98  	i.l.Lock()
    99  	defer i.l.Unlock()
   100  	if i.held {
   101  		return nil, fmt.Errorf("lock already held")
   102  	}
   103  
   104  	// Attempt an async acquisition
   105  	didLock := make(chan struct{})
   106  	releaseCh := make(chan bool, 1)
   107  	go func() {
   108  		// Wait to acquire the lock
   109  		i.in.l.Lock()
   110  		_, ok := i.in.locks[i.key]
   111  		for ok {
   112  			i.in.cond.Wait()
   113  			_, ok = i.in.locks[i.key]
   114  		}
   115  		i.in.locks[i.key] = i.value
   116  		i.in.l.Unlock()
   117  
   118  		// Signal that lock is held
   119  		close(didLock)
   120  
   121  		// Handle an early abort
   122  		release := <-releaseCh
   123  		if release {
   124  			i.in.l.Lock()
   125  			delete(i.in.locks, i.key)
   126  			i.in.l.Unlock()
   127  			i.in.cond.Broadcast()
   128  		}
   129  	}()
   130  
   131  	// Wait for lock acquisition or shutdown
   132  	select {
   133  	case <-didLock:
   134  		releaseCh <- false
   135  	case <-stopCh:
   136  		releaseCh <- true
   137  		return nil, nil
   138  	}
   139  
   140  	// Create the leader channel
   141  	i.held = true
   142  	i.leaderCh = make(chan struct{})
   143  	return i.leaderCh, nil
   144  }
   145  
   146  func (i *InmemLock) Unlock() error {
   147  	i.l.Lock()
   148  	defer i.l.Unlock()
   149  
   150  	if !i.held {
   151  		return nil
   152  	}
   153  
   154  	close(i.leaderCh)
   155  	i.leaderCh = nil
   156  	i.held = false
   157  
   158  	i.in.l.Lock()
   159  	delete(i.in.locks, i.key)
   160  	i.in.l.Unlock()
   161  	i.in.cond.Broadcast()
   162  	return nil
   163  }
   164  
   165  func (i *InmemLock) Value() (bool, string, error) {
   166  	i.in.l.Lock()
   167  	val, ok := i.in.locks[i.key]
   168  	i.in.l.Unlock()
   169  	return ok, val, nil
   170  }