golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.99k stars 17.67k forks source link

proposal: context: NewLinkedCancelCtx #68247

Closed szmcdull closed 4 months ago

szmcdull commented 4 months ago

Proposal Details

Currently, a cancelCtx propagates its cancelFunc to its parent context.

If we add some little changes, we can make a child cancelCtx linked with multiple parent contexts, and make the parent contexts propagate back to the child.

In this way, we make a LinkedCancelCtx, which is canceled automatically when any of its parent is canceled, which is very useful in handling exit signals.

For example, if context C should be canceled when context A or B is canceled, and context D should be canceled when context C is canceled. Then we can do C := NewLinkedCancelCtx(A, B), and then pass C to D or anyone to be called by C and interested in when to cancel the call.

Example codes: https://github.com/szmcdull/go-cancelContext/blob/main/impl_1.21.go

gabyhelp commented 4 months ago

Related Issues

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

ianlancetaylor commented 4 months ago

CC @Sajmani

seankhliao commented 4 months ago

This is a dupe of #36503 (it's long, but the final focus was on merging cancelation), where the final decision was:

We can decline this for now and then feel free to open a new proposal for Merge in a year or so if there is new evidence.

This proposal only describes the desired functionality, but doesn't provide any new evidence of need or widespread use of the pattern in the ecosystem.

szmcdull commented 4 months ago

This functionality was inspired by dotnet's CancellationTokenSource.CreateLinkedTokenSource(https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource.createlinkedtokensource?view=net-8.0)

It is widely used and provided in the dotnet standard library.

Here is the search result of CreateLinkedTokenSource in github.com: https://github.com/search?q=CreateLinkedTokenSource&type=code. It shows 17.5k files are using the method.

AndrewHarrisSPU commented 4 months ago

This functionality was inspired by dotnet's CancellationTokenSource.CreateLinkedTokenSource(https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource.createlinkedtokensource?view=net-8.0)

It is widely used and provided in the dotnet standard library.

Here is the search result of CreateLinkedTokenSource in github.com: https://github.com/search?q=CreateLinkedTokenSource&type=code. It shows 17.5k files are using the method.

Perusing, the high quantity of lines found here seems like the result of language differences rather than use case differences. The distinction in C# between CancellationToken and CancellationTokenSource serves the invariant that children observe, and don't cause, parents' cancellation, as expressed in an OOP approach. Equivalent parent/child derivation occurs in context.

CreateLinkedTokenSource is most frequently used with one token; the result is a CancellationTokenSource parent that cancels its CancellationToken children - either independently, or consequent to its' own parent's cancellation. This seems pretty equivalent to context.WithCancel.

Similarly, CreateLinkedTokenSource with two tokens is usually context.WithTimeout or context.WithDeadline. Or, with exit signals, context.AfterFunc is pertinent.

szmcdull commented 4 months ago

This functionality was inspired by dotnet's CancellationTokenSource.CreateLinkedTokenSource(https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource.createlinkedtokensource?view=net-8.0) It is widely used and provided in the dotnet standard library. Here is the search result of CreateLinkedTokenSource in github.com: https://github.com/search?q=CreateLinkedTokenSource&type=code. It shows 17.5k files are using the method.

Perusing, the high quantity of lines found here seems like the result of language differences rather than use case differences. The distinction in C# between CancellationToken and CancellationTokenSource serves the invariant that children observe, and don't cause, parents' cancellation, as expressed in an OOP approach. Equivalent parent/child derivation occurs in context.

CreateLinkedTokenSource is most frequently used with one token; the result is a CancellationTokenSource parent that cancels its CancellationToken children - either independently, or consequent to its' own parent's cancellation. This seems pretty equivalent to context.WithCancel.

Similarly, CreateLinkedTokenSource with two tokens is usually context.WithTimeout or context.WithDeadline. Or, with exit signals, context.AfterFunc is pertinent.

I don't agree. Just on the first search result page there are 11 occurrence of CreateLinkedTokenSource with 2 paramters that neither is a Timeout. It is of high quantity, but not just language differences.

It is true that Go's context lacks the ability to combine multiple contexts' cancel signals.

seankhliao commented 4 months ago

What we want to see is the need for it within the Go ecosystem, not a different language which will have different patterns available to it.

szmcdull commented 4 months ago

Then what is the need for context.WithCancel()? It is because we need a context that can parse down the cancel signal to a dependency service right?

So it is the same need, sometimes we need to parse down multiple cancel signals to the dependency service, but Go's pattern only allows one. Of course there should be one context because it would be non-sense and ambiguous to have multiple contexts. The need is to combine multiple cancel signals into a new context.

Consider the scenario of a network service. The service listen to client connections and requests, and provide certain functionality. If the functionality is resource consuming we need to abort the functionality soon if the client disconnects early. We can achieve that because have a context from maybe http.Request or some wrapped structure, so that we can parse the context to the resource consuming function.

Now consider a more complex scenario, where the functionality depends on other aggregated services. Now besides the client early disconnection, all services need to be canceled when any one of them fails. How can we achieve this when we can only parse one context and the context is read only, and the cancel function is not parsed together with the context?

szmcdull commented 4 months ago

ChatGPT:

Certainly! Here are more specific scenarios where CreateLinkedTokenSource can be applied effectively:

Web Service Aggregation: Scenario: You are aggregating data from multiple web services concurrently. If a user cancels the request or a timeout occurs, you want to cancel all ongoing requests to release resources. Usage: Create a linked token source where the main cancellation token is linked to tokens for each individual web service request. This ensures that if the main operation is cancelled, all ongoing web service requests are cancelled promptly.

Database Query Parallelization: Scenario: You have a system that executes multiple database queries in parallel. If one query fails or is cancelled, you want to cancel all other queries to prevent unnecessary load on the database and ensure consistent handling. Usage: Create a linked token source where each database query task is linked to a main cancellation token. This allows you to cancel all ongoing database queries if any one of them encounters an issue or if the main operation is cancelled.

File Processing with Validation: Scenario: You are processing multiple files concurrently, and each file needs to undergo validation. If validation fails for any file or if the user cancels the operation, you want to stop processing all remaining files. Usage: Create a linked token source where each file processing task (including validation) is linked to a main cancellation token. This ensures that if validation fails for one file or if the operation is cancelled, processing stops for all files immediately.

Parallel Task Execution with Dependency: Scenario: You have a series of tasks that need to execute in parallel, where the outcome of one task determines whether subsequent tasks should proceed. If an error occurs in any task or if the user cancels the operation, you want to abort all remaining tasks. Usage: Use CreateLinkedTokenSource to link cancellation tokens across tasks. Each task can check for cancellation and propagate cancellation to subsequent tasks as needed. This ensures that if one task fails or is cancelled, dependent tasks are also cancelled accordingly.

Real-time Data Processing: Scenario: You are processing real-time data streams in multiple threads or tasks. If an error occurs or if the processing is no longer necessary (e.g., due to changing user requirements), you want to stop all ongoing data processing immediately. Usage: Create a linked token source where each data processing task is linked to a main cancellation token. This allows you to cancel all ongoing data processing tasks simultaneously if the main operation is cancelled or if an error condition arises.

Composite Operations in GUI Applications: Scenario: You are performing composite operations in a GUI application where multiple asynchronous operations (like data loading, validation, and UI updates) need to be coordinated. If the user navigates away or cancels an operation, you want to halt all ongoing operations cleanly. Usage: Use CreateLinkedTokenSource to manage cancellation tokens across all involved asynchronous operations. This ensures that if the user cancels the operation or navigates away, all ongoing operations are cancelled, preventing unnecessary resource usage and ensuring a responsive user interface.

These scenarios illustrate more concrete examples where CreateLinkedTokenSource can be utilized to manage cancellation effectively in various practical applications and programming contexts.

seankhliao commented 4 months ago

There doesn't appear to be any significant new evidence compared to then this was proposed last year.

seankhliao commented 4 months ago

Duplicate of ##36503

AndrewHarrisSPU commented 4 months ago

I don't agree. Just on the first search result page there are 11 occurrence of CreateLinkedTokenSource with 2 paramters that neither is a Timeout. It is of high quantity, but not just language differences.

I filtered to just C#. To the extent I grok the C#, I'm not really seeing novel usage. A few times 2 tokens are used for e.g. application background plus request lifetime - Go knows how to do this with http.NewRequestWithContext.

Without filtering, there is JS or TS that does interesting things like converting F# to JavaScript - I don't think this serves as great evidence for convenient idioms.

Now consider a more complex scenario, where the functionality depends on other aggregated services. Now besides the client early disconnection, all services need to be canceled when any one of them fails. How can we achieve this when we can only parse one context and the context is read only, and the cancel function is not parsed together with the context?

https://pkg.go.dev/golang.org/x/sync/errgroup shows one way to do this. Here, a Group acts as a supervisor that cancels all work if any subtask returns an error. Underneath, in implementation, the error propagates child-to-parent via a sync.Once function rather than a context.Context.

I don't think a universal formalization of the child-to-parent cancellation edge is feasible, but errgroup is usually practical. Architecturally, keeping these child-to-parent cancellation edges explicitly distinct from context.Context derivation is IMHO worthwhile.

szmcdull commented 4 months ago

Thanks, I found out a way to implement the functionality using context.AfterFunc