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

[manager/dispatcher] Fix deadlock in dispatcher #2744

Merged
merged 1 commit into from
Sep 10, 2018

Conversation

anshulpundir
Copy link
Contributor

There was a rare case where the dispatcher could end up deadlocked when
calling stop, which would cause the whole leadership change procedure to
go sideways, the dispatcher to pile up with goroutines, and the node to
crash.

In a nutshell, calls to the Session RPC end up in a (*Cond).Wait(),
waiting for a Broadcast that, once Stop is called, may never come. To
avoid that case, Stop, after being called and canceling the Dispatcher
context, does one final Broadcast to wake the sleeping waiters.

However, because the rpcRW lock, which stops Stop from proceeding until
all RPCs have returned, was previously obtained BEFORE the call to
Broadcast, Stop would never reach this final Broadcast call, waiting on
the Session RPCs to release the rpcRW lock, which they could not do
until Broadcast was called. Hence, deadlock.

To fix this, we simple have to move this final Broadcast to above the
attempt to acquire the rpcRW lock, allowing everything to proceed
correctly.

Signed-off-by: Drew Erny [email protected]

@anshulpundir anshulpundir changed the title Fix deadlock in dispatcher [manager/dispatcher] Fix deadlock in dispatcher Sep 10, 2018
@@ -342,6 +342,19 @@ func (d *Dispatcher) Stop() error {
d.cancel()
d.mu.Unlock()

d.processUpdatesLock.Lock()
// when we called d.cancel(), there may have been waiters currently
// waiting. they would be forever sleeping if we didn't call Broadcast here
Copy link
Contributor Author

Choose a reason for hiding this comment

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

they => They

@@ -342,6 +342,19 @@ func (d *Dispatcher) Stop() error {
d.cancel()
d.mu.Unlock()

d.processUpdatesLock.Lock()
// when we called d.cancel(), there may have been waiters currently
Copy link
Contributor Author

Choose a reason for hiding this comment

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

there may have been waiters

Maybe also describe who the waiters are?

// more waits will start after this Broadcast, because before waiting they
// check if the context is canceled.
//
// if that context cancelation check were not present, it would be possible
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its not clear from this description that the waiters actually go on wait() holding the rpcRW. Do we want to clarify that?

There was a rare case where the dispatcher could end up deadlocked when
calling stop, which would cause the whole leadership change procedure to
go sideways, the dispatcher to pile up with goroutines, and the node to
crash.

In a nutshell, calls to the Session RPC end up in a (*Cond).Wait(),
waiting for a Broadcast that, once Stop is called, may never come. To
avoid that case, Stop, after being called and canceling the Dispatcher
context, does one final Broadcast to wake the sleeping waiters.

However, because the rpcRW lock, which stops Stop from proceeding until
all RPCs have returned, was previously obtained BEFORE the call to
Broadcast, Stop would never reach this final Broadcast call, waiting on
the Session RPCs to release the rpcRW lock, which they could not do
until Broadcast was called. Hence, deadlock.

To fix this, we simple have to move this final Broadcast to above the
attempt to acquire the rpcRW lock, allowing everything to proceed
correctly.

Signed-off-by: Drew Erny <[email protected]>
@codecov
Copy link

codecov bot commented Sep 10, 2018

Codecov Report

Merging #2744 into master will increase coverage by 0.02%.
The diff coverage is 100%.

@@            Coverage Diff             @@
##           master    #2744      +/-   ##
==========================================
+ Coverage   61.68%   61.71%   +0.02%     
==========================================
  Files         134      134              
  Lines       21888    21888              
==========================================
+ Hits        13502    13508       +6     
+ Misses       6926     6917       -9     
- Partials     1460     1463       +3

@anshulpundir
Copy link
Contributor Author

LGTM!

@anshulpundir anshulpundir merged commit e24c2a4 into moby:master Sep 10, 2018
tiborvass pushed a commit to tiborvass/docker that referenced this pull request Sep 22, 2018
docker-jenkins pushed a commit to docker-archive/docker-ce that referenced this pull request Sep 22, 2018
This also brings in these PRs from swarmkit:
- moby/swarmkit#2691
- moby/swarmkit#2744
- moby/swarmkit#2732
- moby/swarmkit#2729
- moby/swarmkit#2748

Signed-off-by: Tibor Vass <[email protected]>
Upstream-commit: cce1763d57b5c8fc446b0863517bb5313e7e53be
Component: engine
@antonybichon17
Copy link

How is this tested?

@anshulpundir
Copy link
Contributor Author

For regressions only using unit/e2e tests, and that’s what we’ll recommend too. It’s a race condition so can’t be predictably reproduced @antonybichon17

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.

3 participants