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

Put context.Context arguments on almost everything #431

Merged
merged 1 commit into from
Apr 10, 2018

Conversation

novas0x2a
Copy link
Contributor

@novas0x2a novas0x2a commented Mar 20, 2018

This is a pretty huge change, and it breaks api, so consider this first steps :)

After this change:

  • Network IO paths should react to cancels now.
  • File IO paths generally still won't.
  • SystemContext objects have been renamed to sys to leave ctx available for the stdlib context objects.

@novas0x2a novas0x2a force-pushed the context-everywhere branch 2 times, most recently from 625295f to adcbd74 Compare March 21, 2018 20:37
@@ -1,3 +1,5 @@
github.com/containers/image
Copy link
Collaborator

@mtrmac mtrmac Mar 22, 2018

Choose a reason for hiding this comment

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

One-minute look, will review in detail later:

  • ^^ This seems unexpected.
  • Overall having a context.Context in most places is definitely valuable.
  • I’m not sure about passing types.SystemContext and context.Context by a single parameter, notably because SystemContext currently intentionally violates the “Do not store Contexts inside a struct type” rule. Combining them may still be the right thing to do, I’ll need to take a deeper look.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just replying to the first point; the first line of the trash vendor.conf identifies the package root (e.g.); trash gives me a warning without it (presumably because I use a multiple-GOPATH and it can't identify the canonical parent on its own).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, I had no idea that was a thing. Thanks.

@mtrmac
Copy link
Collaborator

mtrmac commented Mar 27, 2018

that's slightly gauche to pass a nil to something that takes a context.Context, but in this case (because the type system can't catch it) I've made it so passing a nil to GetSystemContext behaves the way passing a nil as a SystemContext used to.

That’s not sufficient; if anything actually tries to use the context.Context as a context.Context (e.g. calling Done() or WithCancel(), it will crash on nil.

Copy link
Collaborator

@mtrmac mtrmac left a comment

Choose a reason for hiding this comment

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

Overall, I don’t think transferring types.SystemContext as a Value in context.Context makes sense; the two should remain separate.

  • Looking at how copy.Image uses separate types.SystemContext values for the source and destination (copy.Options.{SourceCtx,DestinationCtx}), it’s clear that the two are not semantically close enough: types.SystemContext is an options storage mechanism, which is propagated along with the relevant data structures; context.Context is a control flow management mechanism, which is propagated along the call stacks and goroutine creations. A single value can’t cleanly be both.
  • As mentioned earlier, the way SystemContext is stored in ImageSource/ImageDestination implementations violates the context.Context rules (though, arguably, so does the cancelation behavior of http.Request/http.Response).
  • (types.SystemContext is commonly nil for programs which don’t need non-default behavior, and supporting that for context.Context values is not cleanly possible, nor even desirable—linters which validate the context.Context usage rules should warn about using nil.)

So, I think the two parameters should stay separate, like the current docker.CheckAuth.

We might consider renaming SystemContext to make the two easier to distinguish (CIOptions?), but that’s really a separate discussion and probably a separate PR, if we should do this at all.

Cc: @runcom if he disagrees.


If this PR adds the context.Context parameters, they should actually be used. This is most important for the blocking <- channel operations.

Similarly, though perhaps less urgently, I think many of the local-filesystem operations should be cancelable if they work on layers or whole images; copying a multi-gigabyte image may well take long enough that the caller may realize in the middle that the operation is not needed after all. AFAICT having a cancelable io.Copy helper would make this reasonably easy. OTOH the local disks tend to still be reasonably fast and the most important local storage mechanisms (containers-storage:) seem to not have implemented cancelation/interruptible operations so far, so this is not completely blocking for me; adding FIXME notes to the relevant places would be nice, though.


https://golang.org/pkg/context/#Background documents context.Background as “typically used by…tests”; if we are not actually trying to test cancelation, that’s what we should do. context.TODO is pretty much equivalent to context.Background, and exists primarily to make missing handling easy to find; cluttering the results with all our tests would not be helpful.

@@ -495,9 +496,9 @@ type diffIDResult struct {

// copyLayer copies a layer with srcInfo (with known Digest and possibly known Size) in src to dest, perhaps compressing it if canCompress,
// and returns a complete blobInfo of the copied layer, and a value for LayerDiffIDs if diffIDIsNeeded
func (ic *imageCopier) copyLayer(srcInfo types.BlobInfo) (types.BlobInfo, digest.Digest, error) {
func (ic *imageCopier) copyLayer(ctx context.Context, srcInfo types.BlobInfo) (types.BlobInfo, digest.Digest, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The blocking

		diffIDResult = <-diffIDChan

needs to abort if ctx is canceled.

@@ -548,7 +549,7 @@ func (ic *imageCopier) copyLayer(srcInfo types.BlobInfo) (types.BlobInfo, digest
// it copies a blob with srcInfo (with known Digest and possibly known Size) from srcStream to dest,
// perhaps compressing the stream if canCompress,
// and returns a complete blobInfo of the copied blob and perhaps a <-chan diffIDResult if diffIDIsNeeded, to be read by the caller.
func (ic *imageCopier) copyLayerFromStream(srcStream io.Reader, srcInfo types.BlobInfo,
func (ic *imageCopier) copyLayerFromStream(ctx context.Context, srcStream io.Reader, srcInfo types.BlobInfo,
Copy link
Collaborator

Choose a reason for hiding this comment

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

(Non-blocking: It would be nice to have diffIDComputationGoroutine reliably terminate if ctx is canceled.

It does terminate, ultimately due to the defer…pipeWriter.CloseWithError, but that’s a bit convoluted.

E.g. if a cancelable io.Copy helper were created, using it in the goroutine would be nice.)

@@ -607,7 +608,7 @@ func computeDiffID(stream io.Reader, decompressor compression.DecompressorFunc)
// perhaps sending a copy to an io.Writer if getOriginalLayerCopyWriter != nil,
// perhaps compressing it if canCompress,
// and returns a complete blobInfo of the copied blob.
func (c *copier) copyBlobFromStream(srcStream io.Reader, srcInfo types.BlobInfo,
func (c *copier) copyBlobFromStream(ctx context.Context, srcStream io.Reader, srcInfo types.BlobInfo,
Copy link
Collaborator

Choose a reason for hiding this comment

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

(Non-blocking: It would be nice to have compressGoroutine reliably terminate if ctx is canceled, just like diffIDComputationGoroutine.

Same for the

		_, err := io.Copy(ioutil.Discard, originalLayerReader)

In all these cases, having the ctx cancelation abort future reads from the Readers should be sufficient, but it’s convoluted.)

doc.go Outdated
@@ -13,12 +13,13 @@
// if err != nil {
// panic(err)
// }
// img, err := ref.NewImage(nil)
// ctx := context.Background()
// img, err := ref.NewImage(ctx)
Copy link
Collaborator

Choose a reason for hiding this comment

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

(Nit: The alignment is inconsistent.)

@@ -13,7 +14,7 @@ type archiveImageSource struct {

// newImageSource returns a types.ImageSource for the specified image reference.
// The caller must call .Close() on the returned ImageSource.
func newImageSource(ctx *types.SystemContext, ref archiveReference) (types.ImageSource, error) {
func newImageSource(ctx context.Context, ref archiveReference) (types.ImageSource, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This really should be actually cancelable, in the tarfile.NewSourceFromFileNewSourceFromStream:

_, err := io.Copy(tarCopyFile, inputStream)

part at least, which, although does not do anything over the network, may be copying many gigabytes of data and take several seconds.

@@ -389,7 +390,7 @@ func (d *ostreeImageDestination) PutSignatures(signatures [][]byte) error {
return nil
}

func (d *ostreeImageDestination) Commit() error {
func (d *ostreeImageDestination) Commit(ctx context.Context) error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

importBlob, fixFiles, generateTarSplitMetadata, and maybe more, can handle many-gigabyte files or directory structures and should be cancelable.

@@ -256,13 +256,13 @@ func (s *ostreeImageSource) readSingleFile(commit, path string) (io.ReadCloser,
}

// GetBlob returns a stream for the specified blob, and the blob's size.
func (s *ostreeImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
func (s *ostreeImageSource) GetBlob(ctx context.Context, info types.BlobInfo) (io.ReadCloser, int64, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The goroutine should probably be canceled by ctx.

@@ -284,7 +284,7 @@ func (s storageImageDestination) ShouldCompressLayers() bool {

// PutBlob stores a layer or data blob in our temporary directory, checking that any information
// in the blobinfo matches the incoming data.
func (s *storageImageDestination) PutBlob(stream io.Reader, blobinfo types.BlobInfo) (types.BlobInfo, error) {
func (s *storageImageDestination) PutBlob(ctx context.Context, stream io.Reader, blobinfo types.BlobInfo) (types.BlobInfo, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The

	_, err = io.Copy(diffID.Hash(), decompressed)

can copy many gigabytes of data and should be cancelable.

@@ -460,7 +460,7 @@ func (s *storageImageDestination) getConfigBlob(info types.BlobInfo) ([]byte, er
return nil, errors.New("blob not found")
}

func (s *storageImageDestination) Commit() error {
func (s *storageImageDestination) Commit(ctx context.Context) error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The

		layer, _, err := s.imageRef.transport.store.PutLayer(id, lastLayer, nil, "", false, diff)

line can copy many gigabytes of data, and should be cancelable. (I’m not sure that can be done right now.)

@@ -34,7 +34,7 @@ type tarballImageSource struct {
manifest []byte
}

func (r *tarballReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
func (r *tarballReference) NewImageSource(ctx context.Context) (types.ImageSource, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The

		n, err := io.Copy(ioutil.Discard, reader)

can copy many gigabytes of data and should be cancelable.

@novas0x2a
Copy link
Contributor Author

Hm, I'm willing to follow through with changes like splitting SystemContext and Context apart (which would also resolve the nil problem by making it a compile failure), and I can easily use Background instead of TODO in the tests (generally, I use TODO because those tests are the ones that would catch failures to use ctx in the code, but if you prefer Background, that's fine, too).

I'm probably not willing to take on the task of fixing the whole library re: context behavior, although there's clear value in that, my PR was basically me scratching my itch along the code paths I needed, and then propagating those changes as needed :)

Does that work for you?

@mtrmac
Copy link
Collaborator

mtrmac commented Mar 27, 2018

I’m fine with not handling the local FS copies; in the worst case I can add the FIXMEs in there myself.

I’d insist on having the two cases of blocking on a channel handled; that should be roughly

select {
case <-ctx.Done():
	return ctx.Err()
case theVariable <- theChannel:
}

but I guess I can trade that: if you are willing to split the SystemContext and Context, I’ll add the FIXMEs and the channel aborts — and the skopeo updates needed so that tests pass :). Does that make sense?


(generally, I use TODO because those tests are the ones that would catch failures to use ctx in the code, but if you prefer Background, that's fine, too)

I’m not sure what you mean; looking at the implementation, TODO and Background are instances of the same object that differ only in their String() return value. How does using TODO help catch failures to use ctx in the code?

@novas0x2a
Copy link
Contributor Author

novas0x2a commented Mar 27, 2018

Yeah, SGTM. It'll probably take me a few days, but I'll get to it.

I’m not sure what you mean; looking at the implementation, TODO and Background are instances of the same object that differ only in their String() return value. How does using TODO help catch failures to use ctx in the code?

Ah, sorry, I didn't mean the TODOs would, I meant that that they're real TODOs in the sense of indicating the need for future work; the tests they're in would be the things meant to catch failures to use the context. The next step on those TODOs might be to have a test-specific context in the suite that makes sure it cancels the context after each test, so you can't leak threads during the test (for example). But, either way, grepping for context.Background in the tests is just as easy as context.TODO; context.TODO is more for real code to indicate a booby-trap for people debugging something that landed there :)

- Network IO paths should react to cancels now.
- File IO paths generally still won't.
- `SystemContext` objects have been renamed to `sys` to leave `ctx`
  available for the stdlib context objects.

Signed-off-by: Mike Lundy <mike@fluffypenguin.org>
@novas0x2a
Copy link
Contributor Author

I think I've handled all the comments now, except for the file io paths. How's it look?

@mtrmac
Copy link
Collaborator

mtrmac commented Apr 9, 2018

@runcom RFC, please at least ACK the general direction and types.go.

@mtrmac
Copy link
Collaborator

mtrmac commented Apr 9, 2018

👍 Thanks!

I have prepared a skopeo PR at containers/skopeo#493 .

Note to self: also check a few of the newImage* for unnecessary parameters.

Approved with PullApprove

@mtrmac mtrmac mentioned this pull request Apr 10, 2018
@runcom
Copy link
Member

runcom commented Apr 10, 2018

LGTM (just checked types/types.go, it's awesome!)

Approved with PullApprove

@runcom
Copy link
Member

runcom commented Apr 10, 2018

skopeo's PR passing, so merging here

@runcom runcom merged commit e5b7a01 into containers:master Apr 10, 2018
mtrmac added a commit to containers/skopeo that referenced this pull request Apr 10, 2018
@novas0x2a novas0x2a deleted the context-everywhere branch April 10, 2018 17:50
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