Skip to content

image/copy: Fix missing progress reporting for chunked layers#848

Open
simek-m wants to merge 4 commits into
containers:mainfrom
simek-m:469-chunked-progress
Open

image/copy: Fix missing progress reporting for chunked layers#848
simek-m wants to merge 4 commits into
containers:mainfrom
simek-m:469-chunked-progress

Conversation

@simek-m
Copy link
Copy Markdown

@simek-m simek-m commented May 15, 2026

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.
This PR adds missing reporting and refactors parts shared with progressReader.

The Podman REST API images/pull with the pullProgress flag introduced in containers/podman#28224 did not get progress updates from the channel and the output stream lacked events for partial blobs.

Test added in: containers/podman#28713

Fixes: #469

API pullProgress stream with this fix

➜  bin/podman system service --log-level=trace --time=0 tcp://localhost:8888 2> >(grep -i partial >&2)
time="2026-05-14T15:18:48Z" level=debug msg="Retrieved partial blob sha256:0ea8cd0810b88e14631c1064bc882a6ff5e664860ff768c70102716ab64e7f0b"
time="2026-05-14T15:18:49Z" level=debug msg="Retrieved partial blob sha256:da9bf6c3f7cfad233b1e4de2f6dbf010d48a73dfc14395bf634f45a413e4dbb0"
time="2026-05-14T15:18:49Z" level=debug msg="Retrieved partial blob sha256:8ad5c254127956ee8bea277ea593be376859bff00be2e987b4e6905c2e8d5538"
time="2026-05-14T15:18:49Z" level=debug msg="Retrieved partial blob sha256:7fd8aa9622b5f5f23bee3c4035235d929a4fe5da860b52bdda81ff432cb82b51"
time="2026-05-14T15:18:49Z" level=debug msg="Retrieved partial blob sha256:fc285a90f91602992c8cb458431ffe63fcb61295489191d153e366704885d936"
time="2026-05-14T15:18:49Z" level=debug msg="Retrieved partial blob sha256:400129928e227203c8f2146c61b5142a362ba9afdbd37fb459d61f7829983fe4"
➜  ~ podman rmi -f localhost:15000/chunked-test
Untagged: localhost:15000/chunked-test:latest
Deleted: be9027b559bccd87b7e10e472e464beadace2012e4dd69615a3a72682d83f006
➜  ~ curl --silent -X POST 'http://localhost:8888/v6.0.0/libpod/images/pull?reference=localhost:15000/chunked-test:latest&pullProgress=true' | jq .
{
  "status": "pulling",
  "stream": "Trying to pull localhost:15000/chunked-test:latest...\n"
}
{
  "status": "pulling",
  "stream": "Getting image source signatures\n"
}
{
  "status": "pulling",
  "stream": "Copying blob sha256:8ad5c254127956ee8bea277ea593be376859bff00be2e987b4e6905c2e8d5538\n"
}
{
  "status": "pulling",
  "stream": "Copying blob sha256:400129928e227203c8f2146c61b5142a362ba9afdbd37fb459d61f7829983fe4\n"
}
{
  "status": "pulling",
  "stream": "Copying blob sha256:09aa76a8de5384893277e1a0ba676ed7d063e4089c5b2966362e4de9eb37efb6\n"
}
{
  "status": "pulling",
  "stream": "Copying blob sha256:0ea8cd0810b88e14631c1064bc882a6ff5e664860ff768c70102716ab64e7f0b\n"
}
{
  "status": "pulling",
  "stream": "Copying blob sha256:7fd8aa9622b5f5f23bee3c4035235d929a4fe5da860b52bdda81ff432cb82b51\n"
}
{
  "status": "pulling",
  "stream": "Copying blob sha256:da9bf6c3f7cfad233b1e4de2f6dbf010d48a73dfc14395bf634f45a413e4dbb0\n"
}
{
  "status": "pulling",
  "stream": "Starting to pull artifact",
  "pullProgress": {
    "status": "pulling",
    "total": 10499078,
    "progressComponentID": "sha256:8ad5c254127956ee8bea277ea593be376859bff00be2e987b4e6905c2e8d5538"
  }
}
{
  "status": "pulling",
  "stream": "Starting to pull artifact",
  "pullProgress": {
    "status": "pulling",
    "total": 5250753,
    "progressComponentID": "sha256:400129928e227203c8f2146c61b5142a362ba9afdbd37fb459d61f7829983fe4"
  }
}
{
  "status": "pulling",
  "stream": "Starting to pull artifact",
  "pullProgress": {
    "status": "pulling",
    "total": 6299391,
    "progressComponentID": "sha256:0ea8cd0810b88e14631c1064bc882a6ff5e664860ff768c70102716ab64e7f0b"
  }
}
{
  "status": "pulling",
  "stream": "Starting to pull artifact",
  "pullProgress": {
    "status": "pulling",
    "total": 6299400,
    "progressComponentID": "sha256:7fd8aa9622b5f5f23bee3c4035235d929a4fe5da860b52bdda81ff432cb82b51"
  }
}
{
  "status": "pulling",
  "stream": "Starting to pull artifact",
  "pullProgress": {
    "status": "pulling",
    "total": 5250775,
    "progressComponentID": "sha256:da9bf6c3f7cfad233b1e4de2f6dbf010d48a73dfc14395bf634f45a413e4dbb0"
  }
}
{
  "status": "pulling",
  "stream": "Artifact already exists",
  "pullProgress": {
    "status": "skipped",
    "progressComponentID": "sha256:09aa76a8de5384893277e1a0ba676ed7d063e4089c5b2966362e4de9eb37efb6"
  }
}
{
  "status": "pulling",
  "stream": "Copying blob sha256:fc285a90f91602992c8cb458431ffe63fcb61295489191d153e366704885d936\n"
}
{
  "status": "pulling",
  "stream": "Starting to pull artifact",
  "pullProgress": {
    "status": "pulling",
    "total": 6299404,
    "progressComponentID": "sha256:fc285a90f91602992c8cb458431ffe63fcb61295489191d153e366704885d936"
  }
}
{
  "status": "pulling",
  "stream": "Pulling artifact",
  "pullProgress": {
    "status": "pulling",
    "current": 6086,
    "total": 6299391,
    "progressComponentID": "sha256:0ea8cd0810b88e14631c1064bc882a6ff5e664860ff768c70102716ab64e7f0b"
  }
}
{
  "status": "pulling",
  "stream": "Pulling artifact",
  "pullProgress": {
    "status": "pulling",
    "current": 10246,
    "total": 10499078,
    "progressComponentID": "sha256:8ad5c254127956ee8bea277ea593be376859bff00be2e987b4e6905c2e8d5538"
  }
}
{
  "status": "pulling",
  "stream": "Pulling artifact",
  "pullProgress": {
    "status": "pulling",
    "current": 6042,
    "total": 5250753,
    "progressComponentID": "sha256:400129928e227203c8f2146c61b5142a362ba9afdbd37fb459d61f7829983fe4"
  }
}
{
  "status": "pulling",
  "stream": "Pulling artifact",
  "pullProgress": {
    "status": "pulling",
    "current": 6089,
    "total": 6299400,
    "progressComponentID": "sha256:7fd8aa9622b5f5f23bee3c4035235d929a4fe5da860b52bdda81ff432cb82b51"
  }
}
{
  "status": "pulling",
  "stream": "Pulling artifact",
  "pullProgress": {
    "status": "pulling",
    "current": 6058,
    "total": 5250775,
    "progressComponentID": "sha256:da9bf6c3f7cfad233b1e4de2f6dbf010d48a73dfc14395bf634f45a413e4dbb0"
  }
}
{
  "status": "pulling",
  "stream": "Pulling artifact",
  "pullProgress": {
    "status": "pulling",
    "current": 6086,
    "total": 6299404,
    "progressComponentID": "sha256:fc285a90f91602992c8cb458431ffe63fcb61295489191d153e366704885d936"
  }
}
{
  "status": "pulling",
  "stream": "Pulling artifact",
  "pullProgress": {
    "status": "pulling",
    "current": 6299225,
    "total": 6299391,
    "progressComponentID": "sha256:0ea8cd0810b88e14631c1064bc882a6ff5e664860ff768c70102716ab64e7f0b"
  }
}
{
  "status": "pulling",
  "stream": "Pulling artifact",
  "pullProgress": {
    "status": "pulling",
    "current": 10498912,
    "total": 10499078,
    "progressComponentID": "sha256:8ad5c254127956ee8bea277ea593be376859bff00be2e987b4e6905c2e8d5538"
  }
}
{
  "status": "pulling",
  "stream": "Pulling artifact",
  "pullProgress": {
    "status": "pulling",
    "current": 6299234,
    "total": 6299400,
    "progressComponentID": "sha256:7fd8aa9622b5f5f23bee3c4035235d929a4fe5da860b52bdda81ff432cb82b51"
  }
}
{
  "status": "pulling",
  "stream": "Pulling artifact",
  "pullProgress": {
    "status": "pulling",
    "current": 6299237,
    "total": 6299404,
    "progressComponentID": "sha256:fc285a90f91602992c8cb458431ffe63fcb61295489191d153e366704885d936"
  }
}
{
  "status": "pulling",
  "stream": "Pulling artifact",
  "pullProgress": {
    "status": "pulling",
    "current": 5250586,
    "total": 5250753,
    "progressComponentID": "sha256:400129928e227203c8f2146c61b5142a362ba9afdbd37fb459d61f7829983fe4"
  }
}
{
  "status": "pulling",
  "stream": "Artifact pulled successfully",
  "pullProgress": {
    "status": "success",
    "current": 6299225,
    "total": 6299391,
    "progressComponentID": "sha256:0ea8cd0810b88e14631c1064bc882a6ff5e664860ff768c70102716ab64e7f0b"
  }
}
{
  "status": "pulling",
  "stream": "Pulling artifact",
  "pullProgress": {
    "status": "pulling",
    "current": 5250607,
    "total": 5250775,
    "progressComponentID": "sha256:da9bf6c3f7cfad233b1e4de2f6dbf010d48a73dfc14395bf634f45a413e4dbb0"
  }
}
{
  "status": "pulling",
  "stream": "Artifact pulled successfully",
  "pullProgress": {
    "status": "success",
    "current": 5250607,
    "total": 5250775,
    "progressComponentID": "sha256:da9bf6c3f7cfad233b1e4de2f6dbf010d48a73dfc14395bf634f45a413e4dbb0"
  }
}
{
  "status": "pulling",
  "stream": "Artifact pulled successfully",
  "pullProgress": {
    "status": "success",
    "current": 10498912,
    "total": 10499078,
    "progressComponentID": "sha256:8ad5c254127956ee8bea277ea593be376859bff00be2e987b4e6905c2e8d5538"
  }
}
{
  "status": "pulling",
  "stream": "Artifact pulled successfully",
  "pullProgress": {
    "status": "success",
    "current": 6299234,
    "total": 6299400,
    "progressComponentID": "sha256:7fd8aa9622b5f5f23bee3c4035235d929a4fe5da860b52bdda81ff432cb82b51"
  }
}
{
  "status": "pulling",
  "stream": "Artifact pulled successfully",
  "pullProgress": {
    "status": "success",
    "current": 6299237,
    "total": 6299404,
    "progressComponentID": "sha256:fc285a90f91602992c8cb458431ffe63fcb61295489191d153e366704885d936"
  }
}
{
  "status": "pulling",
  "stream": "Artifact pulled successfully",
  "pullProgress": {
    "status": "success",
    "current": 5250586,
    "total": 5250753,
    "progressComponentID": "sha256:400129928e227203c8f2146c61b5142a362ba9afdbd37fb459d61f7829983fe4"
  }
}
{
  "status": "pulling",
  "stream": "Copying config sha256:be9027b559bccd87b7e10e472e464beadace2012e4dd69615a3a72682d83f006\n"
}
{
  "status": "pulling",
  "stream": "Starting to pull artifact",
  "pullProgress": {
    "status": "pulling",
    "total": 1859,
    "progressComponentID": "sha256:be9027b559bccd87b7e10e472e464beadace2012e4dd69615a3a72682d83f006"
  }
}
{
  "status": "pulling",
  "stream": "Artifact pulled successfully",
  "pullProgress": {
    "status": "success",
    "current": 1859,
    "total": 1859,
    "progressComponentID": "sha256:be9027b559bccd87b7e10e472e464beadace2012e4dd69615a3a72682d83f006"
  }
}
{
  "status": "pulling",
  "stream": "Writing manifest to image destination\n"
}
{
  "status": "success",
  "images": [
    "be9027b559bccd87b7e10e472e464beadace2012e4dd69615a3a72682d83f006"
  ]

@github-actions github-actions Bot added the image Related to "image" package label May 15, 2026
Comment thread image/copy/single.go
}
// 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 {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.)

Copy link
Copy Markdown
Contributor

@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.

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.

Comment thread image/copy/blob_chunk_accessor_proxy.go Outdated
}
total += int64(c.Length)
}
// Report read bytes if possible.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(This comment adds no value over the code itself, please drop.)

Comment thread image/copy/blob_chunk_accessor_proxy.go Outdated
@@ -0,0 +1,50 @@
package copy
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This works, but I think progress.go would be a better name long-term.

We don’t really need a separate file for every small type we define (the language does not require it, at least) — and if we move towards sharing more of the progress-reporting proxies (in particular, for containers/podman#20634 moving away from the current bar.ProxyReader), having them all in progress.go would allow keeping them together and making sure they are consistent.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I wanted to move it because progress_bars.go was no longer true, but wasn't sure about having this small a file. I could see other examples, but noticed the tendency to keep more code together.

progress was my other option, I'll go with that.

Comment thread image/copy/progress_channel.go Outdated
Comment on lines +30 to +31
// reportRead fires the types.ProgressEventRead event with `bytesRead`
// to its progress channel.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  1. “fires… with bytesRead” is not actually true most of the time
  2. If we have this abstraction, fine, but then it would be useful for the abstraction to abstract the mechanism, i.e. for the documentation to say what the semantics is, not “fires $internalImplementationDetail”. E.g. “indicates to the internal channel that the progress has been finished” is not at all ideal but it’s better than “fires ProgressEventDone” because the user then has to look up what ProgressEventDone means. Compare https://github.com/containers/container-libs/blob/main/image/AGENTS.md#documentation .

Comment thread image/copy/blob_chunk_accessor_proxy.go Outdated
// fully fetches the remaining data from the offset to the end of the blob.
//
// blobChunkAccessorProxy.GetBlobAt also updates a *progressBar
// and *progressReporter (if non-nil) with the number of bytes read.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The comment here was exactly copied from the interface definition … and I think that might be sufficient, just as a reminder of the contract.

Comment thread image/copy/progress_channel.go Outdated
// that a new artifact will be read
channel <- types.ProgressProperties{
// reportNewArtifact fires types.ProgressEventNewArtifact to its progress channel.
func (r *progressReporter) reportNewArtifact() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this is better done in the constructor; that makes the callers shorter and they can’t forget.

In particular the mechanism of the object requires lastUpdate to be initialized. Now, technically, we could just document that creators of the object must immediately call reportNewArtifact but it’s just easier for everyone to use a constructor.

(Just because Go allows external callers to initialize objects themselves, that doesn’t make it a good idea — it completely breaks the idea of encapsulation or abstraction.)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm used to the doctrine that constructors should only initialize in Go, but that's completely subjective.

The lastUpdate is indeed problematic, good point. I'll stick to the constructor for that.

(Yes, it's a big limitation of "constructors" in Go that you cannot prevent struct literal initialization.)

Comment thread image/copy/progress_channel.go Outdated
Comment on lines +57 to +58
// progressReader is an io.Reader that reports its progress to
// an underlying *progressReporter.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The API is not shaped that way.

The comment seems to suggest that users should construct a progressReporter and then use it from a separate progressReader, but actually progressReader is a ~subclass and users are not expected to interact with the progressReporter as a separate instance.

In general, it’s surprising that the shape of integration between progressReader (a ~superclass) and blobChunkAccessorProxy (a property updated after initialization) is so different.

Comment thread image/copy/single.go Outdated
Comment on lines +807 to +808
// Setup progress reporting and report a new artifact event
// if the channel available and a non-zero interval set.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Already obvious in the code, please drop the comment.

Comment thread image/copy/single.go Outdated

// Report completion for an artifact if possible.
if proxy.reporter != nil {
proxy.reporter.reportDone()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • Reaching into proxy which now “owns” the reporter this way is a bit inelegant (and reportedly an anti-pattern in OO philosophy, which I don’t care about so much). Who is responsible for doing this? If it is this function, it could just as well hold to the reporter itself… (see elsewhere about field vs. superclass vs…)
  • So when is reportDone expected to be called? Always or only on success? copyBlobFromStream does it unconditionally. [See what I mean about documenting methods at the relevant abstraction level, not “fires …”?]
  • I didn’t realize originally, and this complicates things: We need to do something reasonable on ErrFallbackToOrdinaryLayerDownload. I suppose reporting the original access as done / aborted, and starting a new report with the same progressComponentID but starting with offset 0 would be plausible… or maybe we need a new ProgressEventRestarting event?! I suppose this needs looking at the Podman remote API to see whether that would like to receive some specific data.

Comment thread image/copy/single.go
}
// 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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.)

Comment thread image/copy/progress_channel.go Outdated
artifact types.BlobInfo // The blob metadata which is currently being progressed
lastUpdate time.Time // The last time a progress channel event was sent
offset uint64 // The currently downloaded size in bytes
offsetUpdate uint64 // The number of bytes downloaded within the last update interval
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
offsetUpdate uint64 // The number of bytes downloaded within the last update interval
offsetUpdate uint64 // The number of bytes downloaded since lastUpdate

then — it’s shorter and clearer.

Comment thread image/copy/blob_chunk_accessor_proxy.go Outdated
//
// blobChunkAccessorProxy.GetBlobAt also updates a *progressBar
// and *progressReporter (if non-nil) with the number of bytes read.
func (s *blobChunkAccessorProxy) GetBlobAt(ctx context.Context, info types.BlobInfo, chunks []private.ImageSourceChunk) (chan io.ReadCloser, chan error, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: A unit test wouldn’t hurt.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I added tests for the reporter itself but not for GetBlobAt as it interacts with the progressBar and wasn't sure it would add some value to test it separately.

If you think, they should exist, I'll, of course, add them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ACK, we don’t want/need to mock the MPB progress bars for this.

@simek-m
Copy link
Copy Markdown
Author

simek-m commented May 15, 2026

@mtrmac Thank you, I responded to some of the more obvious comments. I'll go through the rest later and change the code accordingly.

simek-m added 2 commits May 20, 2026 13:24
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 reporting and refactor
parts shared with `progressReader`.

Fixes: containers#469
Signed-off-by: Marek Simek <msimek@redhat.com>
Signed-off-by: Marek Simek <msimek@redhat.com>
@simek-m simek-m force-pushed the 469-chunked-progress branch from e67f898 to 80883b6 Compare May 20, 2026 11:24
// TestNewProgressReporter verifies that constructing a reporter
// signals a new artifact event.
func TestNewProgressReporter(t *testing.T) {
channel := make(chan types.ProgressProperties, 1)
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The buffered channel is used for simplicity to avoid a race because the newProgressReporter constructor sends the event and then returns.

@simek-m
Copy link
Copy Markdown
Author

simek-m commented May 20, 2026

I hope changes in 80883b6 reflect your comments and what we discussed.

  • Now reportDone() should only be called on success (there's no event for errors) and it's the responsibility of the caller.
  • The ErrFallbackToOrdinaryLayerDownload path resets progress of the re-used reporter.

Edit: There was an omission in the previous commit.
However, I don't know if it's correct not to have a reporter for imageCopier.copyConfig and I'm not sure what I did is a good approach and if there should be any reset/reuse.

simek-m added 2 commits May 20, 2026 14:53
Signed-off-by: Marek Simek <msimek@redhat.com>
…layers

Signed-off-by: Marek Simek <msimek@redhat.com>
Comment thread image/copy/blob.go
)
defer progressReader.reportDone()
stream.reader = progressReader
// === Wrap stream with progress reporting if a reporter was provided.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(For these “pipeline steps” (“section headers”), documenting what happens is more important than how exactly, unless the “how exactly” is non-trivial.)

Comment thread image/copy/progress.go
@@ -145,11 +145,12 @@ func (bar *progressBar) mark100PercentComplete() {
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it would still make sense to have progress_bars.go dealing with mpb and the specifics of the layout, vs. progress.go for the things shared between progress_bars.go and progress_channel.go (i.e. the blobChunkAccessorProxy).

artifact: artifact,
lastUpdate: time.Now(),
offset: 0,
offsetUpdate: 0,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(Absolutely non-blocking: I think keeping explicit initializers here is a tiny bit better because we do rely on having these values exactly.)

lastUpdate time.Time
offset uint64
offsetUpdate uint64
// progressReporter facilitates progress reporting through its
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

“facilitates” means nothing specific.

Maybe something vaguely like “reports progress about a single blob to a types.ProgressProperties channel [and supports re-starting from zero without reporting the negative progress through the channel]”

// the processing has to be re-started
// (e.g. ErrFallbackToOrdinaryLayerDownload).
func (r *progressReporter) reset() {
r.offset = 0
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In this situation, we don’t want the GUI progress bars to decrease; just note that the current offset is 0 but remember that we reported N, and then report no progress until we reach N again.

Comment thread image/copy/single.go
}

destInfo, err := ic.copyBlobFromStream(ctx, bytes.NewReader(configBlob), srcInfo, nil, true, false, bar, -1, false)
destInfo, err := ic.copyBlobFromStream(ctx, bytes.NewReader(configBlob), srcInfo, nil, true, false, bar, -1, false, nil)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Configs should still report progress, discussed elsewhere.

Comment thread image/copy/single.go
}
}

// Only report completion on success.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(Discussed elsewhere: This could happen in copyBlobFromStream, as long as the function documents that. Alternatively, allow a no-op reporter where we can reporter.reportDone() without the condition.)

Comment thread image/copy/single.go
}
if ic.c.options.Progress != nil && ic.c.options.ProgressInterval > 0 {
reporter = newProgressReporter(ic.c.options.Progress, ic.c.options.ProgressInterval, srcInfo)
proxy.reporter = reporter
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think reordering to create the reporter first and the proxy second would be a bit cleaner.

Comment thread image/copy/blob_chunk_accessor_proxy.go Outdated
//
// blobChunkAccessorProxy.GetBlobAt also updates a *progressBar
// and *progressReporter (if non-nil) with the number of bytes read.
func (s *blobChunkAccessorProxy) GetBlobAt(ctx context.Context, info types.BlobInfo, chunks []private.ImageSourceChunk) (chan io.ReadCloser, chan error, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ACK, we don’t want/need to mock the MPB progress bars for this.

assert.Equal(t, types.ProgressProperties{
Event: types.ProgressEventNewArtifact,
Artifact: artifact,
}, <-channel, "constructor should send a new artifact event")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(I think it would help reliability of the tests to have a consumeNonblocking helper that returns all current []ProgressProperties; we could then also test that operations within the internal report nothing.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

image Related to "image" package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants