File: ctl_v3_lock_test.go

package info (click to toggle)
etcd 3.2.26%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 9,620 kB
  • sloc: sh: 2,191; makefile: 729
file content (147 lines) | stat: -rw-r--r-- 3,725 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package e2e

import (
	"os"
	"runtime"
	"strings"
	"syscall"
	"testing"
	"time"

	"github.com/coreos/etcd/pkg/expect"
)

// debugLockSignal forces SIGQUIT to debug etcdctl elect and lock failures
var debugLockSignal os.Signal

func init() {
	// hacks to ignore SIGQUIT debugging for some builds
	switch {
	case os.Getenv("COVERDIR") != "":
		// SIGQUIT interferes with coverage collection
		debugLockSignal = syscall.SIGTERM
	case runtime.GOARCH == "ppc64le":
		// ppc64le's signal handling won't kill processes with SIGQUIT
		// in the same way as amd64/i386, so processes won't terminate
		// as expected. Since this debugging code for CI, just ignore
		// ppc64le.
		debugLockSignal = syscall.SIGKILL
	default:
		// stack dumping OK
		debugLockSignal = syscall.SIGQUIT
	}
}

func TestCtlV3Lock(t *testing.T) {
	oldenv := os.Getenv("EXPECT_DEBUG")
	defer os.Setenv("EXPECT_DEBUG", oldenv)
	os.Setenv("EXPECT_DEBUG", "1")

	testCtl(t, testLock)
}

func testLock(cx ctlCtx) {
	// debugging for #6464
	sig := cx.epc.withStopSignal(debugLockSignal)
	defer cx.epc.withStopSignal(sig)

	name := "a"

	holder, ch, err := ctlV3Lock(cx, name)
	if err != nil {
		cx.t.Fatal(err)
	}

	l1 := ""
	select {
	case <-time.After(2 * time.Second):
		cx.t.Fatalf("timed out locking")
	case l1 = <-ch:
		if !strings.HasPrefix(l1, name) {
			cx.t.Errorf("got %q, expected %q prefix", l1, name)
		}
	}

	// blocked process that won't acquire the lock
	blocked, ch, err := ctlV3Lock(cx, name)
	if err != nil {
		cx.t.Fatal(err)
	}
	select {
	case <-time.After(100 * time.Millisecond):
	case <-ch:
		cx.t.Fatalf("should block")
	}

	// overlap with a blocker that will acquire the lock
	blockAcquire, ch, err := ctlV3Lock(cx, name)
	if err != nil {
		cx.t.Fatal(err)
	}
	defer blockAcquire.Stop()
	select {
	case <-time.After(100 * time.Millisecond):
	case <-ch:
		cx.t.Fatalf("should block")
	}

	// kill blocked process with clean shutdown
	if err = blocked.Signal(os.Interrupt); err != nil {
		cx.t.Fatal(err)
	}
	if err = closeWithTimeout(blocked, time.Second); err != nil {
		cx.t.Fatal(err)
	}

	// kill the holder with clean shutdown
	if err = holder.Signal(os.Interrupt); err != nil {
		cx.t.Fatal(err)
	}
	if err = closeWithTimeout(holder, time.Second); err != nil {
		cx.t.Fatal(err)
	}

	// blockAcquire should acquire the lock
	select {
	case <-time.After(time.Second):
		cx.t.Fatalf("timed out from waiting to holding")
	case l2 := <-ch:
		if l1 == l2 || !strings.HasPrefix(l2, name) {
			cx.t.Fatalf("expected different lock name, got l1=%q, l2=%q", l1, l2)
		}
	}
}

// ctlV3Lock creates a lock process with a channel listening for when it acquires the lock.
func ctlV3Lock(cx ctlCtx, name string) (*expect.ExpectProcess, <-chan string, error) {
	cmdArgs := append(cx.PrefixArgs(), "lock", name)
	proc, err := spawnCmd(cmdArgs)
	outc := make(chan string, 1)
	if err != nil {
		close(outc)
		return proc, outc, err
	}
	proc.StopSignal = debugLockSignal
	go func() {
		s, xerr := proc.ExpectFunc(func(string) bool { return true })
		if xerr != nil {
			cx.t.Errorf("expect failed (%v)", xerr)
		}
		outc <- s
	}()
	return proc, outc, err
}