Skip to content

Share disk usage computation results between concurrent invocations#42715

Merged
thaJeztah merged 2 commits intomoby:masterfrom
rvolosatovs:shared_disk_usage
Aug 10, 2021
Merged

Share disk usage computation results between concurrent invocations#42715
thaJeztah merged 2 commits intomoby:masterfrom
rvolosatovs:shared_disk_usage

Conversation

@rvolosatovs
Copy link
Copy Markdown

@rvolosatovs rvolosatovs commented Aug 6, 2021

- What I did
Share disk usage computation results between concurrent invocations instead of throwing an error

- How I did it

  • Use x/sync/singleflight.Group, which ensures computations are simultaneously performed by at most one goroutine and the results are propagated to all goroutines simultaneously calling the method.
  • Extract the disk usage computation functionality for containers and images for consistency with other object types and better separation of concerns. It also fits nicely with the current design.

- How to verify it
E.g.

docker system df& docker system df& docker system df

Or:

curl -s --unix-socket /var/run/docker.sock 'http://localhost/system/df?types=image'& curl -s --unix-socket /var/run/docker.sock 'http://localhost/system/df?types=container'& curl -s --unix-socket /var/run/docker.sock 'http://localhost/system/df?types=volume'& curl -s --unix-socket /var/run/docker.sock 'http://localhost/system/df?types=build-cache'& curl -s --unix-socket /var/run/docker.sock 'http://localhost/system/df?types=image&type=container'

Such invocations do not error anymore, but just return the result once computed by one of the goroutines

** description for changeling**

The `GET /system/df` endpoint can now be used concurrently. If a request is made
to the endpoint while a calculation is still running, the request will receive
the result of the already running calculation, once completed. Previously, an
error (`a disk usage operation is already running`) would be returned in this
situation.

@rvolosatovs rvolosatovs force-pushed the shared_disk_usage branch 2 times, most recently from 82c1da4 to 9b26c2b Compare August 6, 2021 15:12
@rvolosatovs rvolosatovs marked this pull request as ready for review August 6, 2021 15:36
@rvolosatovs

This comment has been minimized.

@cpuguy83
Copy link
Copy Markdown
Member

cpuguy83 commented Aug 6, 2021

Seems like we can use the singleflight package in x/sync.
https://pkg.go.dev/golang.org/x/sync@v0.0.0-20210220032951-036812b2e83c/singleflight#Group.DoChan

@rvolosatovs rvolosatovs force-pushed the shared_disk_usage branch 4 times, most recently from 03a6cb9 to 57b7519 Compare August 9, 2021 10:38
@rvolosatovs
Copy link
Copy Markdown
Author

Thanks, didn't know about existence of this package!
Added a x/sync/singleflight-based implementation in 57b7519.
I do find the previous approach simpler and easier to read, mostly due to the lack of context handling in singleflight. Also, note that the returned channel is never closed and would leak a goroutine if we did not ensure a read on caller side, which means we have to start a goroutine on ctx.Done().
A possible minor advantage, is that we can reuse the "group" for e.g. improving prune UX.
I'm fine with both, with a slight preference to the original due to the above. (note, that for "visibility" of the called function, the function could be passed as argument to Do, just like with singleflight, and, in fact, that was done in the first iteration of this package)

Does it maybe make sense to add a wrapper for singleflight, which would add easy-to-use context support? (basically just wrapping the select block)

What do you think? @cpuguy83 @thaJeztah

Copy link
Copy Markdown
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

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

I do find the previous approach simpler and easier to read, mostly due to the lack of context handling in singleflight.

Yeah, the handling is slightly more complex. That said, I do like that we won't have to maintain yet-another-package.

the returned channel is never closed and would leak a goroutine if we did not ensure a read on caller side, which means we have to start a goroutine on ctx.Done().

That's a bit ugly, yes. @cpuguy83 any other suggestions for that?

A possible minor advantage, is that we can reuse the "group" for e.g. improving prune UX.

Ah, just left a comment about that. Was your idea to reuse the same groups, or to use the same approach ?

Does it maybe make sense to add a wrapper for singleflight, which would add easy-to-use context support? (basically just wrapping the select block)

Always a bit cautious with abstracting away things too much. You mean something like;

package singleflight

import (
    "context"

    "golang.org/x/sync/singleflight"
)

func Do(ctx context.Context, sf *singleflight.Group, key string, fn func() (interface{}, error)) (interface{}, error)
	ch := sf.DoChan(key, fn)
	select {
	case <-ctx.Done():
		go func() { <-ch }()
		return nil, ctx.Err()
	case res := <-ch:
		if res.Err != nil {
			return nil, res.Err
		}
		return res.Val, nil
	}
}

ds ds
pruneRunning int32
eventLogger VolumeEventLogger
singleflightGroup singleflight.Group
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

perhaps just singleFlight (slightly shorter, and reduces reformatting/reflowing other lines)

Or (if we will be implement the same for pruning) perhaps make the singleFlight more descriptive to what it's for. e.g. usage (which would then be called as usage.DoChan()), then we can use prune for the other one (prune.DoChan())

Same for the other singleFlightGroups

startupDone chan struct{}
singleflightGroup singleflight.Group

pruneRunning int32
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not for this PR, but should we use the same approach for pruning, and use a singleFlightGroup for that as well?

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.

Yes!

@rvolosatovs rvolosatovs force-pushed the shared_disk_usage branch 2 times, most recently from 9b31dc1 to 700a0c4 Compare August 9, 2021 16:12
@rvolosatovs
Copy link
Copy Markdown
Author

Ah, just left a comment about that. Was your idea to reuse the same groups, or to use the same approach ?

Given the library API it seems that the expected usage is to reuse the group. I have hard time understanding why so much functionality would be present for key support otherwise.

Personally, reusing anything here does not seem like the right approach, but if that's the "idiomatic" usage, then I suppose we should follow.

Always a bit cautious with abstracting away things too much. You mean something like;
(example)

Yes, exactly that, but it doesn't seem like it should deserve it's own package. It's something I would like to put in a internal/syncutil, if there was such a package.

@cpuguy83
Copy link
Copy Markdown
Member

cpuguy83 commented Aug 9, 2021

I don't see how we'd leak anything since the channel is buffered.

@rvolosatovs
Copy link
Copy Markdown
Author

I don't see how we'd leak anything since the channel is buffered.

Yes, you're right, we don't actually need the goroutine

@thaJeztah
Copy link
Copy Markdown
Member

For reviewers; use the ?w=1 option to view the diff; it makes the diff a lot easier to review (due to whitespace changes); https://github.com/moby/moby/pull/42715/files?w=1

Copy link
Copy Markdown
Member

@cpuguy83 cpuguy83 left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Copy Markdown
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

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

changes look good, but can you squash/combine the first and last commit?

Roman Volosatovs added 2 commits August 9, 2021 19:59
Signed-off-by: Roman Volosatovs <roman.volosatovs@docker.com>
Signed-off-by: Roman Volosatovs <roman.volosatovs@docker.com>
Copy link
Copy Markdown
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

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

LGTM, thanks!

@thaJeztah thaJeztah added area/api API impact/changelog kind/enhancement Enhancements are not bugs or new features but can improve usability or performance. labels Aug 9, 2021
@thaJeztah thaJeztah added this to the 21.xx milestone Aug 9, 2021
@thaJeztah
Copy link
Copy Markdown
Member

thaJeztah commented Aug 9, 2021

Hmm


[2021-08-09T18:06:13.240Z] 29d5b6a226d2: Download complete
[2021-08-09T18:07:54.461Z] powershell.exe : failed to register layer: re-exec error: exit status 1: output: hcsshim::ProcessUtilityVMImage 
[2021-08-09T18:07:54.461Z] \\?\D:\docker\windowsfilter\1258f08accef9b7f046ebfe52bddc3814446163e842dca420fe8242533da0e06\UtilityVM: To use this container image, you must join the Windows Insider Program. Please see 
[2021-08-09T18:07:54.461Z] https://go.microsoft.com/fwlink/?linkid=850659 for more information.

@StefanScherer could this be because the machines were updated? (if so, do we need #42527 to be merged?)

@thaJeztah
Copy link
Copy Markdown
Member

merged #42527. Let me trigger CI again to see if it picks up that change

@thaJeztah
Copy link
Copy Markdown
Member

Yup! All green again now

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

Labels

area/api API impact/changelog kind/enhancement Enhancements are not bugs or new features but can improve usability or performance. status/4-merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants