Browse Source

etcdserver: fix data race in cluster

The data race happens when etcd updates member attributes and fetches
member info in http handler at the same time.
Yicheng Qin 11 years ago
parent
commit
014ef0f52d
4 changed files with 52 additions and 8 deletions
  1. 9 3
      etcdserver/cluster.go
  2. 21 0
      etcdserver/member.go
  3. 19 0
      etcdserver/member_test.go
  4. 3 5
      etcdserver/server.go

+ 9 - 3
etcdserver/cluster.go

@@ -121,7 +121,7 @@ func (c *Cluster) Members() []*Member {
 	defer c.Unlock()
 	defer c.Unlock()
 	var sms SortableMemberSlice
 	var sms SortableMemberSlice
 	for _, m := range c.members {
 	for _, m := range c.members {
-		sms = append(sms, m)
+		sms = append(sms, m.Clone())
 	}
 	}
 	sort.Sort(sms)
 	sort.Sort(sms)
 	return []*Member(sms)
 	return []*Member(sms)
@@ -136,7 +136,7 @@ func (s SortableMemberSlice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
 func (c *Cluster) Member(id types.ID) *Member {
 func (c *Cluster) Member(id types.ID) *Member {
 	c.Lock()
 	c.Lock()
 	defer c.Unlock()
 	defer c.Unlock()
-	return c.members[id]
+	return c.members[id].Clone()
 }
 }
 
 
 // MemberByName returns a Member with the given name if exists.
 // MemberByName returns a Member with the given name if exists.
@@ -153,7 +153,7 @@ func (c *Cluster) MemberByName(name string) *Member {
 			memb = m
 			memb = m
 		}
 		}
 	}
 	}
-	return memb
+	return memb.Clone()
 }
 }
 
 
 func (c *Cluster) MemberIDs() []types.ID {
 func (c *Cluster) MemberIDs() []types.ID {
@@ -335,6 +335,12 @@ func (c *Cluster) RemoveMember(id types.ID) {
 	c.removed[id] = true
 	c.removed[id] = true
 }
 }
 
 
+func (c *Cluster) UpdateMemberAttributes(id types.ID, attr Attributes) {
+	c.Lock()
+	defer c.Unlock()
+	c.members[id].Attributes = attr
+}
+
 // nodeToMember builds member through a store node.
 // nodeToMember builds member through a store node.
 // the child nodes of the given node should be sorted by key.
 // the child nodes of the given node should be sorted by key.
 func nodeToMember(n *store.NodeExtern) (*Member, error) {
 func nodeToMember(n *store.NodeExtern) (*Member, error) {

+ 21 - 0
etcdserver/member.go

@@ -80,6 +80,27 @@ func (m *Member) PickPeerURL() string {
 	return m.PeerURLs[rand.Intn(len(m.PeerURLs))]
 	return m.PeerURLs[rand.Intn(len(m.PeerURLs))]
 }
 }
 
 
+func (m *Member) Clone() *Member {
+	if m == nil {
+		return nil
+	}
+	mm := &Member{
+		ID: m.ID,
+		Attributes: Attributes{
+			Name: m.Name,
+		},
+	}
+	if m.PeerURLs != nil {
+		mm.PeerURLs = make([]string, len(m.PeerURLs))
+		copy(mm.PeerURLs, m.PeerURLs)
+	}
+	if m.ClientURLs != nil {
+		mm.ClientURLs = make([]string, len(m.ClientURLs))
+		copy(mm.ClientURLs, m.ClientURLs)
+	}
+	return mm
+}
+
 func memberStoreKey(id types.ID) string {
 func memberStoreKey(id types.ID) string {
 	return path.Join(storeMembersPrefix, id.String())
 	return path.Join(storeMembersPrefix, id.String())
 }
 }

+ 19 - 0
etcdserver/member_test.go

@@ -18,6 +18,7 @@ package etcdserver
 
 
 import (
 import (
 	"net/url"
 	"net/url"
+	"reflect"
 	"testing"
 	"testing"
 	"time"
 	"time"
 
 
@@ -88,3 +89,21 @@ func TestMemberPick(t *testing.T) {
 		}
 		}
 	}
 	}
 }
 }
+
+func TestMemberClone(t *testing.T) {
+	tests := []*Member{
+		newTestMemberp(1, nil, "abc", nil),
+		newTestMemberp(1, []string{"http://a"}, "abc", nil),
+		newTestMemberp(1, nil, "abc", []string{"http://b"}),
+		newTestMemberp(1, []string{"http://a"}, "abc", []string{"http://b"}),
+	}
+	for i, tt := range tests {
+		nm := tt.Clone()
+		if nm == tt {
+			t.Errorf("#%d: the pointers are the same, and clone doesn't happen", i)
+		}
+		if !reflect.DeepEqual(nm, tt) {
+			t.Errorf("#%d: member = %+v, want %+v", i, nm, tt)
+		}
+	}
+}

+ 3 - 5
etcdserver/server.go

@@ -618,13 +618,11 @@ func (s *EtcdServer) applyRequest(r pb.Request) Response {
 		default:
 		default:
 			if storeMemberAttributeRegexp.MatchString(r.Path) {
 			if storeMemberAttributeRegexp.MatchString(r.Path) {
 				id := mustParseMemberIDFromKey(path.Dir(r.Path))
 				id := mustParseMemberIDFromKey(path.Dir(r.Path))
-				m := s.Cluster.Member(id)
-				if m == nil {
-					log.Panicf("fetch member %s should never fail", id)
-				}
-				if err := json.Unmarshal([]byte(r.Val), &m.Attributes); err != nil {
+				var attr Attributes
+				if err := json.Unmarshal([]byte(r.Val), &attr); err != nil {
 					log.Panicf("unmarshal %s should never fail: %v", r.Val, err)
 					log.Panicf("unmarshal %s should never fail: %v", r.Val, err)
 				}
 				}
+				s.Cluster.UpdateMemberAttributes(id, attr)
 			}
 			}
 			return f(s.store.Set(r.Path, r.Dir, r.Val, expr))
 			return f(s.store.Set(r.Path, r.Dir, r.Val, expr))
 		}
 		}