mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
144 lines
4.1 KiB
144 lines
4.1 KiB
// Package gls implements goroutine-local storage. |
|
package gls |
|
|
|
import ( |
|
"sync" |
|
) |
|
|
|
const ( |
|
maxCallers = 64 |
|
) |
|
|
|
var ( |
|
stackTagPool = &idPool{} |
|
mgrRegistry = make(map[*ContextManager]bool) |
|
mgrRegistryMtx sync.RWMutex |
|
) |
|
|
|
// Values is simply a map of key types to value types. Used by SetValues to |
|
// set multiple values at once. |
|
type Values map[interface{}]interface{} |
|
|
|
// ContextManager is the main entrypoint for interacting with |
|
// Goroutine-local-storage. You can have multiple independent ContextManagers |
|
// at any given time. ContextManagers are usually declared globally for a given |
|
// class of context variables. You should use NewContextManager for |
|
// construction. |
|
type ContextManager struct { |
|
mtx sync.RWMutex |
|
values map[uint]Values |
|
} |
|
|
|
// NewContextManager returns a brand new ContextManager. It also registers the |
|
// new ContextManager in the ContextManager registry which is used by the Go |
|
// method. ContextManagers are typically defined globally at package scope. |
|
func NewContextManager() *ContextManager { |
|
mgr := &ContextManager{values: make(map[uint]Values)} |
|
mgrRegistryMtx.Lock() |
|
defer mgrRegistryMtx.Unlock() |
|
mgrRegistry[mgr] = true |
|
return mgr |
|
} |
|
|
|
// Unregister removes a ContextManager from the global registry, used by the |
|
// Go method. Only intended for use when you're completely done with a |
|
// ContextManager. Use of Unregister at all is rare. |
|
func (m *ContextManager) Unregister() { |
|
mgrRegistryMtx.Lock() |
|
defer mgrRegistryMtx.Unlock() |
|
delete(mgrRegistry, m) |
|
} |
|
|
|
// SetValues takes a collection of values and a function to call for those |
|
// values to be set in. Anything further down the stack will have the set |
|
// values available through GetValue. SetValues will add new values or replace |
|
// existing values of the same key and will not mutate or change values for |
|
// previous stack frames. |
|
// SetValues is slow (makes a copy of all current and new values for the new |
|
// gls-context) in order to reduce the amount of lookups GetValue requires. |
|
func (m *ContextManager) SetValues(new_values Values, context_call func()) { |
|
if len(new_values) == 0 { |
|
context_call() |
|
return |
|
} |
|
|
|
tags := readStackTags(1) |
|
|
|
m.mtx.Lock() |
|
values := new_values |
|
for _, tag := range tags { |
|
if existing_values, ok := m.values[tag]; ok { |
|
// oh, we found existing values, let's make a copy |
|
values = make(Values, len(existing_values)+len(new_values)) |
|
for key, val := range existing_values { |
|
values[key] = val |
|
} |
|
for key, val := range new_values { |
|
values[key] = val |
|
} |
|
break |
|
} |
|
} |
|
new_tag := stackTagPool.Acquire() |
|
m.values[new_tag] = values |
|
m.mtx.Unlock() |
|
defer func() { |
|
m.mtx.Lock() |
|
delete(m.values, new_tag) |
|
m.mtx.Unlock() |
|
stackTagPool.Release(new_tag) |
|
}() |
|
|
|
addStackTag(new_tag, context_call) |
|
} |
|
|
|
// GetValue will return a previously set value, provided that the value was set |
|
// by SetValues somewhere higher up the stack. If the value is not found, ok |
|
// will be false. |
|
func (m *ContextManager) GetValue(key interface{}) (value interface{}, ok bool) { |
|
|
|
tags := readStackTags(1) |
|
m.mtx.RLock() |
|
defer m.mtx.RUnlock() |
|
for _, tag := range tags { |
|
if values, ok := m.values[tag]; ok { |
|
value, ok := values[key] |
|
return value, ok |
|
} |
|
} |
|
return "", false |
|
} |
|
|
|
func (m *ContextManager) getValues() Values { |
|
tags := readStackTags(2) |
|
m.mtx.RLock() |
|
defer m.mtx.RUnlock() |
|
for _, tag := range tags { |
|
if values, ok := m.values[tag]; ok { |
|
return values |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
// Go preserves ContextManager values and Goroutine-local-storage across new |
|
// goroutine invocations. The Go method makes a copy of all existing values on |
|
// all registered context managers and makes sure they are still set after |
|
// kicking off the provided function in a new goroutine. If you don't use this |
|
// Go method instead of the standard 'go' keyword, you will lose values in |
|
// ContextManagers, as goroutines have brand new stacks. |
|
func Go(cb func()) { |
|
mgrRegistryMtx.RLock() |
|
defer mgrRegistryMtx.RUnlock() |
|
|
|
for mgr, _ := range mgrRegistry { |
|
values := mgr.getValues() |
|
if len(values) > 0 { |
|
mgr_copy := mgr |
|
cb_copy := cb |
|
cb = func() { mgr_copy.SetValues(values, cb_copy) } |
|
} |
|
} |
|
|
|
go cb() |
|
}
|
|
|