[#2112] progress.Controller should own the progress.Writer to prevent leaks#2203
[#2112] progress.Controller should own the progress.Writer to prevent leaks#2203coryb merged 2 commits intomoby:masterfrom
Conversation
source/containerimage/pull.go
Outdated
| progressController := &controller.Controller{ | ||
| Writer: pw, | ||
| } | ||
| progressController := &controller.Controller{} |
There was a problem hiding this comment.
I think this loses the correct context for the progress. Eg. lazy blob pulls happening later should still be associated with the image where they are coming from. I think we need to read out the progress writer from context in here but not initialize it yet. Only read out constructor/callback.
There was a problem hiding this comment.
It is confusing with the multiple contexts involved here, I was hoping the ctx passed into the Start would be the right one, but sounds like not. If my understanding is correct, I think we have two options:
-
Capture
ctxat the time we create the&controller.Controller{}, perhaps store aprogressCtxin the struct for later use? For this I would just addfunc NewController(context.Context) progress.Controller -
Split
progress.FromContextinto two parts, one to just return the progress stored inctx.Value()so we can stash it in theController, and then another that does thenewWriter. If we go this route I think I would renameprogress.FromContexttoprogress.NewFromContext. Then add a newprogress.FromContextthat would just load progress fromctx.Value. Then renamenewWriter(Writer)toNewWriter(writer)so it can be used outside the package.
Thoughts?
There was a problem hiding this comment.
I went with option 2) and updated the PR, if you disagree should be easy enough to change.
util/progress/progress.go
Outdated
| // NewWriter returns a new progress writer based on the Writer argument. | ||
| // It is the callers responsibility to call Close on the Writer to prevent | ||
| // resource leaks. | ||
| func NewWriter(w Writer, opts ...WriterOption) Writer { |
There was a problem hiding this comment.
Generally ok with this approach but this public API makes little sense. Both New and From return writer, NewWriter takes writer as a parameter. I think better would be if FromContext returns ProgressFactory, a function that returns a new writer when called. NewWriter is not public.
There was a problem hiding this comment.
Okay, updated. I went with WriterFactory to avoid the stutter with progress.ProgressFactory.
util/progress/progress.go
Outdated
| // responsibility to Close the returned Writer to avoid resource leaks. | ||
| type WriterFactory func() (Writer, bool, context.Context) | ||
|
|
||
| // FromContext returns a ProgressFactory to generate new progress writers |
There was a problem hiding this comment.
| // FromContext returns a ProgressFactory to generate new progress writers | |
| // FromContext returns a WriterFactory to generate new progress writers |
| for _, o := range opts { | ||
| o(pw) | ||
| } | ||
| ctx = context.WithValue(ctx, contextKey, pw) |
There was a problem hiding this comment.
I think this should return . It is wrong for the callback to return context that was created by another stack. That goroutine is likely already completed and original context has been canceled. If you have a case where you need new context when (WriterFactory, bool, context.Context)newWriter is called then the callback itself should take a context and new writer is added to that context, not to the one that was passed into FromContext.
There was a problem hiding this comment.
I see that newWriter() result is added to the context. If that is the requirement and there isn't another way to reorganize this that means that my second proposal makes more sense. For the default case, you can add back NewFromContext helper so that caller does not need to pass context twice when from&new happen together.
There was a problem hiding this comment.
Okay, makes sense. Update the WriterFactory now takes a ctx and added NewFromContext wrapper to automatically call the WriterFactory with the same ctx passed in.
|
Looks like I broke this with #2195 merge Rebase should fix it. |
|
Test failing with an odd error: https://github.com/moby/buildkit/pull/2203/checks?check_run_id=2934987969#step:7:3822 |
…vent leaks Signed-off-by: Cory Bennett <cbennett@netflix.com>
this allows progress.Controller to manage the writer lifecycle Signed-off-by: Cory Bennett <cbennett@netflix.com>
|
Rebased and tests finally pass 🎉 |
fixes #2112
To prevent leaks with
Closenot being called onprogress.Writerthere is an expectation that everywhere weWriteto aprogress.Writerwe should also be callingClosewhen the writing is complete. I looked through the code and currently the only occurrence ofWritewithoutClosewas in theprogress.Controller. Moving the lifecycle management of theprogress.Writerinto the reference-counted implementation of the Controller fixes the leak.