Skip to content

Commit 48ed4f0

Browse files
authored
Merge pull request moby#29733 from cpuguy83/fix_v1plugin_deadlock
Fix race/deadlock in v1 plugin handlers
2 parents 8e64ca3 + 2938dce commit 48ed4f0

3 files changed

Lines changed: 92 additions & 23 deletions

File tree

integration-cli/docker_cli_external_graphdriver_unix_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ func (s *DockerExternalGraphdriverSuite) SetUpTest(c *check.C) {
5757
})
5858
}
5959

60+
func (s *DockerExternalGraphdriverSuite) OnTimeout(c *check.C) {
61+
s.d.DumpStackAndQuit()
62+
}
63+
6064
func (s *DockerExternalGraphdriverSuite) TearDownTest(c *check.C) {
6165
if s.d != nil {
6266
s.d.Stop(c)

pkg/plugins/plugin_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package plugins
2+
3+
import (
4+
"path/filepath"
5+
"runtime"
6+
"sync"
7+
"testing"
8+
"time"
9+
)
10+
11+
// regression test for deadlock in handlers
12+
func TestPluginAddHandler(t *testing.T) {
13+
// make a plugin which is pre-activated
14+
p := &Plugin{activateWait: sync.NewCond(&sync.Mutex{})}
15+
p.Manifest = &Manifest{Implements: []string{"bananas"}}
16+
storage.plugins["qwerty"] = p
17+
18+
testActive(t, p)
19+
Handle("bananas", func(_ string, _ *Client) {})
20+
testActive(t, p)
21+
}
22+
23+
func testActive(t *testing.T, p *Plugin) {
24+
done := make(chan struct{})
25+
go func() {
26+
p.waitActive()
27+
close(done)
28+
}()
29+
30+
select {
31+
case <-time.After(100 * time.Millisecond):
32+
_, f, l, _ := runtime.Caller(1)
33+
t.Fatalf("%s:%d: deadlock in waitActive", filepath.Base(f), l)
34+
case <-done:
35+
}
36+
37+
}

pkg/plugins/plugins.go

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@ type Plugin struct {
7070
// Manifest of the plugin (see above)
7171
Manifest *Manifest `json:"-"`
7272

73-
// error produced by activation
74-
activateErr error
75-
// specifies if the activation sequence is completed (not if it is successful or not)
76-
activated bool
7773
// wait for activation to finish
7874
activateWait *sync.Cond
75+
// error produced by activation
76+
activateErr error
77+
// keeps track of callback handlers run against this plugin
78+
handlersRun bool
7979
}
8080

8181
// BasePath returns the path to which all paths returned by the plugin are relative to.
@@ -112,19 +112,51 @@ func NewLocalPlugin(name, addr string) *Plugin {
112112

113113
func (p *Plugin) activate() error {
114114
p.activateWait.L.Lock()
115-
if p.activated {
115+
116+
if p.activated() {
117+
p.runHandlers()
116118
p.activateWait.L.Unlock()
117119
return p.activateErr
118120
}
119121

120122
p.activateErr = p.activateWithLock()
121-
p.activated = true
122123

124+
p.runHandlers()
123125
p.activateWait.L.Unlock()
124126
p.activateWait.Broadcast()
125127
return p.activateErr
126128
}
127129

130+
// runHandlers runs the registered handlers for the implemented plugin types
131+
// This should only be run after activation, and while the activation lock is held.
132+
func (p *Plugin) runHandlers() {
133+
if !p.activated() {
134+
return
135+
}
136+
137+
handlers.RLock()
138+
if !p.handlersRun {
139+
for _, iface := range p.Manifest.Implements {
140+
hdlrs, handled := handlers.extpointHandlers[iface]
141+
if !handled {
142+
continue
143+
}
144+
for _, handler := range hdlrs {
145+
handler(p.name, p.client)
146+
}
147+
}
148+
p.handlersRun = true
149+
}
150+
handlers.RUnlock()
151+
152+
}
153+
154+
// activated returns if the plugin has already been activated.
155+
// This should only be called with the activation lock held
156+
func (p *Plugin) activated() bool {
157+
return p.Manifest != nil
158+
}
159+
128160
func (p *Plugin) activateWithLock() error {
129161
c, err := NewClient(p.Addr, p.TLSConfig)
130162
if err != nil {
@@ -138,32 +170,20 @@ func (p *Plugin) activateWithLock() error {
138170
}
139171

140172
p.Manifest = m
141-
142-
handlers.RLock()
143-
for _, iface := range m.Implements {
144-
hdlrs, handled := handlers.extpointHandlers[iface]
145-
if !handled {
146-
continue
147-
}
148-
for _, handler := range hdlrs {
149-
handler(p.name, p.client)
150-
}
151-
}
152-
handlers.RUnlock()
153173
return nil
154174
}
155175

156176
func (p *Plugin) waitActive() error {
157177
p.activateWait.L.Lock()
158-
for !p.activated {
178+
for !p.activated() {
159179
p.activateWait.Wait()
160180
}
161181
p.activateWait.L.Unlock()
162182
return p.activateErr
163183
}
164184

165185
func (p *Plugin) implements(kind string) bool {
166-
if err := p.waitActive(); err != nil {
186+
if p.Manifest == nil {
167187
return false
168188
}
169189
for _, driver := range p.Manifest.Implements {
@@ -232,7 +252,7 @@ func Get(name, imp string) (*Plugin, error) {
232252
if err != nil {
233253
return nil, err
234254
}
235-
if pl.implements(imp) {
255+
if err := pl.waitActive(); err == nil && pl.implements(imp) {
236256
logrus.Debugf("%s implements: %s", name, imp)
237257
return pl, nil
238258
}
@@ -249,9 +269,17 @@ func Handle(iface string, fn func(string, *Client)) {
249269

250270
hdlrs = append(hdlrs, fn)
251271
handlers.extpointHandlers[iface] = hdlrs
272+
273+
storage.Lock()
252274
for _, p := range storage.plugins {
253-
p.activated = false
275+
p.activateWait.L.Lock()
276+
if p.activated() && p.implements(iface) {
277+
p.handlersRun = false
278+
}
279+
p.activateWait.L.Unlock()
254280
}
281+
storage.Unlock()
282+
255283
handlers.Unlock()
256284
}
257285

@@ -292,7 +320,7 @@ func GetAll(imp string) ([]*Plugin, error) {
292320
logrus.Error(pl.err)
293321
continue
294322
}
295-
if pl.pl.implements(imp) {
323+
if err := pl.pl.waitActive(); err == nil && pl.pl.implements(imp) {
296324
out = append(out, pl.pl)
297325
}
298326
}

0 commit comments

Comments
 (0)