-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjoin.go
More file actions
135 lines (114 loc) · 2.51 KB
/
Copy pathjoin.go
File metadata and controls
135 lines (114 loc) · 2.51 KB
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
package scope
import (
"context"
"sync"
"time"
)
// join implements a context that is canceled when either of two
// contexts is canceled. It is a variation on the original implementation
// with race condition bug fixes.
//
// https://github.com/LK4D4/joincontext/blob/master/context.go
type join struct {
once sync.Once
a C
b C
done chan struct{}
lock *sync.Mutex
err error
}
// Join combines two contexts into a single context that is canceled when
// either input context is canceled. The returned context's Deadline,
// Value, and Err methods delegate to the earliest of the two input
// contexts. If either context is already done, the returned context is
// immediately done with that context's error.
func Join(a, b C) (C, Cancel) {
j := &join{
a: a,
b: b,
done: make(chan struct{}),
lock: new(sync.Mutex),
}
// check if either context is already done before spawning the goroutine
select {
case <-a.Done():
j.lock.Lock()
j.err = a.Err()
j.lock.Unlock()
close(j.done)
return j, func() {}
case <-b.Done():
j.lock.Lock()
j.err = b.Err()
j.lock.Unlock()
close(j.done)
return j, func() {}
default:
}
go j.run()
return j, j.cancel
}
// Deadline returns the earliest deadline from either context.
// If neither context has a deadline, ok is false.
func (j *join) Deadline() (deadline time.Time, ok bool) {
a, aok := j.a.Deadline()
if !aok {
return j.b.Deadline()
}
b, bok := j.b.Deadline()
if !bok {
return a, true
}
if b.Before(a) {
return b, true
}
return a, true
}
// Done returns a channel that is closed when either context is done.
func (j *join) Done() <-chan struct{} {
return j.done
}
// Err returns the error from whichever context was canceled first,
// or ErrCanceled if Cancel was called on the joined context.
func (j *join) Err() error {
j.lock.Lock()
defer j.lock.Unlock()
return j.err
}
// Value returns the value associated with key in either context,
// prioritizing the first context's value if present.
func (j *join) Value(key any) any {
v := j.a.Value(key)
if v == nil {
v = j.b.Value(key)
}
return v
}
func (j *join) run() {
select {
case <-j.a.Done():
j.once.Do(func() {
j.lock.Lock()
j.err = j.a.Err()
j.lock.Unlock()
close(j.done)
})
case <-j.b.Done():
j.once.Do(func() {
j.lock.Lock()
j.err = j.b.Err()
j.lock.Unlock()
close(j.done)
})
case <-j.done:
return
}
}
func (j *join) cancel() {
j.once.Do(func() {
j.lock.Lock()
j.err = context.Canceled
j.lock.Unlock()
close(j.done)
})
}