1// Copyright 2016 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package integration
16
17import (
18	"testing"
19	"time"
20
21	"github.com/coreos/etcd/clientv3/concurrency"
22	"github.com/coreos/etcd/contrib/recipes"
23)
24
25func TestDoubleBarrier(t *testing.T) {
26	clus := NewClusterV3(t, &ClusterConfig{Size: 3})
27	defer clus.Terminate(t)
28
29	waiters := 10
30	session, err := concurrency.NewSession(clus.RandClient())
31	if err != nil {
32		t.Error(err)
33	}
34	defer session.Orphan()
35
36	b := recipe.NewDoubleBarrier(session, "test-barrier", waiters)
37	donec := make(chan struct{})
38	for i := 0; i < waiters-1; i++ {
39		go func() {
40			session, err := concurrency.NewSession(clus.RandClient())
41			if err != nil {
42				t.Error(err)
43			}
44			defer session.Orphan()
45
46			bb := recipe.NewDoubleBarrier(session, "test-barrier", waiters)
47			if err := bb.Enter(); err != nil {
48				t.Fatalf("could not enter on barrier (%v)", err)
49			}
50			donec <- struct{}{}
51			if err := bb.Leave(); err != nil {
52				t.Fatalf("could not leave on barrier (%v)", err)
53			}
54			donec <- struct{}{}
55		}()
56	}
57
58	time.Sleep(10 * time.Millisecond)
59	select {
60	case <-donec:
61		t.Fatalf("barrier did not enter-wait")
62	default:
63	}
64
65	if err := b.Enter(); err != nil {
66		t.Fatalf("could not enter last barrier (%v)", err)
67	}
68
69	timerC := time.After(time.Duration(waiters*100) * time.Millisecond)
70	for i := 0; i < waiters-1; i++ {
71		select {
72		case <-timerC:
73			t.Fatalf("barrier enter timed out")
74		case <-donec:
75		}
76	}
77
78	time.Sleep(10 * time.Millisecond)
79	select {
80	case <-donec:
81		t.Fatalf("barrier did not leave-wait")
82	default:
83	}
84
85	b.Leave()
86	timerC = time.After(time.Duration(waiters*100) * time.Millisecond)
87	for i := 0; i < waiters-1; i++ {
88		select {
89		case <-timerC:
90			t.Fatalf("barrier leave timed out")
91		case <-donec:
92		}
93	}
94}
95
96func TestDoubleBarrierFailover(t *testing.T) {
97	clus := NewClusterV3(t, &ClusterConfig{Size: 3})
98	defer clus.Terminate(t)
99
100	waiters := 10
101	donec := make(chan struct{})
102
103	s0, err := concurrency.NewSession(clus.clients[0])
104	if err != nil {
105		t.Error(err)
106	}
107	defer s0.Orphan()
108	s1, err := concurrency.NewSession(clus.clients[0])
109	if err != nil {
110		t.Error(err)
111	}
112	defer s1.Orphan()
113
114	// sacrificial barrier holder; lease will be revoked
115	go func() {
116		b := recipe.NewDoubleBarrier(s0, "test-barrier", waiters)
117		if berr := b.Enter(); berr != nil {
118			t.Fatalf("could not enter on barrier (%v)", berr)
119		}
120		donec <- struct{}{}
121	}()
122
123	for i := 0; i < waiters-1; i++ {
124		go func() {
125			b := recipe.NewDoubleBarrier(s1, "test-barrier", waiters)
126			if berr := b.Enter(); berr != nil {
127				t.Fatalf("could not enter on barrier (%v)", berr)
128			}
129			donec <- struct{}{}
130			b.Leave()
131			donec <- struct{}{}
132		}()
133	}
134
135	// wait for barrier enter to unblock
136	for i := 0; i < waiters; i++ {
137		select {
138		case <-donec:
139		case <-time.After(10 * time.Second):
140			t.Fatalf("timed out waiting for enter, %d", i)
141		}
142	}
143
144	if err = s0.Close(); err != nil {
145		t.Fatal(err)
146	}
147	// join on rest of waiters
148	for i := 0; i < waiters-1; i++ {
149		select {
150		case <-donec:
151		case <-time.After(10 * time.Second):
152			t.Fatalf("timed out waiting for leave, %d", i)
153		}
154	}
155}
156