summaryrefslogtreecommitdiff
path: root/vendor/github.com/getlantern/context/context.go
blob: c77f12979ed6ede9ae28af7437d194bbf1b380ed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
// Package context provides a mechanism for transparently tracking contextual
// state associated to the current goroutine and even across goroutines.
package context

import (
	"sync"
)

// Manager provides the ability to create and access Contexts.
type Manager interface {
	// Enter enters a new level on the current Context stack, creating a new Context
	// if necessary.
	Enter() Context

	// Go starts the given function on a new goroutine but sharing the context of
	// the current goroutine (if it has one).
	Go(func())

	// PutGlobal puts the given key->value pair into the global context.
	PutGlobal(key string, value interface{})

	// PutGlobalDynamic puts a key->value pair into the global context where the
	// value is generated by a function that gets evaluated at every Read. If the
	// value is a map[string]interface{}, we will unpack the map and set each
	// contained key->value pair independently.
	PutGlobalDynamic(key string, valueFN func() interface{})

	// AsMap returns a map containing all values from the supplied obj if it is a
	// Contextual, plus any addition values from along the stack, plus globals if so
	// specified.
	AsMap(obj interface{}, includeGlobals bool) Map
}

type manager struct {
	contexts   map[uint64]*context
	mxContexts sync.RWMutex
	global     Map
	mxGlobal   sync.RWMutex
}

// NewManager creates a new Manager
func NewManager() Manager {
	return &manager{
		contexts: make(map[uint64]*context),
		global:   make(Map),
	}
}

// Contextual is an interface for anything that maintains its own context.
type Contextual interface {
	// Fill fills the given Map with all of this Contextual's context
	Fill(m Map)
}

// Map is a map of key->value pairs.
type Map map[string]interface{}

// Fill implements the method from the Contextual interface.
func (_m Map) Fill(m Map) {
	for key, value := range _m {
		m[key] = value
	}
}

// Context is a context containing key->value pairs
type Context interface {
	// Enter enters a new level on this Context stack.
	Enter() Context

	// Go starts the given function on a new goroutine.
	Go(fn func())

	// Exit exits the current level on this Context stack.
	Exit()

	// Put puts a key->value pair into the current level of the context stack.
	Put(key string, value interface{}) Context

	// PutIfAbsent puts the given key->value pair into the current level of the
	// context stack if and only if that key is defined nowhere within the context
	// stack (including parent contexts).
	PutIfAbsent(key string, value interface{}) Context

	// PutDynamic puts a key->value pair into the current level of the context
	// stack where the value is generated by a function that gets evaluated at
	// every Read. If the value is a map[string]interface{}, we will unpack the
	// map and set each contained key->value pair independently.
	PutDynamic(key string, valueFN func() interface{}) Context

	// Fill fills the given map with data from this Context
	Fill(m Map)

	// AsMap returns a map containing all values from the supplied obj if it is a
	// Contextual, plus any addition values from along the stack, plus globals if
	// so specified.
	AsMap(obj interface{}, includeGlobals bool) Map
}

type context struct {
	cm           *manager
	id           uint64
	parent       *context
	branchedFrom *context
	data         Map
	mx           sync.RWMutex
}

type dynval struct {
	fn func() interface{}
}

func (cm *manager) Enter() Context {
	return cm.enter(curGoroutineID())
}

func (cm *manager) enter(id uint64) *context {
	cm.mxContexts.Lock()
	parentOrNil := cm.contexts[id]
	c := cm.makeContext(id, parentOrNil, nil)
	cm.contexts[id] = c
	cm.mxContexts.Unlock()
	return c
}

func (cm *manager) exit(id uint64, parent *context) {
	cm.mxContexts.Lock()
	if parent == nil {
		delete(cm.contexts, id)
	} else {
		cm.contexts[id] = parent
	}
	cm.mxContexts.Unlock()
}

func (cm *manager) branch(id uint64, from *context) {
	next := cm.makeContext(id, nil, from)
	cm.mxContexts.Lock()
	cm.contexts[id] = next
	cm.mxContexts.Unlock()
}

func (cm *manager) merge(id uint64) {
	cm.mxContexts.Lock()
	delete(cm.contexts, id)
	cm.mxContexts.Unlock()
}

func (c *context) Enter() Context {
	c.mx.RLock()
	id := c.id
	c.mx.RUnlock()
	return c.cm.enter(id)
}

func (c *context) Go(fn func()) {
	go func() {
		id := curGoroutineID()
		c.cm.branch(id, c)
		fn()
		c.cm.merge(id)
	}()
}

func (cm *manager) Go(fn func()) {
	c := cm.currentContext()
	if c != nil {
		c.Go(fn)
	} else {
		go fn()
	}
}

func (cm *manager) makeContext(id uint64, parent *context, branchedFrom *context) *context {
	return &context{
		cm:           cm,
		id:           id,
		parent:       parent,
		branchedFrom: branchedFrom,
		data:         make(Map),
	}
}

func (c *context) Exit() {
	c.mx.RLock()
	id := c.id
	parent := c.parent
	c.mx.RUnlock()
	c.cm.exit(id, parent)
}

func (c *context) Put(key string, value interface{}) Context {
	c.mx.Lock()
	c.data[key] = value
	c.mx.Unlock()
	return c
}

func (c *context) PutIfAbsent(key string, value interface{}) Context {
	for ctx := c; ctx != nil; {
		ctx.mx.RLock()
		_, exists := ctx.data[key]
		next := ctx.parent
		if next == nil {
			next = ctx.branchedFrom
		}
		ctx.mx.RUnlock()
		if exists {
			return c
		}
		ctx = next
	}

	// Value not set, set it
	return c.Put(key, value)
}

func (c *context) PutDynamic(key string, valueFN func() interface{}) Context {
	value := &dynval{valueFN}
	c.mx.Lock()
	c.data[key] = value
	c.mx.Unlock()
	return c
}

func (cm *manager) PutGlobal(key string, value interface{}) {
	cm.mxGlobal.Lock()
	cm.global[key] = value
	cm.mxGlobal.Unlock()
}

func (cm *manager) PutGlobalDynamic(key string, valueFN func() interface{}) {
	value := &dynval{valueFN}
	cm.mxGlobal.Lock()
	cm.global[key] = value
	cm.mxGlobal.Unlock()
}

func (c *context) Fill(m Map) {
	for ctx := c; ctx != nil; {
		ctx.mx.RLock()
		fill(m, ctx.data)
		next := ctx.parent
		if next == nil {
			next = ctx.branchedFrom
		}
		ctx.mx.RUnlock()
		ctx = next
	}
}

func (cm *manager) AsMap(obj interface{}, includeGlobals bool) Map {
	return cm.currentContext().asMap(cm, obj, includeGlobals)
}

func (c *context) AsMap(obj interface{}, includeGlobals bool) Map {
	return c.asMap(c.cm, obj, includeGlobals)
}

func (c *context) asMap(cm *manager, obj interface{}, includeGlobals bool) Map {
	result := make(Map, 0)
	cl, ok := obj.(Contextual)
	if ok {
		cl.Fill(result)
	}
	if c != nil {
		c.Fill(result)
	}
	if includeGlobals {
		cm.mxGlobal.RLock()
		fill(result, cm.global)
		cm.mxGlobal.RUnlock()
	}
	return result
}

func fill(m Map, from Map) {
	if m != nil {
		doFill := func(key string, _value interface{}) {
			switch value := _value.(type) {
			case map[string]interface{}:
				for k, v := range value {
					m[k] = v
				}
			default:
				m[key] = value
			}
		}

		for key, value := range from {
			_, alreadyRead := m[key]
			if !alreadyRead {
				switch v := value.(type) {
				case *dynval:
					doFill(key, v.fn())
				default:
					doFill(key, v)
				}
			}
		}
	}
}

func (cm *manager) currentContext() *context {
	id := curGoroutineID()
	cm.mxContexts.RLock()
	c := cm.contexts[id]
	cm.mxContexts.RUnlock()
	return c
}