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

Improve progress reader to report start/end and offset update #732

Merged
merged 1 commit into from
Oct 30, 2019

Conversation

saschagrunert
Copy link
Member

@saschagrunert saschagrunert commented Oct 22, 2019

The progress reader will be now created via the new newProgressReader
function. This way we have the possibility to trigger a new event called
ProgressEventNewArtifact and ProgressEventDone to indicate the
boundaries of the artifact download to the API consumer. During the
download, we additionally provide the OffsetUpdate, which can be used
to report the downloaded data during the last elapsed time interval.

Unit tests have been added as well to the progress reader, whereas the
documentation for the new types has been enhanced as well.

Needed for metrics improvements for CRI-O: cri-o/cri-o#2912

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, sure, this works, but I’m a bit worried that it may not do quite what the caller expects.

  • Is “the first time Read returns” really exactly the right moment? If the source is very slow, the caller won’t receive any report about trying to work with the blob until after the first data/timeout arrives. Should the first progress indication about the artifact happen at the very start of copyBlobFromStream, before asking for the first byte? (Or, actually, all the way up before TryReusingBlob and the like, so that it captures the delays communicating over HTTP and the like, and setting up the streams, not just waiting for data?)
  • Note that PutBlob may never actually read all of the data (well, it may not read any), e.g. if the blob already exists on the destination (well, if it was uploaded after TryReusingBlob fails). Should the last update be conditioned on r.artifact.Size, or be determined by code flow using a defer or something like that? (r.artifact.Size can even be -1 to mean “unknown”!) And should we, in that case, somehow tell the caller that the artifact is done (by lying and saying Offset = Size, or by decreasing Size)?

Neither is really a big show-stopper to me, and I’m not sure that CRI-O cares about either of the two at all. Thinking too far in this direction would lead to a complete redesign of the progress interface, which is currently rather a crude hack, to be much more detailed about the semantics, and I suppose that’s not all that needed right now — or is it?

@saschagrunert
Copy link
Member Author

saschagrunert commented Oct 24, 2019

Overall, sure, this works, but I’m a bit worried that it may not do quite what the caller expects.

Hey @mtrmac, thanks for the first look. My main intention was to have something which changes
the library as little as possible, so I'm totally open to some rework here.

Is “the first time Read returns” really exactly the right moment? If the source is very slow, the caller won’t receive any report about trying to work with the blob until after the first data/timeout arrives. Should the first progress indication about the artifact happen at the very start of copyBlobFromStream, before asking for the first byte? (Or, actually, all the way up before TryReusingBlob and the like, so that it captures the delays communicating over HTTP and the like, and setting up the streams, not just waiting for data?)

I would expect (as a user) that the progress fires the first time a connection with the server (stream) has been successfully setup and no data got transferred. Maybe a progress state (CONNECTED) would be a good indicator for this.

Note that PutBlob may never actually read all of the data (well, it may not read any), e.g. if the blob already exists on the destination (well, if it was uploaded after TryReusingBlob fails). Should the last update be conditioned on r.artifact.Size, or be determined by code flow using a defer or something like that? (r.artifact.Size can even be -1 to mean “unknown”!) And should we, in that case, somehow tell the caller that the artifact is done (by lying and saying Offset = Size, or by decreasing Size)?

The caller needs the information that data has been skipped, because this will be an additional metric in CRI-O.

Neither is really a big show-stopper to me, and I’m not sure that CRI-O cares about either of the two at all. Thinking too far in this direction would lead to a complete redesign of the progress interface, which is currently rather a crude hack, to be much more detailed about the semantics, and I suppose that’s not all that needed right now — or is it?

Yeah, I'm not sure. The first time I used this API I must honestly admit that I expected something different from a usage perspective. I would assume that the progress bar display is handled by a more higher level library (like podman), whereas the API here works like:

  1. Tell the library that I want to copy an image from A to B, where the copy function only returns a channel
  2. I can now listen on that channel and get events (connected, pulling, interrupted, done), whereas the pulling events get triggered periodically and the library calculates the overall download status for me (packed in events data).

@mtrmac
Copy link
Collaborator

mtrmac commented Oct 24, 2019

My main intention was to have something which changes
the library as little as possible

That’s very fair, and I don’t at at all want to insist on a big (API-breaking) redesign. It’s perfectly fine to do a small incremental improvement, as long as it’s clear that it still has limitations.

Neither is really a big show-stopper to me, and I’m not sure that CRI-O cares about either of the two at all. Thinking too far in this direction would lead to a complete redesign of the progress interface, which is currently rather a crude hack, to be much more detailed about the semantics, and I suppose that’s not all that needed right now — or is it?

Yeah, I'm not sure. The first time I used this API I must honestly admit that I expected something different from a usage perspective. I would assume that the progress bar display is handled by a more higher level library (like podman), whereas the API here works like:

The current progress bars are handled by copy.Image directly, and that is managed in more places than progressReader. Yes, it would be conceptually attractive for both to be built on the same abstraction. OTOH, that’s a fair amount of work — especially knowing about #611 , which among other things, included fairly significant extensions to the progress bar model as well (to be able to switch between status updates / progress status, for the same blob / unit of work).


  • I would expect (as a user) that the progress fires the first time a connection with the server (stream) has been successfully setup and no data got transferred.
  • The caller needs the information that data has been skipped, because this will be an additional metric in CRI-O.

Doing just these two might be easy enough in the ~current model, see the code comments:

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.

My only real quibble is with the LastRead field name; these are just possible ways to extend this PR.

copy/copy.go Outdated
@@ -1159,7 +1159,7 @@ func (c *copier) copyBlobFromStream(ctx context.Context, srcStream io.Reader, sr
channel: c.progress,
interval: c.progressInterval,
artifact: srcInfo,
lastTime: time.Now(),
lastTime: time.Time{},
Copy link
Collaborator

Choose a reason for hiding this comment

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

copyBlobFromStream should ideally not need to know about the time.Time{} special case; this rather calls for a newProgressReader() constructor that handles that.

  • That constructor could legitimately fire a “dealing with new artifact, 0% progress” event, without making this code any more complex.
  • It would then be very reasonable to add a defer func() { progressReader.reportDone(didReadAll, errorIfAny) } or something like that (let’s not add data that no-one needs), and ensure that there really is a 100%/skipped report.
  • Perhaps we could also split the constructor from a progressReader.assignSource(destStream) call, and call the constructor earlier than here, to get the 0% indication in the right place.

@mtrmac
Copy link
Collaborator

mtrmac commented Oct 24, 2019

OTOH, that’s a fair amount of work — especially knowing about #611 , which among other things, included fairly significant extensions to the progress bar model as well (to be able to switch between status updates / progress status, for the same blob / unit of work).

(To be clear, that work was good and it would be a shame to lose it — and splitting that work into a smaller PR focused just on progress bars could help. Still, designing a well-typed stable API with detailed events about the progress is much more work than this PR.)

@saschagrunert saschagrunert force-pushed the reader-adaptions branch 2 times, most recently from daf6d55 to 42fdc78 Compare October 25, 2019 09:12
@saschagrunert saschagrunert changed the title Add last read value to progress reader Improve progress reader to report start/end and offset update Oct 25, 2019
@saschagrunert saschagrunert marked this pull request as ready for review October 25, 2019 09:12
@saschagrunert
Copy link
Member Author

Ready for review, @mtrmac and @vrothberg PTAL.

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.

Thanks, this is great!

I’ll probably not get to make a careful review before Tuesday, so just a quick pass — other than the test question, it’s all nits and I don’t expect anything more serious to come up.

) *progressReader {
artifact := types.BlobInfo{Size: 10}

go func() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Note to self: does the testing framework know it has to wait for all spawned goroutines?

@@ -0,0 +1,94 @@
package copy
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yay, tests. Great!

@vrothberg
Copy link
Member

Needs rebase.

The progress reader will be now created via the new `newProgressReader`
function. This way we have the possibility to trigger a new event called
`ProgressEventNewArtifact` and `ProgressEventDone` to indicate the
boundaries of the artifact download to the API consumer. During the
download, we additionally provide the `OffsetUpdate`, which can be used
to report the downloaded data during the last elapsed time interval.

Unit tests have been added as well to the progress reader, whereas the
documentation for the new types has been enhanced as well.

Signed-off-by: Sascha Grunert <[email protected]>
@saschagrunert
Copy link
Member Author

Rebased on top of the latest master branch.

Copy link
Member

@vrothberg vrothberg left a comment

Choose a reason for hiding this comment

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

LGTM

@TomSweeneyRedHat
Copy link
Member

LGTM and happy green test buttons. Would like a final head nod from @mtrmac

@rhatdan
Copy link
Member

rhatdan commented Oct 28, 2019

Hopefully this does not require a new version? :^(

@vrothberg
Copy link
Member

Hopefully this does not require a new version? :^(

Only a minor bump is required :)

@rhatdan rhatdan merged commit 6bbebfc into containers:master Oct 30, 2019
@saschagrunert saschagrunert deleted the reader-adaptions branch October 30, 2019 17:18
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.

5 participants