image/copy: Fix missing progress reporting for chunked layers#848
Conversation
| } | ||
| // Setup progress reporting and report a new artifact event | ||
| // if the channel available and a non-zero interval set. | ||
| if ic.c.options.Progress != nil && ic.c.options.ProgressInterval > 0 { |
There was a problem hiding this comment.
I was thinking of moving the nil checks from the caller side to the methods and allow nil receivers to clean it up a bit, but there is only one special-cased instance of this pattern (or two) in this codebase and it would be confusing, I think.
There was a problem hiding this comment.
This is not immediately relevant (bar.ProxyReader breaks it anyway), but various code in the standard library special-cases known implementations of io.Reader and the like (e.g., but not only, checking for things like WriterTo); so adding proxy objects might have a non-obvious performance cost — it’s better to make the whole proxy usage conditional than to always interpose a proxy and then have it be a no-op under some conditions.
(This does not really apply to GetBlobAt because that’s our private interface.)
mtrmac
left a comment
There was a problem hiding this comment.
Thanks!
Most importantly, the Podman-side tests seem broken/insufficient.
I don’t feel strongly about adding the extra progressReporter abstraction — but if we have it, it should be used consistently in the two users.
| } | ||
| // Setup progress reporting and report a new artifact event | ||
| // if the channel available and a non-zero interval set. | ||
| if ic.c.options.Progress != nil && ic.c.options.ProgressInterval > 0 { |
There was a problem hiding this comment.
This is not immediately relevant (bar.ProxyReader breaks it anyway), but various code in the standard library special-cases known implementations of io.Reader and the like (e.g., but not only, checking for things like WriterTo); so adding proxy objects might have a non-obvious performance cost — it’s better to make the whole proxy usage conditional than to always interpose a proxy and then have it be a no-op under some conditions.
(This does not really apply to GetBlobAt because that’s our private interface.)
|
@mtrmac Thank you, I responded to some of the more obvious comments. I'll go through the rest later and change the code accordingly. |
e67f898 to
80883b6
Compare
| // TestNewProgressReporter verifies that constructing a reporter | ||
| // signals a new artifact event. | ||
| func TestNewProgressReporter(t *testing.T) { | ||
| channel := make(chan types.ProgressProperties, 1) |
There was a problem hiding this comment.
The buffered channel is used for simplicity to avoid a race because the newProgressReporter constructor sends the event and then returns.
|
I hope changes in 80883b6 reflect your comments and what we discussed.
Edit: There was an omission in the previous commit. |
| // it's higher than the offset reached before the restart to | ||
| // avoid confusing behavior in consumers of the events | ||
| // (skipping back). | ||
| type channelProgressReporter struct { |
There was a problem hiding this comment.
Just to make sure, is it ok not worry about thread-safety here?
There was no synchronization in progressReader before my changes.
There was a problem hiding this comment.
Yes; we have one goroutine per blob, so that naturally synchronizes the per-blob operations; and io.Reader is generally not thread-safe, so this is not making anything worse.
1c7ef0e to
bcc8be6
Compare
mtrmac
left a comment
There was a problem hiding this comment.
A full review now; just some nits / cleanups, mostly in tests.
| if err != nil { | ||
| return types.BlobInfo{}, err | ||
| } | ||
| reporter.reportDone() |
There was a problem hiding this comment.
(Absolutely non-blocking: Being consistent about the location of the empty line, and the relative ordering of reportDone/mark100PercentComplete would make it clearer that the 3 code paths are consistent.)
|
|
||
| // After interval, reportRead(15): nothing (15 < 20 already reported). | ||
| time.Sleep(2 * interval) | ||
| r.reportRead(15) |
There was a problem hiding this comment.
Non-blocking: A test for /* no sleep */ reportRead(); reportDone() in ~this situation wouldn’t hurt.
There was a problem hiding this comment.
Added a new test TestProgressReporterResetIntervalNotElapsed.
There was a problem hiding this comment.
For the record, that test does not test the "15 < 20 already reported" case.
The `PutBlobPartial` code path only updated a progress bar, but it did not report its progress to the `copy.Options.Progress` channel for chunked layers. Add missing progress reporting, tests and refactor parts shared with `progressReader`. Fixes: podman-container-tools#469 Signed-off-by: Marek Simek <msimek@redhat.com>
bcc8be6 to
c6c5ff4
Compare
The reader tests did not use the tested progressReader wrapper. Use progressReader in the tests and remove goroutines. Signed-off-by: Marek Simek <msimek@redhat.com>
The
PutBlobPartialcode path only updated a progress bar, but it did not report its progress to thecopy.Options.Progresschannel for chunked layers.This PR adds missing reporting and refactors parts shared with
progressReader.The Podman REST API
images/pullwith thepullProgressflag introduced in podman-container-tools/podman#28224 did not get progress updates from the channel and the output stream lacked events for partial blobs.Test added in: podman-container-tools/podman#28713
Fixes: #469
API pullProgress stream with this fix