gls

package
v3.0.2+incompatible Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 16, 2016 License: MIT, Apache-2.0 Imports: 3 Imported by: 0

README

gls

Goroutine local storage

Huhwaht? Why?

Every so often, a thread shows up on the golang-nuts asking for some form of goroutine-local-storage, or some kind of goroutine id, or some kind of context. There are a few valid use cases for goroutine-local-storage, one of the most prominent being log line context. One poster was interested in being able to log an HTTP request context id in every log line in the same goroutine as the incoming HTTP request, without having to change every library and function call he was interested in logging.

This would be pretty useful. Provided that you could get some kind of goroutine-local-storage, you could call log.SetOutput with your own logging writer that checks goroutine-local-storage for some context information and adds that context to your log lines.

But alas, Andrew Gerrand's typically diplomatic answer to the question of goroutine-local variables was:

We wouldn't even be having this discussion if thread local storage wasn't useful. But every feature comes at a cost, and in my opinion the cost of threadlocals far outweighs their benefits. They're just not a good fit for Go.

So, yeah, that makes sense. That's a pretty good reason for why the language won't support a specific and (relatively) unuseful feature that requires some runtime changes, just for the sake of a little bit of log improvement.

But does Go require runtime changes?

How it works

Go has pretty fantastic introspective and reflective features, but one thing Go doesn't give you is any kind of access to the stack pointer, or frame pointer, or goroutine id, or anything contextual about your current stack. It gives you access to your list of callers, but only along with program counters, which are fixed at compile time.

But it does give you the stack.

So, we define 16 special functions and embed base-16 tags into the stack using the call order of those 16 functions. Then, we can read our tags back out of the stack looking at the callers list.

We then use these tags as an index into a traditional map for implementing this library.

What are people saying?

"Wow, that's horrifying."

"This is the most terrible thing I have seen in a very long time."

"Where is it getting a context from? Is this serializing all the requests? What the heck is the client being bound to? What are these tags? Why does he need callers? Oh god no. No no no."

Docs

Please see the docs at http://godoc.org/github.com/jtolds/gls

Documentation

Overview

Package gls implements goroutine-local storage.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Go

func Go(cb func())

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.

Example
var (
	mgr            = NewContextManager()
	request_id_key = GenSym()
)

MyLog := func() {
	if request_id, ok := mgr.GetValue(request_id_key); ok {
		fmt.Println("My request id is:", request_id)
	} else {
		fmt.Println("No request id found")
	}
}

mgr.SetValues(Values{request_id_key: "12345"}, func() {
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		MyLog()
	}()
	wg.Wait()
	wg.Add(1)
	Go(func() {
		defer wg.Done()
		MyLog()
	})
	wg.Wait()
})
Output:

No request id found
My request id is: 12345

Types

type ContextKey

type ContextKey struct {
	// contains filtered or unexported fields
}

ContextKey is a throwaway value you can use as a key to a ContextManager

func GenSym

func GenSym() ContextKey

GenSym will return a brand new, never-before-used ContextKey

type ContextManager

type ContextManager struct {
	// contains filtered or unexported fields
}

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.

func NewContextManager

func NewContextManager() *ContextManager

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 (*ContextManager) GetValue

func (m *ContextManager) GetValue(key interface{}) (value interface{}, ok bool)

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 (*ContextManager) SetValues

func (m *ContextManager) SetValues(new_values Values, context_call func())

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.

Example
var (
	mgr            = NewContextManager()
	request_id_key = GenSym()
)

MyLog := func() {
	if request_id, ok := mgr.GetValue(request_id_key); ok {
		fmt.Println("My request id is:", request_id)
	} else {
		fmt.Println("No request id found")
	}
}

mgr.SetValues(Values{request_id_key: "12345"}, func() {
	MyLog()
})
MyLog()
Output:

My request id is: 12345
No request id found

func (*ContextManager) Unregister

func (m *ContextManager) Unregister()

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.

type Values

type Values map[interface{}]interface{}

Values is simply a map of key types to value types. Used by SetValues to set multiple values at once.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL