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

fs, memory, s3 marker: take context cancellation into account #76

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

ahouene
Copy link
Contributor

@ahouene ahouene commented Aug 27, 2024

All function for backends fs and memory do not use their ctx parameter. This adds checks for ctx.Err() at the beginning of those.

As for s3, check ctx from marker logic even if marker is disabled.

@ahouene ahouene self-assigned this Aug 27, 2024
@ahouene ahouene changed the title fs, memory: take context cancellation into account fs, memory, s3 marker: take context cancellation into account Aug 27, 2024
@@ -14,7 +14,7 @@ import (
// anything and returns no error.
func (b *Backend) setMarker(ctx context.Context, name, etag string, isDel bool) error {
if !b.opt.UseUpdateMarker {
return nil
return ctx.Err()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am struggling a little to understand why ctx.Err() is returned here. The doc above says:
// In case the UseUpdateMarker option is false, this function doesn't do
// anything and returns no error

so returning nil seems to better match that description?

Copy link

@johanlantz johanlantz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added two comments but none that prevents a merge from my side.

Copy link
Member

@wojas wojas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contexts exist to cancel or prevent long expensive operations, typically network operations.

The context package introduction puts it like this:

Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes. Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context.

None of the os.* functions for filesystem access take any context, and checking it in the fs backend feels a bit artificial. For the memory backend it is definitely quite unconventional.

Is there a practical reason for adding all these boilerplate checks?

@ahouene
Copy link
Contributor Author

ahouene commented Sep 19, 2024

The idea behind that is to honour the ctx parameter, since it is present in the function signatures regardless of having an actual networked/concurrent backend implementation.

Consider the following code:

var storage simpleblob.Interface
// ...
ctx, cancel := context.WithCancel(context.Background())
cancel()
err := storage.Delete(ctx, "foo")

To the question Has the blob named "foo" been deleted and do we have an error?, the answer is currently It depends on the backend, and this PR aims to make the answer be independent of the backend: An error was returned and the blob was not deleted.

More generally, if I call err := f(ctx) I expect err to be non-nil if ctx is cancelled. Incidentally, the current implementation makes it up to the person using simpleblob to write the boilerplate checks to make sure that the context is indeed not cancelled.

@wojas
Copy link
Member

wojas commented Sep 20, 2024

Typically cancellation occurs asynchronously from the operation that is covered by the context. If you check at the start of a function and expect that to prevent execution, you are basically introducing a race condition.

One cannot expect that just because a function takes a context as a parameter, the function will not do anything if the context is already cancelled. Context were originally an optimization to prevent unnecessary when a client connection was already closed for whatever reason.

If the caller wants to ensure that the following code is not executed if the context is already closed, it needs to explicitly perform that check. You cannot expect every single function that receives a context to check it before continuing execution. The context is passed through the stack so that any deeper layer can use it for cancellation, but there is no requirement to do so. If it were, Go code would have to have such boilerplate checks all over the place.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants