vitess.io/vitess@v0.16.2/go/test/endtoend/vtgate/reservedconn/get_lock_test.go (about) 1 /* 2 Copyright 2020 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package reservedconn 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 "time" 24 25 "vitess.io/vitess/go/test/endtoend/utils" 26 27 "vitess.io/vitess/go/sync2" 28 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 32 "vitess.io/vitess/go/mysql" 33 ) 34 35 func TestLockUnlock(t *testing.T) { 36 conn, err := mysql.Connect(context.Background(), &vtParams) 37 require.NoError(t, err) 38 defer conn.Close() 39 40 utils.AssertMatches(t, conn, `select release_lock('lock name')`, `[[NULL]]`) 41 utils.AssertMatches(t, conn, `select get_lock('lock name', 2)`, `[[INT64(1)]]`) 42 utils.AssertMatches(t, conn, `select get_lock('lock name', 2)`, `[[INT64(1)]]`) 43 utils.AssertMatches(t, conn, `select is_free_lock('lock name')`, `[[INT64(0)]]`) 44 assert.NotEmpty(t, 45 utils.Exec(t, conn, `select is_used_lock('lock name')`)) 46 utils.AssertMatches(t, conn, `select release_lock('lock name')`, `[[INT64(1)]]`) 47 utils.AssertMatches(t, conn, `select release_all_locks()`, `[[UINT64(1)]]`) 48 utils.AssertMatches(t, conn, `select release_lock('lock name')`, `[[NULL]]`) 49 } 50 51 func TestLocksDontIntersect(t *testing.T) { 52 conn1, err := mysql.Connect(context.Background(), &vtParams) 53 require.NoError(t, err) 54 defer conn1.Close() 55 conn2, err := mysql.Connect(context.Background(), &vtParams) 56 require.NoError(t, err) 57 defer conn2.Close() 58 59 utils.AssertMatches(t, conn1, `select get_lock('lock1', 2)`, `[[INT64(1)]]`) 60 utils.AssertMatches(t, conn2, `select get_lock('lock2', 2)`, `[[INT64(1)]]`) 61 utils.AssertMatches(t, conn1, `select release_lock('lock1')`, `[[INT64(1)]]`) 62 utils.AssertMatches(t, conn2, `select release_lock('lock2')`, `[[INT64(1)]]`) 63 } 64 65 func TestLocksIntersect(t *testing.T) { 66 conn1, err := mysql.Connect(context.Background(), &vtParams) 67 require.NoError(t, err) 68 defer conn1.Close() 69 conn2, err := mysql.Connect(context.Background(), &vtParams) 70 require.NoError(t, err) 71 defer conn2.Close() 72 73 utils.AssertMatches(t, conn1, `select get_lock('lock1', 100)`, `[[INT64(1)]]`) 74 utils.AssertMatches(t, conn2, `select get_lock('lock2', 100)`, `[[INT64(1)]]`) 75 76 // Locks will not succeed. 77 utils.AssertMatches(t, conn1, `select get_lock('lock2', 1)`, `[[INT64(0)]]`) 78 utils.AssertMatches(t, conn2, `select get_lock('lock1', 1)`, `[[INT64(0)]]`) 79 utils.AssertMatches(t, conn1, `select release_lock('lock2')`, `[[INT64(0)]]`) 80 utils.AssertMatches(t, conn2, `select release_lock('lock1')`, `[[INT64(0)]]`) 81 82 utils.AssertMatches(t, conn1, `select release_lock('lock1')`, `[[INT64(1)]]`) 83 utils.AssertMatches(t, conn2, `select release_lock('lock2')`, `[[INT64(1)]]`) 84 } 85 86 func TestLocksAreExplicitlyReleaseAndRegrab(t *testing.T) { 87 conn1, err := mysql.Connect(context.Background(), &vtParams) 88 require.NoError(t, err) 89 defer conn1.Close() 90 conn2, err := mysql.Connect(context.Background(), &vtParams) 91 require.NoError(t, err) 92 defer conn2.Close() 93 94 utils.AssertMatches(t, conn1, `select get_lock('lock', 2)`, `[[INT64(1)]]`) 95 utils.AssertMatches(t, conn1, `select release_lock('lock')`, `[[INT64(1)]]`) 96 utils.AssertMatches(t, conn2, `select get_lock('lock', 2)`, `[[INT64(1)]]`) 97 } 98 99 func TestLocksAreReleasedWhenConnectionIsClosed(t *testing.T) { 100 conn1, err := mysql.Connect(context.Background(), &vtParams) 101 require.NoError(t, err) 102 defer conn1.Close() 103 conn2, err := mysql.Connect(context.Background(), &vtParams) 104 require.NoError(t, err) 105 defer conn2.Close() 106 107 utils.AssertMatches(t, conn1, `select get_lock('lock', 2)`, `[[INT64(1)]]`) 108 conn1.Close() 109 110 utils.AssertMatches(t, conn2, `select get_lock('lock', 2)`, `[[INT64(1)]]`) 111 } 112 113 func TestLocksBlockEachOther(t *testing.T) { 114 conn1, err := mysql.Connect(context.Background(), &vtParams) 115 require.NoError(t, err) 116 defer conn1.Close() 117 118 // in the first connection, grab a lock 119 utils.AssertMatches(t, conn1, `select get_lock('lock', 2)`, `[[INT64(1)]]`) 120 121 released := sync2.NewAtomicBool(false) 122 123 go func() { 124 conn2, err := mysql.Connect(context.Background(), &vtParams) 125 require.NoError(t, err) 126 defer conn2.Close() 127 128 // in the second connection, we try to grab a lock, and should get blocked 129 utils.AssertMatches(t, conn2, `select get_lock('lock', 2)`, `[[INT64(1)]]`) 130 assert.True(t, released.Get(), "was not blocked by get_lock") 131 utils.AssertMatches(t, conn2, `select release_lock('lock')`, `[[INT64(1)]]`) 132 }() 133 134 time.Sleep(1 * time.Second) 135 136 released.Set(true) 137 utils.AssertMatches(t, conn1, `select release_lock('lock')`, `[[INT64(1)]]`) 138 } 139 140 func TestLocksBlocksWithTx(t *testing.T) { 141 conn1, err := mysql.Connect(context.Background(), &vtParams) 142 require.NoError(t, err) 143 defer conn1.Close() 144 145 // in the first connection, grab a lock 146 utils.AssertMatches(t, conn1, `select get_lock('lock', 2)`, `[[INT64(1)]]`) 147 utils.Exec(t, conn1, "begin") 148 utils.Exec(t, conn1, "insert into test(id, val1) values(1,'1')") // -80 149 utils.Exec(t, conn1, "commit") 150 151 released := sync2.NewAtomicBool(false) 152 153 go func() { 154 conn2, err := mysql.Connect(context.Background(), &vtParams) 155 require.NoError(t, err) 156 defer conn2.Close() 157 158 // in the second connection, we try to grab a lock, and should get blocked 159 utils.AssertMatches(t, conn2, `select get_lock('lock', 2)`, `[[INT64(1)]]`) 160 assert.True(t, released.Get(), "was not blocked by get_lock") 161 utils.AssertMatches(t, conn2, `select release_lock('lock')`, `[[INT64(1)]]`) 162 }() 163 164 time.Sleep(1 * time.Second) 165 166 released.Set(true) 167 utils.AssertMatches(t, conn1, `select release_lock('lock')`, `[[INT64(1)]]`) 168 utils.Exec(t, conn1, "delete from test") 169 } 170 171 func TestLocksWithTxFailure(t *testing.T) { 172 conn1, err := mysql.Connect(context.Background(), &vtParams) 173 require.NoError(t, err) 174 defer conn1.Close() 175 176 conn2, err := mysql.Connect(context.Background(), &vtParams) 177 require.NoError(t, err) 178 defer conn2.Close() 179 180 // in the first connection, grab a lock for infinite time 181 utils.AssertMatches(t, conn1, `select get_lock('lock', -1)`, `[[INT64(1)]]`) 182 183 utils.Exec(t, conn1, "use `ks:80-`") 184 utils.Exec(t, conn1, "begin") 185 qr := utils.Exec(t, conn1, "select connection_id()") 186 utils.Exec(t, conn1, "use ks") 187 // kill the mysql connection shard which has transaction open. 188 vttablet1 := clusterInstance.Keyspaces[0].Shards[1].PrimaryTablet() // 80- 189 vttablet1.VttabletProcess.QueryTablet(fmt.Sprintf("kill %s", qr.Rows[0][0].ToString()), keyspaceName, false) 190 191 // transaction fails on commit. 192 _, err = conn1.ExecuteFetch("commit", 1, true) 193 require.Error(t, err) 194 195 // in the second connection, lock acquisition should fail as first connection still hold the lock though the transaction has failed. 196 utils.AssertMatches(t, conn2, `select get_lock('lock', 2)`, `[[INT64(0)]]`) 197 utils.AssertMatches(t, conn2, `select release_lock('lock')`, `[[INT64(0)]]`) 198 } 199 200 func TestLocksWithTxOngoingAndReleaseLock(t *testing.T) { 201 conn, err := mysql.Connect(context.Background(), &vtParams) 202 require.NoError(t, err) 203 defer conn.Close() 204 205 utils.AssertMatches(t, conn, `select get_lock('lock', -1)`, `[[INT64(1)]]`) 206 utils.Exec(t, conn, "begin") 207 utils.Exec(t, conn, "insert into test(id, val1) values(1,'1')") 208 utils.AssertMatches(t, conn, `select release_lock('lock')`, `[[INT64(1)]]`) 209 utils.AssertMatches(t, conn, `select id, val1 from test where id = 1`, `[[INT64(1) VARCHAR("1")]]`) 210 utils.Exec(t, conn, "rollback") 211 assertIsEmpty(t, conn, `select id, val1 from test where id = 1`) 212 } 213 214 func TestLocksWithTxOngoingAndLockFails(t *testing.T) { 215 conn1, err := mysql.Connect(context.Background(), &vtParams) 216 require.NoError(t, err) 217 defer conn1.Close() 218 219 conn2, err := mysql.Connect(context.Background(), &vtParams) 220 require.NoError(t, err) 221 defer conn2.Close() 222 223 utils.AssertMatches(t, conn2, `select get_lock('lock', -1)`, `[[INT64(1)]]`) 224 225 utils.Exec(t, conn1, "begin") 226 utils.Exec(t, conn1, "insert into test(id, val1) values(1,'1')") 227 utils.AssertMatches(t, conn1, `select get_lock('lock', 1)`, `[[INT64(0)]]`) 228 utils.AssertMatches(t, conn1, `select id, val1 from test where id = 1`, `[[INT64(1) VARCHAR("1")]]`) 229 utils.Exec(t, conn1, "rollback") 230 assertIsEmpty(t, conn1, `select id, val1 from test where id = 1`) 231 232 utils.AssertMatches(t, conn2, `select get_lock('lock', -1)`, `[[INT64(1)]]`) 233 } 234 235 func TestLocksKeepLockConnectionActive(t *testing.T) { 236 conn, err := mysql.Connect(context.Background(), &vtParams) 237 require.NoError(t, err) 238 defer conn.Close() 239 240 utils.AssertMatches(t, conn, `select get_lock('lock', -1)`, `[[INT64(1)]]`) 241 time.Sleep(3 * time.Second) // lock heartbeat time is 2 seconds. 242 utils.AssertMatches(t, conn, `select * from test where id = 42`, `[]`) // this will trigger heartbeat. 243 time.Sleep(3 * time.Second) // lock connection will not timeout after 5 seconds. 244 utils.AssertMatches(t, conn, `select is_free_lock('lock')`, `[[INT64(0)]]`) 245 246 } 247 248 func TestLocksResetLockOnTimeout(t *testing.T) { 249 conn, err := mysql.Connect(context.Background(), &vtParams) 250 require.NoError(t, err) 251 defer conn.Close() 252 253 utils.AssertMatches(t, conn, `select get_lock('lock', -1)`, `[[INT64(1)]]`) 254 time.Sleep(6 * time.Second) // lock connection timeout is 5 seconds. 255 utils.AssertContainsError(t, conn, `select is_free_lock('lock')`, "held locks released") 256 utils.AssertMatches(t, conn, `select is_free_lock('lock')`, `[[INT64(1)]]`) 257 } 258 259 func TestLockWaitOnConnTimeoutWithTxNext(t *testing.T) { 260 conn, err := mysql.Connect(context.Background(), &vtParams) 261 require.NoError(t, err) 262 defer conn.Close() 263 264 utils.AssertMatches(t, conn, `select get_lock('lock', 5)`, `[[INT64(1)]]`) 265 time.Sleep(1 * time.Second) 266 utils.AssertMatches(t, conn, `select release_lock('lock')`, `[[INT64(1)]]`) 267 time.Sleep(12 * time.Second) // wait for reserved connection timeout of 5 seconds and some buffer 268 _ = utils.Exec(t, conn, `begin`) 269 _ = utils.Exec(t, conn, `insert into test(id, val1) values (1, 'msg')`) 270 time.Sleep(1 * time.Second) // some wait for rollback to kick in (won't happen after fix) 271 utils.AssertMatches(t, conn, `select id, val1 from test where val1 = 'msg'`, `[[INT64(1) VARCHAR("msg")]]`) 272 _ = utils.Exec(t, conn, `commit`) 273 }