Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

context: Incorrect cancellation order (bug) #51075

Closed
mcfly722 opened this issue Feb 8, 2022 · 1 comment
Closed

context: Incorrect cancellation order (bug) #51075

mcfly722 opened this issue Feb 8, 2022 · 1 comment

Comments

@mcfly722
Copy link

mcfly722 commented Feb 8, 2022

go version go1.17.6 windows/amd64

When you cancel parent context, all childs cancels without particular order. (Parent context could exit erlier than his child and its child could get unpredicted execution behaviour when try to use some parent resources which is already disposed)

There is example.
It is recursive function which builds context tree like this: 0 (background)->1->2->3->4->5->6->7->8->9->10
and tries to cancel it from root object 0(backhround).

package main

import (
	"context"
	"fmt"
	"time"
)

func childObject(ctx context.Context, parentObjectNumber int) {
	if parentObjectNumber < 10 {
		ctx, cancel := context.WithCancel(ctx)
		currentObjectNumber := parentObjectNumber + 1

		go func() {
			for {
				select {
				case <-ctx.Done():
					fmt.Println(fmt.Sprintf("object %v canceled", currentObjectNumber))
					cancel()
					return
				default:
				}
			}
		}()

		fmt.Println(fmt.Sprintf("child object %v with parent %v started", currentObjectNumber, parentObjectNumber))
		childObject(ctx, currentObjectNumber)
	}

}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	childObject(ctx, 0)
	time.Sleep(1 * time.Second)
	fmt.Println("cancel main context")
	cancel()
	time.Sleep(1 * time.Second)
	fmt.Println("finishing")
}

Output is:

go run main.go
child object 1 with parent 0 started
child object 2 with parent 1 started
child object 3 with parent 2 started
child object 4 with parent 3 started
child object 5 with parent 4 started
child object 6 with parent 5 started
child object 7 with parent 6 started
child object 8 with parent 7 started
child object 9 with parent 8 started
child object 10 with parent 9 started
cancel main context
object 7 canceled
object 3 canceled
object 5 canceled
object 1 canceled
object 6 canceled
object 9 canceled
object 4 canceled
object 2 canceled
object 8 canceled
object 10 canceled
finishing

As you can see cancellation is without particular order inspite of that fact what context mechanism should wait till all subchilds call cancel() function to avoid context leaks.

Expected beheviour:

child object 1 with parent 0 started
child object 2 with parent 1 started
child object 3 with parent 2 started
child object 4 with parent 3 started
child object 5 with parent 4 started
child object 6 with parent 5 started
child object 7 with parent 6 started
child object 8 with parent 7 started
child object 9 with parent 8 started
child object 10 with parent 9 started
cancel main context
object 10 canceled
object 9 canceled
object 8 canceled
object 7 canceled
object 6 canceled
object 5 canceled
object 4 canceled
object 3 canceled
object 2 canceled
object 1 canceled
finishing

@seankhliao
Copy link
Member

The cancellation does follow the tree downwards in order, however, what you're experiencing is that the scheduler makes no guarantee that any goroutine will run immediately when available, so even though the cancellation is in the correct order, any code attached to it might not be.
You will need some other synchronization if you depend on shared resources.

@golang golang locked and limited conversation to collaborators Feb 8, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants