Browse code

cluster: Avoid recursive RLock

GetTasks can call GetService and GetNode with the read lock held. These
methods try to aquire the read side of the same lock. According to the
sync package documentation, this is not safe:

> If a goroutine holds a RWMutex for reading, it must not expect this or
> any other goroutine to be able to also take the read lock until the
> first read lock is released. In particular, this prohibits recursive
> read locking. This is to ensure that the lock eventually becomes
> available; a blocked Lock call excludes new readers from acquiring the
> lock.

Fix GetTasks to use the lower-level getService and getNode methods
instead. Also, use lockedManagerAction to simplify GetTasks.

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>

Aaron Lehmann authored on 2017/07/25 01:52:31
Showing 1 changed files
... ...
@@ -11,57 +11,50 @@ import (
11 11
 
12 12
 // GetTasks returns a list of tasks matching the filter options.
13 13
 func (c *Cluster) GetTasks(options apitypes.TaskListOptions) ([]types.Task, error) {
14
-	c.mu.RLock()
15
-	defer c.mu.RUnlock()
14
+	var r *swarmapi.ListTasksResponse
16 15
 
17
-	state := c.currentNodeState()
18
-	if !state.IsActiveManager() {
19
-		return nil, c.errNoManager(state)
20
-	}
21
-
22
-	filterTransform := func(filter filters.Args) error {
23
-		if filter.Include("service") {
24
-			serviceFilters := filter.Get("service")
25
-			for _, serviceFilter := range serviceFilters {
26
-				service, err := c.GetService(serviceFilter, false)
27
-				if err != nil {
28
-					return err
16
+	if err := c.lockedManagerAction(func(ctx context.Context, state nodeState) error {
17
+		filterTransform := func(filter filters.Args) error {
18
+			if filter.Include("service") {
19
+				serviceFilters := filter.Get("service")
20
+				for _, serviceFilter := range serviceFilters {
21
+					service, err := getService(ctx, state.controlClient, serviceFilter, false)
22
+					if err != nil {
23
+						return err
24
+					}
25
+					filter.Del("service", serviceFilter)
26
+					filter.Add("service", service.ID)
29 27
 				}
30
-				filter.Del("service", serviceFilter)
31
-				filter.Add("service", service.ID)
32 28
 			}
33
-		}
34
-		if filter.Include("node") {
35
-			nodeFilters := filter.Get("node")
36
-			for _, nodeFilter := range nodeFilters {
37
-				node, err := c.GetNode(nodeFilter)
38
-				if err != nil {
39
-					return err
29
+			if filter.Include("node") {
30
+				nodeFilters := filter.Get("node")
31
+				for _, nodeFilter := range nodeFilters {
32
+					node, err := getNode(ctx, state.controlClient, nodeFilter)
33
+					if err != nil {
34
+						return err
35
+					}
36
+					filter.Del("node", nodeFilter)
37
+					filter.Add("node", node.ID)
40 38
 				}
41
-				filter.Del("node", nodeFilter)
42
-				filter.Add("node", node.ID)
43 39
 			}
40
+			if !filter.Include("runtime") {
41
+				// default to only showing container tasks
42
+				filter.Add("runtime", "container")
43
+				filter.Add("runtime", "")
44
+			}
45
+			return nil
44 46
 		}
45
-		if !filter.Include("runtime") {
46
-			// default to only showing container tasks
47
-			filter.Add("runtime", "container")
48
-			filter.Add("runtime", "")
49
-		}
50
-		return nil
51
-	}
52 47
 
53
-	filters, err := newListTasksFilters(options.Filters, filterTransform)
54
-	if err != nil {
55
-		return nil, err
56
-	}
57
-
58
-	ctx, cancel := c.getRequestContext()
59
-	defer cancel()
48
+		filters, err := newListTasksFilters(options.Filters, filterTransform)
49
+		if err != nil {
50
+			return err
51
+		}
60 52
 
61
-	r, err := state.controlClient.ListTasks(
62
-		ctx,
63
-		&swarmapi.ListTasksRequest{Filters: filters})
64
-	if err != nil {
53
+		r, err = state.controlClient.ListTasks(
54
+			ctx,
55
+			&swarmapi.ListTasksRequest{Filters: filters})
56
+		return err
57
+	}); err != nil {
65 58
 		return nil, err
66 59
 	}
67 60