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

overlord/hooks: make sure only one hook for given snap is executed at a time. #3235

Merged
merged 16 commits into from
May 18, 2017

Conversation

stolowski
Copy link
Contributor

Make sure only one hook for given snap is executed at a time.

Copy link
Contributor

@zyga zyga left a comment

Choose a reason for hiding this comment

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

+1

@mvo5
Copy link
Contributor

mvo5 commented Apr 26, 2017

Thanks for working on this! The code looks fine. I would appreciate a test (direct or indirect) for this as well.

@stolowski
Copy link
Contributor Author

@mvo5 Ok, added an (indirect) test. I'm not sure if this is the most effective way of testing this, but it fails every time without the setBlocked() change in the code, so I think it's good enough to prove we serialize tasks. I'm not sure though how to test if we allow hooks for different snaps to run concurrently - it's not impossible, but could be flaky.

@@ -32,6 +33,9 @@ type MockHandler struct {
ErrorCalled bool
ErrorError bool
Err error

Executed int32
Copy link
Collaborator

Choose a reason for hiding this comment

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

Executing? NExecuting?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, "Executing" sounds good, done.

Copy link
Collaborator

Choose a reason for hiding this comment

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

thanks

@@ -41,6 +45,10 @@ func NewMockHandler() *MockHandler {

// Before satisfies hookstate.Handler.Before
func (h *MockHandler) Before() error {
executing := atomic.AddInt32(&h.Executing, 1)
if executing != 1 {
panic(fmt.Sprintf("More than one handler executed: %d", executing))
Copy link
Contributor

Choose a reason for hiding this comment

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

The logic here doesn't feel entirely sensible because we do allow handlers to execute concurrently as long as they're taking care of different snaps. Your test only passed because there was no second snap on it.

One might also argue that every execution should create a new handler, and that's what we tend to do, but then the logic is bogus for the opposite reason: if every hook has its own handler, this wouldn't catch the problem either.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test deliberately reuses single instance of mock handler and relies on its implementation to verify that multiple hooks are not run concurrently and that SetBlocked() lambda does its job. This test will fail every time if SetBlocked implementation is removed.

I agree it would be nice to have a test where there is a second snap, and its hook can be run at the same time and independent of first snap' hook. But I'm not sure how to make such a test right now without introducing flakiness.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, regarding a test with different snap, two mock handlers where one waits for the other on a channel should do the job... Let me hack on it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added second test, both tests should now cover all cases (making sure we serialize hook tasks for same snap, and making sure we allow hooks from different snaps run concurrently).

Copy link
Contributor

Choose a reason for hiding this comment

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

Again, can we please not do that in this type? This is hardcoding an assumption in a publicly exposed test type that is simply not a real constraint and can blow things up without the logic in the test being wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@niemeyer Ok, I've addressed this by introducing callbacks to the MockHandler as discussed.

})

var testSnap1HookCalls, testSnap2HookCalls int
ch := make(chan struct{}, 0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

the ,0 is unsual I think style wise

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, removed. I prefer to make it more explicit what the intent is even if it's the default, but you're right, we don't do that in other places so it's better to be consistent.

Copy link
Collaborator

@pedronis pedronis Apr 28, 2017

Choose a reason for hiding this comment

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

@stolowski thanks, the langref is not super clear about what make(chan T, 0) should do,

https://golang.org/ref/spec#Making_slices_maps_and_channels

it seems you get un unbuffered channel, but a buffered channel where send would always fail would seem not ruled out. it would be bit useless, though that's the behavior of the nil channel

thisSnapName := hooksup.Snap
// examine all hook tasks, block thisTask if we find any other hook task affecting same snap
for _, t := range running {
if t.Kind() != "run-hook" || t.Get("hook-setup", &hooksup) != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

because Snap is something that is always set in HookSetup this not an actual problem here but in general because json.Unmarshal updates without resetting target values, doing Get on a reused variable can be risky

@pedronis pedronis merged commit d0f3985 into canonical:master May 18, 2017
@pedronis pedronis added this to the 2.27 milestone May 18, 2017
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