Skip to content

Commit 2641c4b

Browse files
committed
context: refactor with new internal interface types
* Modify canceler to cancelable, with exported Cancel function. * Add canceler interface with Register and Unregister fnctions. * Add child interface with Parent function. These interface allow external implementations to define functions that improve the context performance when being used. Fixes #28728
1 parent 6f7542e commit 2641c4b

File tree

2 files changed

+140
-87
lines changed

2 files changed

+140
-87
lines changed

src/context/context.go

+126-80
Original file line numberDiff line numberDiff line change
@@ -232,97 +232,97 @@ type CancelFunc func()
232232
// call cancel as soon as the operations running in this Context complete.
233233
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
234234
c := newCancelCtx(parent)
235-
propagateCancel(parent, &c)
236-
return &c, func() { c.cancel(true, Canceled) }
235+
c.link = newLink(parent, &c)
236+
return &c, func() { c.cancelAndUnlink(Canceled) }
237237
}
238238

239239
// newCancelCtx returns an initialized cancelCtx.
240240
func newCancelCtx(parent Context) cancelCtx {
241241
return cancelCtx{Context: parent}
242242
}
243243

244-
// propagateCancel arranges for child to be canceled when parent is.
245-
func propagateCancel(parent Context, child canceler) {
246-
if parent.Done() == nil {
247-
return // parent is never canceled
248-
}
249-
if p, ok := parentCancelCtx(parent); ok {
250-
p.mu.Lock()
251-
if p.err != nil {
252-
// parent has already been canceled
253-
child.cancel(false, p.err)
254-
} else {
255-
if p.children == nil {
256-
p.children = make(map[canceler]struct{})
257-
}
258-
p.children[child] = struct{}{}
259-
}
260-
p.mu.Unlock()
261-
} else {
262-
go func() {
263-
select {
264-
case <-parent.Done():
265-
child.cancel(false, parent.Err())
266-
case <-child.Done():
267-
}
268-
}()
269-
}
244+
// canceler is an optional interface for a context type for propagating
245+
// cancellation. If a context implementation want to manage context
246+
// cancellation, it needs to cancel all its children when cancelled.
247+
// This interface provides a way for a child to register itself in a
248+
// parent context.
249+
type canceler interface {
250+
// Register a new cancelable child for a context.
251+
Register(cancelable)
252+
// Unregister existing cancelable child.
253+
Unregister(cancelable)
270254
}
271255

272-
// parentCancelCtx follows a chain of parent references until it finds a
273-
// *cancelCtx. This function understands how each of the concrete types in this
274-
// package represents its parent.
275-
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
256+
// cancelable is an optional interface for a context type that can be canceled
257+
// directly.
258+
type cancelable interface {
259+
Cancel(err error)
260+
Done() <-chan struct{}
261+
}
262+
263+
// child is an optional interface for a context type in which it returns its
264+
// parent context. It is useful for context to expose this interface to prevent
265+
// an extra goroutine when being called with a cancelable context conversion.
266+
type child interface {
267+
// Return the parent context of a context.
268+
Parent() Context
269+
}
270+
271+
// lookupCanceler follows a chain of parent references until it finds a
272+
// canceler.
273+
func lookupCanceler(parent Context) canceler {
276274
for {
277275
switch c := parent.(type) {
278-
case *cancelCtx:
279-
return c, true
280-
case *timerCtx:
281-
return &c.cancelCtx, true
282-
case *valueCtx:
283-
parent = c.Context
276+
case canceler:
277+
return c
278+
case child:
279+
parent = c.Parent()
284280
default:
285-
return nil, false
281+
return &cancelerCtx{Context: parent}
286282
}
287283
}
288284
}
289285

290-
// removeChild removes a context from its parent.
291-
func removeChild(parent Context, child canceler) {
292-
p, ok := parentCancelCtx(parent)
293-
if !ok {
294-
return
295-
}
296-
p.mu.Lock()
297-
if p.children != nil {
298-
delete(p.children, child)
299-
}
300-
p.mu.Unlock()
301-
}
302-
303-
// A canceler is a context type that can be canceled directly. The
304-
// implementations are *cancelCtx and *timerCtx.
305-
type canceler interface {
306-
cancel(removeFromParent bool, err error)
307-
Done() <-chan struct{}
308-
}
309-
310286
// closedchan is a reusable closed channel.
311287
var closedchan = make(chan struct{})
312288

313289
func init() {
314290
close(closedchan)
315291
}
316292

293+
// link maintains a link between a child cancelable to a parent canceler.
294+
type link struct {
295+
parent canceler
296+
child cancelable
297+
}
298+
299+
func newLink(parent Context, child cancelable) link {
300+
if parent.Done() == nil {
301+
return link{} // parent is never canceled
302+
}
303+
c := lookupCanceler(parent)
304+
c.Register(child)
305+
return link{parent: c, child: child}
306+
}
307+
308+
func (l *link) unlink() {
309+
if l.parent != nil {
310+
l.parent.Unregister(l.child)
311+
l.parent = nil
312+
l.child = nil
313+
}
314+
}
315+
317316
// A cancelCtx can be canceled. When canceled, it also cancels any children
318317
// that implement canceler.
319318
type cancelCtx struct {
320319
Context
321320

322-
mu sync.Mutex // protects following fields
323-
done chan struct{} // created lazily, closed by first cancel call
324-
children map[canceler]struct{} // set to nil by the first cancel call
325-
err error // set to non-nil by the first cancel call
321+
mu sync.Mutex // protects following fields
322+
done chan struct{} // created lazily, closed by first cancel call
323+
children map[cancelable]struct{} // set to nil by the first cancel call
324+
err error // set to non-nil by the first cancel call
325+
link link
326326
}
327327

328328
func (c *cancelCtx) Done() <-chan struct{} {
@@ -342,6 +342,29 @@ func (c *cancelCtx) Err() error {
342342
return err
343343
}
344344

345+
func (c *cancelCtx) Register(child cancelable) {
346+
c.mu.Lock()
347+
if c.err != nil {
348+
// parent has already been canceled
349+
child.Cancel(c.err)
350+
} else {
351+
if c.children == nil {
352+
c.children = make(map[cancelable]struct{})
353+
}
354+
c.children[child] = struct{}{}
355+
}
356+
c.mu.Unlock()
357+
}
358+
359+
// Unregister removes a context from the context.
360+
func (c *cancelCtx) Unregister(child cancelable) {
361+
c.mu.Lock()
362+
if c.children != nil {
363+
delete(c.children, child)
364+
}
365+
c.mu.Unlock()
366+
}
367+
345368
type stringer interface {
346369
String() string
347370
}
@@ -357,9 +380,9 @@ func (c *cancelCtx) String() string {
357380
return contextName(c.Context) + ".WithCancel"
358381
}
359382

360-
// cancel closes c.done, cancels each of c's children, and, if
383+
// Cancel closes c.done, cancels each of c's children, and, if
361384
// removeFromParent is true, removes c from its parent's children.
362-
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
385+
func (c *cancelCtx) Cancel(err error) {
363386
if err == nil {
364387
panic("context: internal error: missing cancel error")
365388
}
@@ -376,14 +399,17 @@ func (c *cancelCtx) cancel(removeFromParent bool, err error) {
376399
}
377400
for child := range c.children {
378401
// NOTE: acquiring the child's lock while holding parent's lock.
379-
child.cancel(false, err)
402+
child.Cancel(err)
380403
}
381404
c.children = nil
382405
c.mu.Unlock()
406+
}
383407

384-
if removeFromParent {
385-
removeChild(c.Context, c)
386-
}
408+
func (c *cancelCtx) cancelAndUnlink(err error) {
409+
c.Cancel(err)
410+
c.mu.Lock()
411+
c.link.unlink()
412+
c.mu.Unlock()
387413
}
388414

389415
// WithDeadline returns a copy of the parent context with the deadline adjusted
@@ -404,20 +430,20 @@ func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
404430
cancelCtx: newCancelCtx(parent),
405431
deadline: d,
406432
}
407-
propagateCancel(parent, c)
433+
c.link = newLink(parent, c)
408434
dur := time.Until(d)
409435
if dur <= 0 {
410-
c.cancel(true, DeadlineExceeded) // deadline has already passed
411-
return c, func() { c.cancel(false, Canceled) }
436+
c.cancelAndUnlink(DeadlineExceeded) // deadline has already passed
437+
return c, func() { c.Cancel(Canceled) }
412438
}
413439
c.mu.Lock()
414440
defer c.mu.Unlock()
415441
if c.err == nil {
416442
c.timer = time.AfterFunc(dur, func() {
417-
c.cancel(true, DeadlineExceeded)
443+
c.cancelAndUnlink(DeadlineExceeded)
418444
})
419445
}
420-
return c, func() { c.cancel(true, Canceled) }
446+
return c, func() { c.cancelAndUnlink(Canceled) }
421447
}
422448

423449
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
@@ -440,12 +466,8 @@ func (c *timerCtx) String() string {
440466
time.Until(c.deadline).String() + "])"
441467
}
442468

443-
func (c *timerCtx) cancel(removeFromParent bool, err error) {
444-
c.cancelCtx.cancel(false, err)
445-
if removeFromParent {
446-
// Remove this timerCtx from its parent cancelCtx's children.
447-
removeChild(c.cancelCtx.Context, c)
448-
}
469+
func (c *timerCtx) Cancel(err error) {
470+
c.cancelCtx.Cancel(err)
449471
c.mu.Lock()
450472
if c.timer != nil {
451473
c.timer.Stop()
@@ -523,3 +545,27 @@ func (c *valueCtx) Value(key interface{}) interface{} {
523545
}
524546
return c.Context.Value(key)
525547
}
548+
549+
func (c *valueCtx) Parent() Context {
550+
return c.Context
551+
}
552+
553+
// cancelerCtx wraps a context and implement the canceler interface
554+
// by invoking a goroutine for every registered cancelable.
555+
type cancelerCtx struct {
556+
Context
557+
}
558+
559+
func (r *cancelerCtx) Register(child cancelable) {
560+
go func() {
561+
select {
562+
case <-r.Done():
563+
child.Cancel(r.Err())
564+
case <-child.Done():
565+
}
566+
}()
567+
}
568+
569+
func (r *cancelerCtx) Unregister(child cancelable) {
570+
571+
}

src/context/context_test.go

+14-7
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func XTestWithCancel(t testingT) {
110110
}
111111
}
112112

113-
func contains(m map[canceler]struct{}, key canceler) bool {
113+
func contains(m map[cancelable]struct{}, key cancelable) bool {
114114
_, ret := m[key]
115115
return ret
116116
}
@@ -149,11 +149,11 @@ func XTestParentFinishesChild(t testingT) {
149149
}
150150
pc.mu.Unlock()
151151

152-
if p, ok := parentCancelCtx(cc.Context); !ok || p != pc {
153-
t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc)
152+
if p := lookupCanceler(cc.Context); p != pc {
153+
t.Errorf("bad linkage: lookupCanceler(cancelChild.Context) = %v want %v", p, pc)
154154
}
155-
if p, ok := parentCancelCtx(tc.Context); !ok || p != pc {
156-
t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc)
155+
if p := lookupCanceler(tc.Context); p != pc {
156+
t.Errorf("bad linkage: lookupCanceler(timerChild.Context) = %v want %v", p, pc)
157157
}
158158

159159
cancel()
@@ -208,8 +208,15 @@ func XTestChildFinishesFirst(t testingT) {
208208

209209
cc := child.(*cancelCtx)
210210
pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background()
211-
if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) {
212-
t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok)
211+
p := lookupCanceler(cc.Context)
212+
if pcok {
213+
if pc != p {
214+
t.Errorf("bad linkage: lookupCanceler(cc.Context) = %v want %v", p, pc)
215+
}
216+
} else {
217+
if _, ok := p.(*cancelerCtx); !ok {
218+
t.Errorf("bad linkage: lookupCanceler(cc.Context).(type) = %T want *cancelerCtx", p)
219+
}
213220
}
214221

215222
if pcok {

0 commit comments

Comments
 (0)