optimize requestcontext: use RWMutex and atomic.Value

pull/8/head
hzxuzhonghu 2018-03-06 11:20:46 +08:00
parent 328e3a8ab9
commit 564d53f71b
3 changed files with 46 additions and 16 deletions

View File

@ -543,6 +543,7 @@ staging/src/k8s.io/apiserver/pkg/endpoints/handlers
staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation
staging/src/k8s.io/apiserver/pkg/endpoints/metrics
staging/src/k8s.io/apiserver/pkg/endpoints/openapi/testing
staging/src/k8s.io/apiserver/pkg/endpoints/request
staging/src/k8s.io/apiserver/pkg/endpoints/testing
staging/src/k8s.io/apiserver/pkg/features
staging/src/k8s.io/apiserver/pkg/registry/generic

View File

@ -18,7 +18,7 @@ package request
import (
"context"
stderrs "errors"
"errors"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -83,7 +83,7 @@ func NewDefaultContext() Context {
func WithValue(parent Context, key interface{}, val interface{}) Context {
internalCtx, ok := parent.(context.Context)
if !ok {
panic(stderrs.New("Invalid context type"))
panic(errors.New("Invalid context type"))
}
return context.WithValue(internalCtx, key, val)
}

View File

@ -20,6 +20,7 @@ import (
"errors"
"net/http"
"sync"
"sync/atomic"
"github.com/golang/glog"
)
@ -40,37 +41,62 @@ type RequestContextMapper interface {
}
type requestContextMap struct {
contexts map[*http.Request]Context
lock sync.Mutex
// contexts contains a request Context map
// atomic.Value has a very good read performance compared to sync.RWMutex
// almost all requests have 3-4 context updates associated with them,
// and they can use only read lock to protect updating context, which is of higher performance with higher burst.
contexts map[*http.Request]*atomic.Value
lock sync.RWMutex
}
// NewRequestContextMapper returns a new RequestContextMapper.
// The returned mapper must be added as a request filter using NewRequestContextFilter.
func NewRequestContextMapper() RequestContextMapper {
return &requestContextMap{
contexts: make(map[*http.Request]Context),
contexts: make(map[*http.Request]*atomic.Value),
}
}
func (c *requestContextMap) getValue(req *http.Request) (*atomic.Value, bool) {
c.lock.RLock()
defer c.lock.RUnlock()
value, ok := c.contexts[req]
return value, ok
}
// contextWrap is a wrapper of Context to prevent atomic.Value to be copied
type contextWrap struct {
Context
}
// Get returns the context associated with the given request (if any), and true if the request has an associated context, and false if it does not.
// Get will only return a valid context when called from inside the filter chain set up by NewRequestContextFilter()
func (c *requestContextMap) Get(req *http.Request) (Context, bool) {
c.lock.Lock()
defer c.lock.Unlock()
context, ok := c.contexts[req]
return context, ok
value, ok := c.getValue(req)
if !ok {
return nil, false
}
if context, ok := value.Load().(contextWrap); ok {
return context.Context, ok
}
return nil, false
}
// Update maps the request to the given context.
// If no context was previously associated with the request, an error is returned and the context is ignored.
func (c *requestContextMap) Update(req *http.Request, context Context) error {
c.lock.Lock()
defer c.lock.Unlock()
if _, ok := c.contexts[req]; !ok {
return errors.New("No context associated")
value, ok := c.getValue(req)
if !ok {
return errors.New("no context associated")
}
// TODO: ensure the new context is a descendant of the existing one
c.contexts[req] = context
wrapper, ok := value.Load().(contextWrap)
if !ok {
return errors.New("value type does not match")
}
wrapper.Context = context
value.Store(wrapper)
return nil
}
@ -83,7 +109,10 @@ func (c *requestContextMap) init(req *http.Request, context Context) bool {
if _, exists := c.contexts[req]; exists {
return false
}
c.contexts[req] = context
value := &atomic.Value{}
value.Store(contextWrap{context})
c.contexts[req] = value
return true
}