Skip to content

Commit bb0351a

Browse files
committed
completely redo internals
a bunch of benefits: * no limit on stack size * now only adds stack tags once per goroutine * improved gopherjs support * expose GoroutineId values.
1 parent 8ddce2a commit bb0351a

7 files changed

+271
-222
lines changed

context.go

+62-53
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,7 @@ import (
55
"sync"
66
)
77

8-
const (
9-
maxCallers = 64
10-
)
11-
128
var (
13-
stackTagPool = &idPool{}
149
mgrRegistry = make(map[*ContextManager]bool)
1510
mgrRegistryMtx sync.RWMutex
1611
)
@@ -25,7 +20,7 @@ type Values map[interface{}]interface{}
2520
// class of context variables. You should use NewContextManager for
2621
// construction.
2722
type ContextManager struct {
28-
mtx sync.RWMutex
23+
mtx sync.Mutex
2924
values map[uint]Values
3025
}
3126

@@ -62,63 +57,77 @@ func (m *ContextManager) SetValues(new_values Values, context_call func()) {
6257
return
6358
}
6459

65-
tags := readStackTags(1)
60+
mutated_keys := make([]interface{}, 0, len(new_values))
61+
mutated_vals := make(Values, len(new_values))
6662

67-
m.mtx.Lock()
68-
values := new_values
69-
for _, tag := range tags {
70-
if existing_values, ok := m.values[tag]; ok {
71-
// oh, we found existing values, let's make a copy
72-
values = make(Values, len(existing_values)+len(new_values))
73-
for key, val := range existing_values {
74-
values[key] = val
75-
}
76-
for key, val := range new_values {
77-
values[key] = val
78-
}
79-
break
80-
}
81-
}
82-
new_tag := stackTagPool.Acquire()
83-
m.values[new_tag] = values
84-
m.mtx.Unlock()
85-
defer func() {
63+
EnsureGoroutineId(func(gid uint) {
8664
m.mtx.Lock()
87-
delete(m.values, new_tag)
65+
state, found := m.values[gid]
66+
if !found {
67+
state = make(Values, len(new_values))
68+
m.values[gid] = state
69+
}
8870
m.mtx.Unlock()
89-
stackTagPool.Release(new_tag)
90-
}()
9171

92-
addStackTag(new_tag, context_call)
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+
})
9399
}
94100

95101
// GetValue will return a previously set value, provided that the value was set
96102
// by SetValues somewhere higher up the stack. If the value is not found, ok
97103
// will be false.
98-
func (m *ContextManager) GetValue(key interface{}) (value interface{}, ok bool) {
99-
100-
tags := readStackTags(1)
101-
m.mtx.RLock()
102-
defer m.mtx.RUnlock()
103-
for _, tag := range tags {
104-
if values, ok := m.values[tag]; ok {
105-
value, ok := values[key]
106-
return value, ok
107-
}
104+
func (m *ContextManager) GetValue(key interface{}) (
105+
value interface{}, ok bool) {
106+
gid, ok := GetGoroutineId()
107+
if !ok {
108+
return nil, false
108109
}
109-
return "", false
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
110120
}
111121

112122
func (m *ContextManager) getValues() Values {
113-
tags := readStackTags(2)
114-
m.mtx.RLock()
115-
defer m.mtx.RUnlock()
116-
for _, tag := range tags {
117-
if values, ok := m.values[tag]; ok {
118-
return values
119-
}
123+
gid, ok := GetGoroutineId()
124+
if !ok {
125+
return nil
120126
}
121-
return nil
127+
m.mtx.Lock()
128+
state, _ := m.values[gid]
129+
m.mtx.Unlock()
130+
return state
122131
}
123132

124133
// Go preserves ContextManager values and Goroutine-local-storage across new
@@ -131,12 +140,12 @@ func Go(cb func()) {
131140
mgrRegistryMtx.RLock()
132141
defer mgrRegistryMtx.RUnlock()
133142

134-
for mgr, _ := range mgrRegistry {
143+
for mgr := range mgrRegistry {
135144
values := mgr.getValues()
136145
if len(values) > 0 {
137-
mgr_copy := mgr
138-
cb_copy := cb
139-
cb = func() { mgr_copy.SetValues(values, cb_copy) }
146+
cb = func(mgr *ContextManager, cb func()) func() {
147+
return func() { mgr.SetValues(values, cb) }
148+
}(mgr, cb)
140149
}
141150
}
142151

context_test.go

+26-20
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
package gls
1+
package gls_test
22

33
import (
44
"fmt"
55
"sync"
66
"testing"
7+
8+
"github.com/jtolds/gls"
79
)
810

911
func TestContexts(t *testing.T) {
10-
mgr1 := NewContextManager()
11-
mgr2 := NewContextManager()
12+
mgr1 := gls.NewContextManager()
13+
mgr2 := gls.NewContextManager()
1214

13-
CheckVal := func(mgr *ContextManager, key, exp_val string) {
15+
CheckVal := func(mgr *gls.ContextManager, key, exp_val string) {
1416
val, ok := mgr.GetValue(key)
1517
if len(exp_val) == 0 {
1618
if ok {
@@ -37,32 +39,36 @@ func TestContexts(t *testing.T) {
3739
}
3840

3941
Check("", "", "", "")
40-
mgr2.SetValues(Values{"key1": "val1c"}, func() {
42+
mgr2.SetValues(gls.Values{"key1": "val1c"}, func() {
4143
Check("", "", "val1c", "")
42-
mgr1.SetValues(Values{"key1": "val1a"}, func() {
44+
mgr1.SetValues(gls.Values{"key1": "val1a"}, func() {
4345
Check("val1a", "", "val1c", "")
44-
mgr1.SetValues(Values{"key2": "val1b"}, func() {
46+
mgr1.SetValues(gls.Values{"key2": "val1b"}, func() {
4547
Check("val1a", "val1b", "val1c", "")
4648
var wg sync.WaitGroup
4749
wg.Add(2)
4850
go func() {
4951
defer wg.Done()
5052
Check("", "", "", "")
5153
}()
52-
Go(func() {
54+
gls.Go(func() {
5355
defer wg.Done()
5456
Check("val1a", "val1b", "val1c", "")
5557
})
5658
wg.Wait()
59+
Check("val1a", "val1b", "val1c", "")
5760
})
61+
Check("val1a", "", "val1c", "")
5862
})
63+
Check("", "", "val1c", "")
5964
})
65+
Check("", "", "", "")
6066
}
6167

6268
func ExampleContextManager_SetValues() {
6369
var (
64-
mgr = NewContextManager()
65-
request_id_key = GenSym()
70+
mgr = gls.NewContextManager()
71+
request_id_key = gls.GenSym()
6672
)
6773

6874
MyLog := func() {
@@ -73,7 +79,7 @@ func ExampleContextManager_SetValues() {
7379
}
7480
}
7581

76-
mgr.SetValues(Values{request_id_key: "12345"}, func() {
82+
mgr.SetValues(gls.Values{request_id_key: "12345"}, func() {
7783
MyLog()
7884
})
7985
MyLog()
@@ -84,8 +90,8 @@ func ExampleContextManager_SetValues() {
8490

8591
func ExampleGo() {
8692
var (
87-
mgr = NewContextManager()
88-
request_id_key = GenSym()
93+
mgr = gls.NewContextManager()
94+
request_id_key = gls.GenSym()
8995
)
9096

9197
MyLog := func() {
@@ -96,7 +102,7 @@ func ExampleGo() {
96102
}
97103
}
98104

99-
mgr.SetValues(Values{request_id_key: "12345"}, func() {
105+
mgr.SetValues(gls.Values{request_id_key: "12345"}, func() {
100106
var wg sync.WaitGroup
101107
wg.Add(1)
102108
go func() {
@@ -105,7 +111,7 @@ func ExampleGo() {
105111
}()
106112
wg.Wait()
107113
wg.Add(1)
108-
Go(func() {
114+
gls.Go(func() {
109115
defer wg.Done()
110116
MyLog()
111117
})
@@ -117,8 +123,8 @@ func ExampleGo() {
117123
}
118124

119125
func BenchmarkGetValue(b *testing.B) {
120-
mgr := NewContextManager()
121-
mgr.SetValues(Values{"test_key": "test_val"}, func() {
126+
mgr := gls.NewContextManager()
127+
mgr.SetValues(gls.Values{"test_key": "test_val"}, func() {
122128
b.ResetTimer()
123129
for i := 0; i < b.N; i++ {
124130
val, ok := mgr.GetValue("test_key")
@@ -130,10 +136,10 @@ func BenchmarkGetValue(b *testing.B) {
130136
}
131137

132138
func BenchmarkSetValues(b *testing.B) {
133-
mgr := NewContextManager()
139+
mgr := gls.NewContextManager()
134140
for i := 0; i < b.N/2; i++ {
135-
mgr.SetValues(Values{"test_key": "test_val"}, func() {
136-
mgr.SetValues(Values{"test_key2": "test_val2"}, func() {})
141+
mgr.SetValues(gls.Values{"test_key": "test_val"}, func() {
142+
mgr.SetValues(gls.Values{"test_key2": "test_val2"}, func() {})
137143
})
138144
}
139145
}

gen_sym.go

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
package gls
22

3+
import (
4+
"sync"
5+
)
6+
37
var (
4-
symPool = &idPool{}
8+
keyMtx sync.Mutex
9+
keyCounter uint64
510
)
611

712
// ContextKey is a throwaway value you can use as a key to a ContextManager
8-
type ContextKey struct{ id uint }
13+
type ContextKey struct{ id uint64 }
914

1015
// GenSym will return a brand new, never-before-used ContextKey
1116
func GenSym() ContextKey {
12-
return ContextKey{id: symPool.Acquire()}
17+
keyMtx.Lock()
18+
defer keyMtx.Unlock()
19+
keyCounter += 1
20+
return ContextKey{id: keyCounter}
1321
}

gid.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
}

0 commit comments

Comments
 (0)