Unfortunately, the standard golang context package does not control the closing order of child contexts (issue #51075).
(the parent context could exit earlier than his child, and in this case, you could get unpredicted execution behavior when you try to use some parent resources that are already closed)
To resolve this issue, here is another implementation of this context pattern.
It waits until all child contexts correctly close (parent event loop would be available for servicing their children). Only when all children would be closed parents will exits too.
Documentation: GoDoc
A full example can be found here: examples_test.go
import (
context "github.com/mcfly722/context"
)
Your node should contain Go(..) method:
type node struct {
name: string,
}
func (node *node) Go(current context.Context) {
loop:
for {
select {
case _, isOpened := <-current.Context(): // this method returns context channel. If it closes, it means that we need to finish select loop
if !isOpened {
break loop
}
default: // you can use default or not, it works in both cases
{
}
}
}
fmt.Printf("context %v closed\n", node.name)
}
node0 := &node{name : "root"}
node1 := &node{name : "1"}
node2 := &node{name : "2"}
node3 := &node{name : "3"}
ctx0 := context.NewRootContext(node0)
ctx1, err := ctx0.NewContextFor(node1)
if err != nil {
// child context is not created successfully, possibly the parent is in a closing state, you just need to exit
} else {
// child context created successfully
}
ctx2, err := ctx1.NewContextFor(node2)
...
ctx3, err := ctx2.NewContextFor(node3)
...
ctx0.Close()
It would close all contexts in reverse order: 3->2->1->root.
- Do not exit from your context goroutine without checking that current.Context() channel is closed. It is a potential lock or race, and this library restricts it (panic occurs especially to exclude this code mistake).
- Always check NewContextFor(...) error. A parent could be in a closed state; in this case, a child would not be created.
- Is there any send method to send some control messages from parent to child to change their state?
No. The only possible way to implement this without races, is to use the same channel that currently used for closing. Unfortunately, GoLang has library race between channel.Send and channel.Close methods (see issue #30372). - I want to wait until child context is closed. Where is context.Wait()?
context.Wait() is a race condition potential mistake. You send close to the child and wait for the parent, but children at this moment do not know anything about closing. It continues to send data to parents through channels. Parent blocked, it waits with context.Wait(). The child was also blocked on channel sending. It is a full dead block. - Why does rootContext.Wait() exist?
rootContext has its own empty goroutine loop without any send or receive, so deadblock from scenario 3 is not possible. - Where is 'Deadlines','Timeouts','Values' like in the original context?
It's all sugar.
This timeout or deadlines you can implement in your select loop (see: <-time.After(...) or <-time.Tick(...))
For values, use the constructor function with parameters. - Add same instance more than ones to different parents?
Yes, in this case, the new child goroutine starts only once, several parents will just wait for the same instance to close. - Create a dynamic goroutine pool with single-child input?
Yes, to terminate one of the parents, you should just exit from it without a Cancel() call. Do not close the last parent, otherwise, all the upper hives will close. If you need zero pool size support, just create one additional fake parent to hold an empty pool.