Remove a lot of deferred functions in the request path. (#4733)

* Remove a lot of deferred functions in the request path.

There is an interesting benchmark at https://www.reddit.com/r/golang/comments/3h21nk/simple_micro_benchmark_to_measure_the_overhead_of/

It shows that defer actually adds quite a lot of overhead -- maybe 100ns
per call but we defer a *lot* of functions in the request path. So this
removes some of the ones in request handling, ha, barrier, router, and
physical cache.

One meta-note: nearly every metrics function is in a defer which means
every metrics call we add could add a non-trivial amount of time, e.g.
for every 10 extra metrics statements we add 1ms to a request. I don't
know how to solve this right now without doing what I did in some of
these cases and putting that call into a simple function call that then
goes before each return.

* Simplify barrier defer cleanup
This commit is contained in:
Jeff Mitchell 2018-06-14 09:49:10 -04:00 committed by GitHub
parent be6827feaa
commit 0c2d2226c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 92 additions and 29 deletions

View File

@ -119,12 +119,13 @@ func (c *Cache) Put(ctx context.Context, entry *Entry) error {
lock := locksutil.LockForKey(c.locks, entry.Key) lock := locksutil.LockForKey(c.locks, entry.Key)
lock.Lock() lock.Lock()
defer lock.Unlock()
err := c.backend.Put(ctx, entry) err := c.backend.Put(ctx, entry)
if err == nil { if err == nil {
c.lru.Add(entry.Key, entry) c.lru.Add(entry.Key, entry)
} }
lock.Unlock()
return err return err
} }
@ -135,19 +136,21 @@ func (c *Cache) Get(ctx context.Context, key string) (*Entry, error) {
lock := locksutil.LockForKey(c.locks, key) lock := locksutil.LockForKey(c.locks, key)
lock.RLock() lock.RLock()
defer lock.RUnlock()
// Check the LRU first // Check the LRU first
if raw, ok := c.lru.Get(key); ok { if raw, ok := c.lru.Get(key); ok {
if raw == nil { if raw == nil {
lock.RUnlock()
return nil, nil return nil, nil
} }
lock.RUnlock()
return raw.(*Entry), nil return raw.(*Entry), nil
} }
// Read from the underlying backend // Read from the underlying backend
ent, err := c.backend.Get(ctx, key) ent, err := c.backend.Get(ctx, key)
if err != nil { if err != nil {
lock.RUnlock()
return nil, err return nil, err
} }
@ -156,6 +159,7 @@ func (c *Cache) Get(ctx context.Context, key string) (*Entry, error) {
c.lru.Add(key, ent) c.lru.Add(key, ent)
} }
lock.RUnlock()
return ent, nil return ent, nil
} }
@ -166,12 +170,13 @@ func (c *Cache) Delete(ctx context.Context, key string) error {
lock := locksutil.LockForKey(c.locks, key) lock := locksutil.LockForKey(c.locks, key)
lock.Lock() lock.Lock()
defer lock.Unlock()
err := c.backend.Delete(ctx, key) err := c.backend.Delete(ctx, key)
if err == nil { if err == nil {
c.lru.Remove(key) c.lru.Remove(key)
} }
lock.Unlock()
return err return err
} }
@ -196,10 +201,16 @@ func (c *TransactionalCache) Transaction(ctx context.Context, txns []*TxnEntry)
// Lock the keys // Lock the keys
for _, l := range locksutil.LocksForKeys(c.locks, keys) { for _, l := range locksutil.LocksForKeys(c.locks, keys) {
l.Lock() l.Lock()
defer l.Unlock() }
unlockFunc := func() {
for _, l := range locksutil.LocksForKeys(c.locks, keys) {
l.Unlock()
}
} }
if err := c.Transactional.Transaction(ctx, txns); err != nil { if err := c.Transactional.Transaction(ctx, txns); err != nil {
unlockFunc()
return err return err
} }
@ -216,5 +227,6 @@ func (c *TransactionalCache) Transaction(ctx context.Context, txns []*TxnEntry)
} }
} }
unlockFunc()
return nil return nil
} }

View File

@ -213,8 +213,9 @@ func (b *AESGCMBarrier) KeyLength() (int, int) {
// is not expected to be able to perform any CRUD until it is unsealed. // is not expected to be able to perform any CRUD until it is unsealed.
func (b *AESGCMBarrier) Sealed() (bool, error) { func (b *AESGCMBarrier) Sealed() (bool, error) {
b.l.RLock() b.l.RLock()
defer b.l.RUnlock() sealed := b.sealed
return b.sealed, nil b.l.RUnlock()
return sealed, nil
} }
// VerifyMaster is used to check if the given key matches the master key // VerifyMaster is used to check if the given key matches the master key
@ -636,14 +637,16 @@ func (b *AESGCMBarrier) updateMasterKeyCommon(key []byte) (*Keyring, error) {
func (b *AESGCMBarrier) Put(ctx context.Context, entry *Entry) error { func (b *AESGCMBarrier) Put(ctx context.Context, entry *Entry) error {
defer metrics.MeasureSince([]string{"barrier", "put"}, time.Now()) defer metrics.MeasureSince([]string{"barrier", "put"}, time.Now())
b.l.RLock() b.l.RLock()
defer b.l.RUnlock()
if b.sealed { if b.sealed {
b.l.RUnlock()
return ErrBarrierSealed return ErrBarrierSealed
} }
term := b.keyring.ActiveTerm() term := b.keyring.ActiveTerm()
primary, err := b.aeadForTerm(term) primary, err := b.aeadForTerm(term)
if err != nil { if err != nil {
b.l.RUnlock()
return err return err
} }
@ -652,29 +655,37 @@ func (b *AESGCMBarrier) Put(ctx context.Context, entry *Entry) error {
Value: b.encrypt(entry.Key, term, primary, entry.Value), Value: b.encrypt(entry.Key, term, primary, entry.Value),
SealWrap: entry.SealWrap, SealWrap: entry.SealWrap,
} }
return b.backend.Put(ctx, pe)
err = b.backend.Put(ctx, pe)
b.l.RUnlock()
return err
} }
// Get is used to fetch an entry // Get is used to fetch an entry
func (b *AESGCMBarrier) Get(ctx context.Context, key string) (*Entry, error) { func (b *AESGCMBarrier) Get(ctx context.Context, key string) (*Entry, error) {
defer metrics.MeasureSince([]string{"barrier", "get"}, time.Now()) defer metrics.MeasureSince([]string{"barrier", "get"}, time.Now())
b.l.RLock() b.l.RLock()
defer b.l.RUnlock()
if b.sealed { if b.sealed {
b.l.RUnlock()
return nil, ErrBarrierSealed return nil, ErrBarrierSealed
} }
// Read the key from the backend // Read the key from the backend
pe, err := b.backend.Get(ctx, key) pe, err := b.backend.Get(ctx, key)
if err != nil { if err != nil {
b.l.RUnlock()
return nil, err return nil, err
} else if pe == nil { } else if pe == nil {
b.l.RUnlock()
return nil, nil return nil, nil
} }
// Decrypt the ciphertext // Decrypt the ciphertext
plain, err := b.decryptKeyring(key, pe.Value) plain, err := b.decryptKeyring(key, pe.Value)
if err != nil { if err != nil {
b.l.RUnlock()
return nil, errwrap.Wrapf("decryption failed: {{err}}", err) return nil, errwrap.Wrapf("decryption failed: {{err}}", err)
} }
@ -684,6 +695,8 @@ func (b *AESGCMBarrier) Get(ctx context.Context, key string) (*Entry, error) {
Value: plain, Value: plain,
SealWrap: pe.SealWrap, SealWrap: pe.SealWrap,
} }
b.l.RUnlock()
return entry, nil return entry, nil
} }
@ -691,12 +704,16 @@ func (b *AESGCMBarrier) Get(ctx context.Context, key string) (*Entry, error) {
func (b *AESGCMBarrier) Delete(ctx context.Context, key string) error { func (b *AESGCMBarrier) Delete(ctx context.Context, key string) error {
defer metrics.MeasureSince([]string{"barrier", "delete"}, time.Now()) defer metrics.MeasureSince([]string{"barrier", "delete"}, time.Now())
b.l.RLock() b.l.RLock()
defer b.l.RUnlock()
if b.sealed { if b.sealed {
b.l.RUnlock()
return ErrBarrierSealed return ErrBarrierSealed
} }
return b.backend.Delete(ctx, key) err := b.backend.Delete(ctx, key)
b.l.RUnlock()
return err
} }
// List is used ot list all the keys under a given // List is used ot list all the keys under a given
@ -704,12 +721,16 @@ func (b *AESGCMBarrier) Delete(ctx context.Context, key string) error {
func (b *AESGCMBarrier) List(ctx context.Context, prefix string) ([]string, error) { func (b *AESGCMBarrier) List(ctx context.Context, prefix string) ([]string, error) {
defer metrics.MeasureSince([]string{"barrier", "list"}, time.Now()) defer metrics.MeasureSince([]string{"barrier", "list"}, time.Now())
b.l.RLock() b.l.RLock()
defer b.l.RUnlock()
if b.sealed { if b.sealed {
b.l.RUnlock()
return nil, ErrBarrierSealed return nil, ErrBarrierSealed
} }
return b.backend.List(ctx, prefix) keys, err := b.backend.List(ctx, prefix)
b.l.RUnlock()
return keys, err
} }
// aeadForTerm returns the AES-GCM AEAD for the given term // aeadForTerm returns the AES-GCM AEAD for the given term
@ -852,34 +873,42 @@ func (b *AESGCMBarrier) decryptKeyring(path string, cipher []byte) ([]byte, erro
// Encrypt is used to encrypt in-memory for the BarrierEncryptor interface // Encrypt is used to encrypt in-memory for the BarrierEncryptor interface
func (b *AESGCMBarrier) Encrypt(ctx context.Context, key string, plaintext []byte) ([]byte, error) { func (b *AESGCMBarrier) Encrypt(ctx context.Context, key string, plaintext []byte) ([]byte, error) {
b.l.RLock() b.l.RLock()
defer b.l.RUnlock()
if b.sealed { if b.sealed {
b.l.RUnlock()
return nil, ErrBarrierSealed return nil, ErrBarrierSealed
} }
term := b.keyring.ActiveTerm() term := b.keyring.ActiveTerm()
primary, err := b.aeadForTerm(term) primary, err := b.aeadForTerm(term)
if err != nil { if err != nil {
b.l.RUnlock()
return nil, err return nil, err
} }
ciphertext := b.encrypt(key, term, primary, plaintext) ciphertext := b.encrypt(key, term, primary, plaintext)
b.l.RUnlock()
return ciphertext, nil return ciphertext, nil
} }
// Decrypt is used to decrypt in-memory for the BarrierEncryptor interface // Decrypt is used to decrypt in-memory for the BarrierEncryptor interface
func (b *AESGCMBarrier) Decrypt(ctx context.Context, key string, ciphertext []byte) ([]byte, error) { func (b *AESGCMBarrier) Decrypt(ctx context.Context, key string, ciphertext []byte) ([]byte, error) {
b.l.RLock() b.l.RLock()
defer b.l.RUnlock()
if b.sealed { if b.sealed {
b.l.RUnlock()
return nil, ErrBarrierSealed return nil, ErrBarrierSealed
} }
// Decrypt the ciphertext // Decrypt the ciphertext
plain, err := b.decryptKeyring(key, ciphertext) plain, err := b.decryptKeyring(key, ciphertext)
if err != nil { if err != nil {
b.l.RUnlock()
return nil, errwrap.Wrapf("decryption failed: {{err}}", err) return nil, errwrap.Wrapf("decryption failed: {{err}}", err)
} }
b.l.RUnlock()
return plain, nil return plain, nil
} }

View File

@ -23,8 +23,9 @@ import (
// Standby checks if the Vault is in standby mode // Standby checks if the Vault is in standby mode
func (c *Core) Standby() (bool, error) { func (c *Core) Standby() (bool, error) {
c.stateLock.RLock() c.stateLock.RLock()
defer c.stateLock.RUnlock() standby := c.standby
return c.standby, nil c.stateLock.RUnlock()
return standby, nil
} }
// Leader is used to get the current active leader // Leader is used to get the current active leader
@ -36,30 +37,34 @@ func (c *Core) Leader() (isLeader bool, leaderAddr, clusterAddr string, err erro
} }
c.stateLock.RLock() c.stateLock.RLock()
defer c.stateLock.RUnlock()
// Check if sealed // Check if sealed
if c.sealed { if c.sealed {
c.stateLock.RUnlock()
return false, "", "", consts.ErrSealed return false, "", "", consts.ErrSealed
} }
// Check if we are the leader // Check if we are the leader
if !c.standby { if !c.standby {
c.stateLock.RUnlock()
return true, c.redirectAddr, c.clusterAddr, nil return true, c.redirectAddr, c.clusterAddr, nil
} }
// Initialize a lock // Initialize a lock
lock, err := c.ha.LockWith(coreLockPath, "read") lock, err := c.ha.LockWith(coreLockPath, "read")
if err != nil { if err != nil {
c.stateLock.RUnlock()
return false, "", "", err return false, "", "", err
} }
// Read the value // Read the value
held, leaderUUID, err := lock.Value() held, leaderUUID, err := lock.Value()
if err != nil { if err != nil {
c.stateLock.RUnlock()
return false, "", "", err return false, "", "", err
} }
if !held { if !held {
c.stateLock.RUnlock()
return false, "", "", nil return false, "", "", nil
} }
@ -72,11 +77,13 @@ func (c *Core) Leader() (isLeader bool, leaderAddr, clusterAddr string, err erro
// If the leader hasn't changed, return the cached value; nothing changes // If the leader hasn't changed, return the cached value; nothing changes
// mid-leadership, and the barrier caches anyways // mid-leadership, and the barrier caches anyways
if leaderUUID == localLeaderUUID && localRedirAddr != "" { if leaderUUID == localLeaderUUID && localRedirAddr != "" {
c.stateLock.RUnlock()
return false, localRedirAddr, localClusterAddr, nil return false, localRedirAddr, localClusterAddr, nil
} }
c.logger.Trace("found new active node information, refreshing") c.logger.Trace("found new active node information, refreshing")
defer c.stateLock.RUnlock()
c.clusterLeaderParamsLock.Lock() c.clusterLeaderParamsLock.Lock()
defer c.clusterLeaderParamsLock.Unlock() defer c.clusterLeaderParamsLock.Unlock()

View File

@ -252,16 +252,17 @@ func (c *Core) checkToken(ctx context.Context, req *logical.Request, unauth bool
// HandleRequest is used to handle a new incoming request // HandleRequest is used to handle a new incoming request
func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err error) { func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err error) {
c.stateLock.RLock() c.stateLock.RLock()
defer c.stateLock.RUnlock()
if c.sealed { if c.sealed {
c.stateLock.RUnlock()
return nil, consts.ErrSealed return nil, consts.ErrSealed
} }
if c.standby { if c.standby {
c.stateLock.RUnlock()
return nil, consts.ErrStandby return nil, consts.ErrStandby
} }
ctx, cancel := context.WithCancel(c.activeContext) ctx, cancel := context.WithCancel(c.activeContext)
defer cancel()
// Allowing writing to a path ending in / makes it extremely difficult to // Allowing writing to a path ending in / makes it extremely difficult to
// understand user intent for the filesystem-like backends (kv, // understand user intent for the filesystem-like backends (kv,
@ -272,6 +273,8 @@ func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err
if strings.HasSuffix(req.Path, "/") && if strings.HasSuffix(req.Path, "/") &&
(req.Operation == logical.UpdateOperation || (req.Operation == logical.UpdateOperation ||
req.Operation == logical.CreateOperation) { req.Operation == logical.CreateOperation) {
cancel()
c.stateLock.RUnlock()
return logical.ErrorResponse("cannot write to a path ending in '/'"), nil return logical.ErrorResponse("cannot write to a path ending in '/'"), nil
} }
@ -338,6 +341,8 @@ func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err
err := jsonutil.DecodeJSON(resp.Data[logical.HTTPRawBody].([]byte), httpResp) err := jsonutil.DecodeJSON(resp.Data[logical.HTTPRawBody].([]byte), httpResp)
if err != nil { if err != nil {
c.logger.Error("failed to unmarshal wrapped HTTP response for audit logging", "error", err) c.logger.Error("failed to unmarshal wrapped HTTP response for audit logging", "error", err)
cancel()
c.stateLock.RUnlock()
return nil, ErrInternalError return nil, ErrInternalError
} }
@ -373,9 +378,13 @@ func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err
} }
if auditErr := c.auditBroker.LogResponse(ctx, logInput, c.auditedHeaders); auditErr != nil { if auditErr := c.auditBroker.LogResponse(ctx, logInput, c.auditedHeaders); auditErr != nil {
c.logger.Error("failed to audit response", "request_path", req.Path, "error", auditErr) c.logger.Error("failed to audit response", "request_path", req.Path, "error", auditErr)
cancel()
c.stateLock.RUnlock()
return nil, ErrInternalError return nil, ErrInternalError
} }
cancel()
c.stateLock.RUnlock()
return return
} }

View File

@ -209,9 +209,9 @@ func (r *Router) MatchingMountByUUID(mountID string) *MountEntry {
} }
r.l.RLock() r.l.RLock()
defer r.l.RUnlock()
_, raw, ok := r.mountUUIDCache.LongestPrefix(mountID) _, raw, ok := r.mountUUIDCache.LongestPrefix(mountID)
r.l.RUnlock()
if !ok { if !ok {
return nil return nil
} }
@ -226,9 +226,9 @@ func (r *Router) MatchingMountByAccessor(mountAccessor string) *MountEntry {
} }
r.l.RLock() r.l.RLock()
defer r.l.RUnlock()
_, raw, ok := r.mountAccessorCache.LongestPrefix(mountAccessor) _, raw, ok := r.mountAccessorCache.LongestPrefix(mountAccessor)
r.l.RUnlock()
if !ok { if !ok {
return nil return nil
} }
@ -239,8 +239,8 @@ func (r *Router) MatchingMountByAccessor(mountAccessor string) *MountEntry {
// MatchingMount returns the mount prefix that would be used for a path // MatchingMount returns the mount prefix that would be used for a path
func (r *Router) MatchingMount(path string) string { func (r *Router) MatchingMount(path string) string {
r.l.RLock() r.l.RLock()
defer r.l.RUnlock() mount := r.matchingMountInternal(path)
var mount = r.matchingMountInternal(path) r.l.RUnlock()
return mount return mount
} }
@ -406,10 +406,10 @@ func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenc
// Grab a read lock on the route entry, this protects against the backend // Grab a read lock on the route entry, this protects against the backend
// being reloaded during a request. // being reloaded during a request.
re.l.RLock() re.l.RLock()
defer re.l.RUnlock()
// Filtered mounts will have a nil backend // Filtered mounts will have a nil backend
if re.backend == nil { if re.backend == nil {
re.l.RUnlock()
return logical.ErrorResponse(fmt.Sprintf("no handler for route '%s'", req.Path)), false, false, logical.ErrUnsupportedPath return logical.ErrorResponse(fmt.Sprintf("no handler for route '%s'", req.Path)), false, false, logical.ErrUnsupportedPath
} }
@ -419,6 +419,7 @@ func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenc
switch req.Operation { switch req.Operation {
case logical.RevokeOperation, logical.RollbackOperation: case logical.RevokeOperation, logical.RollbackOperation:
default: default:
re.l.RUnlock()
return logical.ErrorResponse(fmt.Sprintf("no handler for route '%s'", req.Path)), false, false, logical.ErrUnsupportedPath return logical.ErrorResponse(fmt.Sprintf("no handler for route '%s'", req.Path)), false, false, logical.ErrUnsupportedPath
} }
} }
@ -448,6 +449,7 @@ func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenc
// salted ID, so we double-salt what's going to the cubbyhole backend // salted ID, so we double-salt what's going to the cubbyhole backend
salt, err := r.tokenStoreSaltFunc(ctx) salt, err := r.tokenStoreSaltFunc(ctx)
if err != nil { if err != nil {
re.l.RUnlock()
return nil, false, false, err return nil, false, false, err
} }
req.ClientToken = re.SaltID(salt.SaltID(req.ClientToken)) req.ClientToken = re.SaltID(salt.SaltID(req.ClientToken))
@ -489,7 +491,7 @@ func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenc
req.SetTokenEntry(nil) req.SetTokenEntry(nil)
// Reset the request before returning // Reset the request before returning
defer func() { resetFunc := func() {
req.Path = originalPath req.Path = originalPath
req.MountPoint = mount req.MountPoint = mount
req.MountType = re.mountEntry.Type req.MountType = re.mountEntry.Type
@ -511,11 +513,13 @@ func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenc
req.EntityID = originalEntityID req.EntityID = originalEntityID
req.SetTokenEntry(reqTokenEntry) req.SetTokenEntry(reqTokenEntry)
}() }
// Invoke the backend // Invoke the backend
if existenceCheck { if existenceCheck {
ok, exists, err := re.backend.HandleExistenceCheck(ctx, req) ok, exists, err := re.backend.HandleExistenceCheck(ctx, req)
resetFunc()
re.l.RUnlock()
return nil, ok, exists, err return nil, ok, exists, err
} else { } else {
resp, err := re.backend.HandleRequest(ctx, req) resp, err := re.backend.HandleRequest(ctx, req)
@ -540,6 +544,8 @@ func (r *Router) routeCommon(ctx context.Context, req *logical.Request, existenc
alias.MountAccessor = re.mountEntry.Accessor alias.MountAccessor = re.mountEntry.Accessor
} }
} }
resetFunc()
re.l.RUnlock()
return resp, false, false, err return resp, false, false, err
} }
} }