// 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() }