-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathdoc.go
143 lines (118 loc) · 4.15 KB
/
doc.go
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
/*
Package scope provides context objects for the sharing of scope across
goroutines. This context object provides a number of utilities for
coordinating concurrent work, in addition to sharing data.
Lifecycle
Contexts are nodes in a tree. A context is born either by forking from
an existing context (becoming a child of that node in the tree), or a
new tree is started by calling New().
A context can be terminated at any time. This is usually done by calling
the Terminate() or Cancel() method. Termination is associated with an
error value (which may be nil if one wants to indicate success). When
a node in the tree is terminated, that termination is propagated down
to all its unterminated descendents.
For example, here is how one might fan out a search:
// Fan out queries.
for _, q := range queries {
go func() {
a, err := q.Run(ctx.Fork())
if err != nil {
answers <- nil
} else {
answers <- a
}
}()
}
// Receive answers (or failures).
for answer := range answers {
if answer != nil {
ctx.Cancel() // tell outstanding queries to give up
return answer, nil
}
}
return nil, fmt.Errorf("all queries failed")
Contexts can be terminated at any time. You can even fork a context
with a deadline:
ctx := scope.New()
result, err := Search(ctx.ForkWithTimeout(5 * time.Second), queries)
if err == scope.TimedOut {
// one or more backends timed out, have the caller back off
}
There is a termination channel, Done(), available if you want to interrupt
your work when a context is terminated:
// Wait for 10 seconds or termination incurred from another goroutine,
// whichever occurs first.
select {
case <-ctx.Done():
return ctx.Err()
case <-timer.After(10*time.Second):
return nil
}
You can also spot-check for termination with a call to the Alive() method.
for ctx.Alive() {
readChunk()
}
Data Sharing
Contexts provide a data store for key value pairs, shared across the entire
scope. When a context is forked, the child context shares the same data map
as its parent.
This data store maps blank interfaces to blank interfaces, in the exact
same manner as http://www.gorillatoolkit.org/pkg/context. This means you
must use type assertions at runtime. To keep this reasonably safe, it's
recommended to define and use your own unexported type for all keys maintained
by your package.
type myKey int
const (
loggerKey myKey = iota
dbKey
// etc.
)
func SetLogger(ctx scope.Context, logger *log.Logger) {
ctx.Set(loggerKey, logger)
}
func GetLogger(ctx scope.Context) logger *log.Logger) {
return ctx.Get(loggerKey).(*log.Logger)
}
The shared data store is managed in a copy-on-write fashion as the tree
branches. When a context is forked, the child maintains a pointer to the
parent's data map. When Set() is called on the child, the original map
is duplicated for the child, and the update is only applied to the child's
map.
Common WaitGroup
Each context provides a WaitGroup() method, which returns the same pointer
across the entire tree. You can use this to spin off background tasks and
then wait for them before you completely shut down the scope.
ctx.WaitGroup().Add(1)
go func() {
doSomeThing(ctx)
ctx.WaitGroup().Done()
}()
ctx.WaitGroup().Wait()
Breakpoints
Contexts provide an optional feature to facilitate unit testing, called
breakpoints. A breakpoint is identified by a list of hashable values.
Production code can pass this list to the Check() method to synchronize
and allow for an error to be injected. Test code can register a breakpoint
with Breakpoint(), which returns a channel of errors. The test can
receive from this channel to synchronize with the entry of the corresponding
Check() call, and then write back an error to synchronize with the exit.
func Get(ctx scope.Context, url string) (*http.Response, error) {
if err := ctx.Check("http.Get", url); err != nil {
return nil, err
}
return http.Get(url)
}
func TestGetError(t *testing.T) {
ctx := scope.New()
ctrl := ctx.Breakpoint("http.Get", "http://google.com")
testErr := fmt.Errorf("test error")
go func() {
<-ctrl
ctrl <- testErr
}()
if err := Get(ctx, "http://google.com"); err != testErr {
t.Fail()
}
}
*/
package scope