├── gen_sym.go ├── stack_tags_main.go ├── id_pool.go ├── gid.go ├── LICENSE ├── stack_tags_js.go ├── context_test.go ├── README.md ├── stack_tags.go └── context.go /gen_sym.go: -------------------------------------------------------------------------------- 1 | package gls 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | var ( 8 | keyMtx sync.Mutex 9 | keyCounter uint64 10 | ) 11 | 12 | // ContextKey is a throwaway value you can use as a key to a ContextManager 13 | type ContextKey struct{ id uint64 } 14 | 15 | // GenSym will return a brand new, never-before-used ContextKey 16 | func GenSym() ContextKey { 17 | keyMtx.Lock() 18 | defer keyMtx.Unlock() 19 | keyCounter += 1 20 | return ContextKey{id: keyCounter} 21 | } 22 | -------------------------------------------------------------------------------- /stack_tags_main.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package gls 4 | 5 | // This file is used for standard Go builds, which have the expected runtime 6 | // support 7 | 8 | import ( 9 | "runtime" 10 | ) 11 | 12 | var ( 13 | findPtr = func() uintptr { 14 | var pc [1]uintptr 15 | n := runtime.Callers(4, pc[:]) 16 | if n != 1 { 17 | panic("failed to find function pointer") 18 | } 19 | return pc[0] 20 | } 21 | 22 | getStack = func(offset, amount int) (stack []uintptr, next_offset int) { 23 | stack = make([]uintptr, amount) 24 | stack = stack[:runtime.Callers(offset, stack)] 25 | if len(stack) < amount { 26 | return stack, 0 27 | } 28 | return stack, offset + len(stack) 29 | } 30 | ) 31 | -------------------------------------------------------------------------------- /id_pool.go: -------------------------------------------------------------------------------- 1 | package gls 2 | 3 | // though this could probably be better at keeping ids smaller, the goal of 4 | // this class is to keep a registry of the smallest unique integer ids 5 | // per-process possible 6 | 7 | import ( 8 | "sync" 9 | ) 10 | 11 | type idPool struct { 12 | mtx sync.Mutex 13 | released []uint 14 | max_id uint 15 | } 16 | 17 | func (p *idPool) Acquire() (id uint) { 18 | p.mtx.Lock() 19 | defer p.mtx.Unlock() 20 | if len(p.released) > 0 { 21 | id = p.released[len(p.released)-1] 22 | p.released = p.released[:len(p.released)-1] 23 | return id 24 | } 25 | id = p.max_id 26 | p.max_id++ 27 | return id 28 | } 29 | 30 | func (p *idPool) Release(id uint) { 31 | p.mtx.Lock() 32 | defer p.mtx.Unlock() 33 | p.released = append(p.released, id) 34 | } 35 | -------------------------------------------------------------------------------- /gid.go: -------------------------------------------------------------------------------- 1 | package gls 2 | 3 | var ( 4 | stackTagPool = &idPool{} 5 | ) 6 | 7 | // Will return this goroutine's identifier if set. If you always need a 8 | // goroutine identifier, you should use EnsureGoroutineId which will make one 9 | // if there isn't one already. 10 | func GetGoroutineId() (gid uint, ok bool) { 11 | return readStackTag() 12 | } 13 | 14 | // Will call cb with the current goroutine identifier. If one hasn't already 15 | // been generated, one will be created and set first. The goroutine identifier 16 | // might be invalid after cb returns. 17 | func EnsureGoroutineId(cb func(gid uint)) { 18 | if gid, ok := readStackTag(); ok { 19 | cb(gid) 20 | return 21 | } 22 | gid := stackTagPool.Acquire() 23 | defer stackTagPool.Release(gid) 24 | addStackTag(gid, func() { cb(gid) }) 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Space Monkey, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /stack_tags_js.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package gls 4 | 5 | // This file is used for GopherJS builds, which don't have normal runtime 6 | // stack trace support 7 | 8 | import ( 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/gopherjs/gopherjs/js" 13 | ) 14 | 15 | const ( 16 | jsFuncNamePrefix = "github_com_jtolds_gls_mark" 17 | ) 18 | 19 | func jsMarkStack() (f []uintptr) { 20 | lines := strings.Split( 21 | js.Global.Get("Error").New().Get("stack").String(), "\n") 22 | f = make([]uintptr, 0, len(lines)) 23 | for i, line := range lines { 24 | line = strings.TrimSpace(line) 25 | if line == "" { 26 | continue 27 | } 28 | if i == 0 { 29 | if line != "Error" { 30 | panic("didn't understand js stack trace") 31 | } 32 | continue 33 | } 34 | fields := strings.Fields(line) 35 | if len(fields) < 2 || fields[0] != "at" { 36 | panic("didn't understand js stack trace") 37 | } 38 | 39 | pos := strings.Index(fields[1], jsFuncNamePrefix) 40 | if pos < 0 { 41 | continue 42 | } 43 | pos += len(jsFuncNamePrefix) 44 | if pos >= len(fields[1]) { 45 | panic("didn't understand js stack trace") 46 | } 47 | char := string(fields[1][pos]) 48 | switch char { 49 | case "S": 50 | f = append(f, uintptr(0)) 51 | default: 52 | val, err := strconv.ParseUint(char, 16, 8) 53 | if err != nil { 54 | panic("didn't understand js stack trace") 55 | } 56 | f = append(f, uintptr(val)+1) 57 | } 58 | } 59 | return f 60 | } 61 | 62 | // variables to prevent inlining 63 | var ( 64 | findPtr = func() uintptr { 65 | funcs := jsMarkStack() 66 | if len(funcs) == 0 { 67 | panic("failed to find function pointer") 68 | } 69 | return funcs[0] 70 | } 71 | 72 | getStack = func(offset, amount int) (stack []uintptr, next_offset int) { 73 | return jsMarkStack(), 0 74 | } 75 | ) 76 | -------------------------------------------------------------------------------- /context_test.go: -------------------------------------------------------------------------------- 1 | package gls_test 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "testing" 7 | 8 | "github.com/jtolds/gls" 9 | ) 10 | 11 | func TestContexts(t *testing.T) { 12 | mgr1 := gls.NewContextManager() 13 | mgr2 := gls.NewContextManager() 14 | 15 | CheckVal := func(mgr *gls.ContextManager, key, exp_val string) { 16 | val, ok := mgr.GetValue(key) 17 | if len(exp_val) == 0 { 18 | if ok { 19 | t.Fatalf("expected no value for key %s, got %s", key, val) 20 | } 21 | return 22 | } 23 | if !ok { 24 | t.Fatalf("expected value %s for key %s, got no value", 25 | exp_val, key) 26 | } 27 | if exp_val != val { 28 | t.Fatalf("expected value %s for key %s, got %s", exp_val, key, 29 | val) 30 | } 31 | 32 | } 33 | 34 | Check := func(exp_m1v1, exp_m1v2, exp_m2v1, exp_m2v2 string) { 35 | CheckVal(mgr1, "key1", exp_m1v1) 36 | CheckVal(mgr1, "key2", exp_m1v2) 37 | CheckVal(mgr2, "key1", exp_m2v1) 38 | CheckVal(mgr2, "key2", exp_m2v2) 39 | } 40 | 41 | Check("", "", "", "") 42 | mgr2.SetValues(gls.Values{"key1": "val1c"}, func() { 43 | Check("", "", "val1c", "") 44 | mgr1.SetValues(gls.Values{"key1": "val1a"}, func() { 45 | Check("val1a", "", "val1c", "") 46 | mgr1.SetValues(gls.Values{"key2": "val1b"}, func() { 47 | Check("val1a", "val1b", "val1c", "") 48 | var wg sync.WaitGroup 49 | wg.Add(2) 50 | go func() { 51 | defer wg.Done() 52 | Check("", "", "", "") 53 | }() 54 | gls.Go(func() { 55 | defer wg.Done() 56 | Check("val1a", "val1b", "val1c", "") 57 | }) 58 | wg.Wait() 59 | Check("val1a", "val1b", "val1c", "") 60 | }) 61 | Check("val1a", "", "val1c", "") 62 | }) 63 | Check("", "", "val1c", "") 64 | }) 65 | Check("", "", "", "") 66 | } 67 | 68 | func ExampleContextManager_SetValues() { 69 | var ( 70 | mgr = gls.NewContextManager() 71 | request_id_key = gls.GenSym() 72 | ) 73 | 74 | MyLog := func() { 75 | if request_id, ok := mgr.GetValue(request_id_key); ok { 76 | fmt.Println("My request id is:", request_id) 77 | } else { 78 | fmt.Println("No request id found") 79 | } 80 | } 81 | 82 | mgr.SetValues(gls.Values{request_id_key: "12345"}, func() { 83 | MyLog() 84 | }) 85 | MyLog() 86 | 87 | // Output: My request id is: 12345 88 | // No request id found 89 | } 90 | 91 | func ExampleGo() { 92 | var ( 93 | mgr = gls.NewContextManager() 94 | request_id_key = gls.GenSym() 95 | ) 96 | 97 | MyLog := func() { 98 | if request_id, ok := mgr.GetValue(request_id_key); ok { 99 | fmt.Println("My request id is:", request_id) 100 | } else { 101 | fmt.Println("No request id found") 102 | } 103 | } 104 | 105 | mgr.SetValues(gls.Values{request_id_key: "12345"}, func() { 106 | var wg sync.WaitGroup 107 | wg.Add(1) 108 | go func() { 109 | defer wg.Done() 110 | MyLog() 111 | }() 112 | wg.Wait() 113 | wg.Add(1) 114 | gls.Go(func() { 115 | defer wg.Done() 116 | MyLog() 117 | }) 118 | wg.Wait() 119 | }) 120 | 121 | // Output: No request id found 122 | // My request id is: 12345 123 | } 124 | 125 | func BenchmarkGetValue(b *testing.B) { 126 | mgr := gls.NewContextManager() 127 | mgr.SetValues(gls.Values{"test_key": "test_val"}, func() { 128 | b.ResetTimer() 129 | for i := 0; i < b.N; i++ { 130 | val, ok := mgr.GetValue("test_key") 131 | if !ok || val != "test_val" { 132 | b.FailNow() 133 | } 134 | } 135 | }) 136 | } 137 | 138 | func BenchmarkSetValues(b *testing.B) { 139 | mgr := gls.NewContextManager() 140 | for i := 0; i < b.N/2; i++ { 141 | mgr.SetValues(gls.Values{"test_key": "test_val"}, func() { 142 | mgr.SetValues(gls.Values{"test_key2": "test_val2"}, func() {}) 143 | }) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gls 2 | === 3 | 4 | Goroutine local storage 5 | 6 | ### IMPORTANT NOTE ### 7 | 8 | It is my duty to point you to https://blog.golang.org/context, which is how 9 | Google solves all of the problems you'd perhaps consider using this package 10 | for at scale. 11 | 12 | One downside to Google's approach is that *all* of your functions must have 13 | a new first argument, but after clearing that hurdle everything else is much 14 | better. 15 | 16 | If you aren't interested in this warning, read on. 17 | 18 | ### Huhwaht? Why? ### 19 | 20 | Every so often, a thread shows up on the 21 | [golang-nuts](https://groups.google.com/d/forum/golang-nuts) asking for some 22 | form of goroutine-local-storage, or some kind of goroutine id, or some kind of 23 | context. There are a few valid use cases for goroutine-local-storage, one of 24 | the most prominent being log line context. One poster was interested in being 25 | able to log an HTTP request context id in every log line in the same goroutine 26 | as the incoming HTTP request, without having to change every library and 27 | function call he was interested in logging. 28 | 29 | This would be pretty useful. Provided that you could get some kind of 30 | goroutine-local-storage, you could call 31 | [log.SetOutput](http://golang.org/pkg/log/#SetOutput) with your own logging 32 | writer that checks goroutine-local-storage for some context information and 33 | adds that context to your log lines. 34 | 35 | But alas, Andrew Gerrand's typically diplomatic answer to the question of 36 | goroutine-local variables was: 37 | 38 | > We wouldn't even be having this discussion if thread local storage wasn't 39 | > useful. But every feature comes at a cost, and in my opinion the cost of 40 | > threadlocals far outweighs their benefits. They're just not a good fit for 41 | > Go. 42 | 43 | So, yeah, that makes sense. That's a pretty good reason for why the language 44 | won't support a specific and (relatively) unuseful feature that requires some 45 | runtime changes, just for the sake of a little bit of log improvement. 46 | 47 | But does Go require runtime changes? 48 | 49 | ### How it works ### 50 | 51 | Go has pretty fantastic introspective and reflective features, but one thing Go 52 | doesn't give you is any kind of access to the stack pointer, or frame pointer, 53 | or goroutine id, or anything contextual about your current stack. It gives you 54 | access to your list of callers, but only along with program counters, which are 55 | fixed at compile time. 56 | 57 | But it does give you the stack. 58 | 59 | So, we define 16 special functions and embed base-16 tags into the stack using 60 | the call order of those 16 functions. Then, we can read our tags back out of 61 | the stack looking at the callers list. 62 | 63 | We then use these tags as an index into a traditional map for implementing 64 | this library. 65 | 66 | ### What are people saying? ### 67 | 68 | "Wow, that's horrifying." 69 | 70 | "This is the most terrible thing I have seen in a very long time." 71 | 72 | "Where is it getting a context from? Is this serializing all the requests? 73 | What the heck is the client being bound to? What are these tags? Why does he 74 | need callers? Oh god no. No no no." 75 | 76 | ### Docs ### 77 | 78 | Please see the docs at http://godoc.org/github.com/jtolds/gls 79 | 80 | ### Related ### 81 | 82 | If you're okay relying on the string format of the current runtime stacktrace 83 | including a unique goroutine id (not guaranteed by the spec or anything, but 84 | very unlikely to change within a Go release), you might be able to squeeze 85 | out a bit more performance by using this similar library, inspired by some 86 | code Brad Fitzpatrick wrote for debugging his HTTP/2 library: 87 | https://github.com/tylerb/gls (in contrast, jtolds/gls doesn't require 88 | any knowledge of the string format of the runtime stacktrace, which 89 | probably adds unnecessary overhead). 90 | -------------------------------------------------------------------------------- /stack_tags.go: -------------------------------------------------------------------------------- 1 | package gls 2 | 3 | // so, basically, we're going to encode integer tags in base-16 on the stack 4 | 5 | const ( 6 | bitWidth = 4 7 | stackBatchSize = 16 8 | ) 9 | 10 | var ( 11 | pc_lookup = make(map[uintptr]int8, 17) 12 | mark_lookup [16]func(uint, func()) 13 | ) 14 | 15 | func init() { 16 | setEntries := func(f func(uint, func()), v int8) { 17 | var ptr uintptr 18 | f(0, func() { 19 | ptr = findPtr() 20 | }) 21 | pc_lookup[ptr] = v 22 | if v >= 0 { 23 | mark_lookup[v] = f 24 | } 25 | } 26 | setEntries(github_com_jtolds_gls_markS, -0x1) 27 | setEntries(github_com_jtolds_gls_mark0, 0x0) 28 | setEntries(github_com_jtolds_gls_mark1, 0x1) 29 | setEntries(github_com_jtolds_gls_mark2, 0x2) 30 | setEntries(github_com_jtolds_gls_mark3, 0x3) 31 | setEntries(github_com_jtolds_gls_mark4, 0x4) 32 | setEntries(github_com_jtolds_gls_mark5, 0x5) 33 | setEntries(github_com_jtolds_gls_mark6, 0x6) 34 | setEntries(github_com_jtolds_gls_mark7, 0x7) 35 | setEntries(github_com_jtolds_gls_mark8, 0x8) 36 | setEntries(github_com_jtolds_gls_mark9, 0x9) 37 | setEntries(github_com_jtolds_gls_markA, 0xa) 38 | setEntries(github_com_jtolds_gls_markB, 0xb) 39 | setEntries(github_com_jtolds_gls_markC, 0xc) 40 | setEntries(github_com_jtolds_gls_markD, 0xd) 41 | setEntries(github_com_jtolds_gls_markE, 0xe) 42 | setEntries(github_com_jtolds_gls_markF, 0xf) 43 | } 44 | 45 | func addStackTag(tag uint, context_call func()) { 46 | if context_call == nil { 47 | return 48 | } 49 | github_com_jtolds_gls_markS(tag, context_call) 50 | } 51 | 52 | // these private methods are named this horrendous name so gopherjs support 53 | // is easier. it shouldn't add any runtime cost in non-js builds. 54 | 55 | //go:noinline 56 | func github_com_jtolds_gls_markS(tag uint, cb func()) { _m(tag, cb) } 57 | 58 | //go:noinline 59 | func github_com_jtolds_gls_mark0(tag uint, cb func()) { _m(tag, cb) } 60 | 61 | //go:noinline 62 | func github_com_jtolds_gls_mark1(tag uint, cb func()) { _m(tag, cb) } 63 | 64 | //go:noinline 65 | func github_com_jtolds_gls_mark2(tag uint, cb func()) { _m(tag, cb) } 66 | 67 | //go:noinline 68 | func github_com_jtolds_gls_mark3(tag uint, cb func()) { _m(tag, cb) } 69 | 70 | //go:noinline 71 | func github_com_jtolds_gls_mark4(tag uint, cb func()) { _m(tag, cb) } 72 | 73 | //go:noinline 74 | func github_com_jtolds_gls_mark5(tag uint, cb func()) { _m(tag, cb) } 75 | 76 | //go:noinline 77 | func github_com_jtolds_gls_mark6(tag uint, cb func()) { _m(tag, cb) } 78 | 79 | //go:noinline 80 | func github_com_jtolds_gls_mark7(tag uint, cb func()) { _m(tag, cb) } 81 | 82 | //go:noinline 83 | func github_com_jtolds_gls_mark8(tag uint, cb func()) { _m(tag, cb) } 84 | 85 | //go:noinline 86 | func github_com_jtolds_gls_mark9(tag uint, cb func()) { _m(tag, cb) } 87 | 88 | //go:noinline 89 | func github_com_jtolds_gls_markA(tag uint, cb func()) { _m(tag, cb) } 90 | 91 | //go:noinline 92 | func github_com_jtolds_gls_markB(tag uint, cb func()) { _m(tag, cb) } 93 | 94 | //go:noinline 95 | func github_com_jtolds_gls_markC(tag uint, cb func()) { _m(tag, cb) } 96 | 97 | //go:noinline 98 | func github_com_jtolds_gls_markD(tag uint, cb func()) { _m(tag, cb) } 99 | 100 | //go:noinline 101 | func github_com_jtolds_gls_markE(tag uint, cb func()) { _m(tag, cb) } 102 | 103 | //go:noinline 104 | func github_com_jtolds_gls_markF(tag uint, cb func()) { _m(tag, cb) } 105 | 106 | func _m(tag_remainder uint, cb func()) { 107 | if tag_remainder == 0 { 108 | cb() 109 | } else { 110 | mark_lookup[tag_remainder&0xf](tag_remainder>>bitWidth, cb) 111 | } 112 | } 113 | 114 | func readStackTag() (tag uint, ok bool) { 115 | var current_tag uint 116 | offset := 0 117 | for { 118 | batch, next_offset := getStack(offset, stackBatchSize) 119 | for _, pc := range batch { 120 | val, ok := pc_lookup[pc] 121 | if !ok { 122 | continue 123 | } 124 | if val < 0 { 125 | return current_tag, true 126 | } 127 | current_tag <<= bitWidth 128 | current_tag += uint(val) 129 | } 130 | if next_offset == 0 { 131 | break 132 | } 133 | offset = next_offset 134 | } 135 | return 0, false 136 | } 137 | 138 | func (m *ContextManager) preventInlining() { 139 | // dunno if findPtr or getStack are likely to get inlined in a future release 140 | // of go, but if they are inlined and their callers are inlined, that could 141 | // hork some things. let's do our best to explain to the compiler that we 142 | // really don't want those two functions inlined by saying they could change 143 | // at any time. assumes preventInlining doesn't get compiled out. 144 | // this whole thing is probably overkill. 145 | findPtr = m.values[0][0].(func() uintptr) 146 | getStack = m.values[0][1].(func(int, int) ([]uintptr, int)) 147 | } 148 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | // Package gls implements goroutine-local storage. 2 | package gls 3 | 4 | import ( 5 | "sync" 6 | ) 7 | 8 | var ( 9 | mgrRegistry = make(map[*ContextManager]bool) 10 | mgrRegistryMtx sync.RWMutex 11 | ) 12 | 13 | // Values is simply a map of key types to value types. Used by SetValues to 14 | // set multiple values at once. 15 | type Values map[interface{}]interface{} 16 | 17 | // ContextManager is the main entrypoint for interacting with 18 | // Goroutine-local-storage. You can have multiple independent ContextManagers 19 | // at any given time. ContextManagers are usually declared globally for a given 20 | // class of context variables. You should use NewContextManager for 21 | // construction. 22 | type ContextManager struct { 23 | mtx sync.Mutex 24 | values map[uint]Values 25 | } 26 | 27 | // NewContextManager returns a brand new ContextManager. It also registers the 28 | // new ContextManager in the ContextManager registry which is used by the Go 29 | // method. ContextManagers are typically defined globally at package scope. 30 | func NewContextManager() *ContextManager { 31 | mgr := &ContextManager{values: make(map[uint]Values)} 32 | mgrRegistryMtx.Lock() 33 | defer mgrRegistryMtx.Unlock() 34 | mgrRegistry[mgr] = true 35 | return mgr 36 | } 37 | 38 | // Unregister removes a ContextManager from the global registry, used by the 39 | // Go method. Only intended for use when you're completely done with a 40 | // ContextManager. Use of Unregister at all is rare. 41 | func (m *ContextManager) Unregister() { 42 | mgrRegistryMtx.Lock() 43 | defer mgrRegistryMtx.Unlock() 44 | delete(mgrRegistry, m) 45 | } 46 | 47 | // SetValues takes a collection of values and a function to call for those 48 | // values to be set in. Anything further down the stack will have the set 49 | // values available through GetValue. SetValues will add new values or replace 50 | // existing values of the same key and will not mutate or change values for 51 | // previous stack frames. 52 | // SetValues is slow (makes a copy of all current and new values for the new 53 | // gls-context) in order to reduce the amount of lookups GetValue requires. 54 | func (m *ContextManager) SetValues(new_values Values, context_call func()) { 55 | if len(new_values) == 0 { 56 | context_call() 57 | return 58 | } 59 | 60 | mutated_keys := make([]interface{}, 0, len(new_values)) 61 | mutated_vals := make(Values, len(new_values)) 62 | 63 | EnsureGoroutineId(func(gid uint) { 64 | m.mtx.Lock() 65 | state, found := m.values[gid] 66 | if !found { 67 | state = make(Values, len(new_values)) 68 | m.values[gid] = state 69 | } 70 | m.mtx.Unlock() 71 | 72 | for key, new_val := range new_values { 73 | mutated_keys = append(mutated_keys, key) 74 | if old_val, ok := state[key]; ok { 75 | mutated_vals[key] = old_val 76 | } 77 | state[key] = new_val 78 | } 79 | 80 | defer func() { 81 | if !found { 82 | m.mtx.Lock() 83 | delete(m.values, gid) 84 | m.mtx.Unlock() 85 | return 86 | } 87 | 88 | for _, key := range mutated_keys { 89 | if val, ok := mutated_vals[key]; ok { 90 | state[key] = val 91 | } else { 92 | delete(state, key) 93 | } 94 | } 95 | }() 96 | 97 | context_call() 98 | }) 99 | } 100 | 101 | // GetValue will return a previously set value, provided that the value was set 102 | // by SetValues somewhere higher up the stack. If the value is not found, ok 103 | // will be false. 104 | func (m *ContextManager) GetValue(key interface{}) ( 105 | value interface{}, ok bool) { 106 | gid, ok := GetGoroutineId() 107 | if !ok { 108 | return nil, false 109 | } 110 | 111 | m.mtx.Lock() 112 | state, found := m.values[gid] 113 | m.mtx.Unlock() 114 | 115 | if !found { 116 | return nil, false 117 | } 118 | value, ok = state[key] 119 | return value, ok 120 | } 121 | 122 | func (m *ContextManager) getValues() Values { 123 | gid, ok := GetGoroutineId() 124 | if !ok { 125 | return nil 126 | } 127 | m.mtx.Lock() 128 | state, _ := m.values[gid] 129 | m.mtx.Unlock() 130 | return state 131 | } 132 | 133 | // Go preserves ContextManager values and Goroutine-local-storage across new 134 | // goroutine invocations. The Go method makes a copy of all existing values on 135 | // all registered context managers and makes sure they are still set after 136 | // kicking off the provided function in a new goroutine. If you don't use this 137 | // Go method instead of the standard 'go' keyword, you will lose values in 138 | // ContextManagers, as goroutines have brand new stacks. 139 | func Go(cb func()) { 140 | mgrRegistryMtx.RLock() 141 | defer mgrRegistryMtx.RUnlock() 142 | 143 | for mgr := range mgrRegistry { 144 | values := mgr.getValues() 145 | if len(values) > 0 { 146 | cb = func(mgr *ContextManager, cb func()) func() { 147 | return func() { mgr.SetValues(values, cb) } 148 | }(mgr, cb) 149 | } 150 | } 151 | 152 | go cb() 153 | } 154 | --------------------------------------------------------------------------------