-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[#2112] progress.Controller should own the progress.Writer to prevent leaks #2203
Conversation
source/containerimage/pull.go
Outdated
progressController := &controller.Controller{ | ||
Writer: pw, | ||
} | ||
progressController := &controller.Controller{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
ctx
at the time we create the&controller.Controller{}
, perhaps store aprogressCtx
in the struct for later use? For this I would just addfunc NewController(context.Context) progress.Controller
-
Split
progress.FromContext
into 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.FromContext
toprogress.NewFromContext
. Then add a newprogress.FromContext
that 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went with option 2) and updated the PR, if you disagree should be easy enough to change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
Close
not being called onprogress.Writer
there is an expectation that everywhere weWrite
to aprogress.Writer
we should also be callingClose
when the writing is complete. I looked through the code and currently the only occurrence ofWrite
withoutClose
was in theprogress.Controller
. Moving the lifecycle management of theprogress.Writer
into the reference-counted implementation of the Controller fixes the leak.