138 lines
3.5 KiB
Go
138 lines
3.5 KiB
Go
// Copyright 2026 Dolthub, Inc.
|
|
//
|
|
// 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 main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
driver "github.com/dolthub/dolt/go/libraries/doltcore/dtestutils/sql_server_driver"
|
|
)
|
|
|
|
// TestConcurrentDropDatabase is a regression test for #10692.
|
|
//
|
|
// A lock-ordering bug between *DatabaseProvider and *DoltSession meant that
|
|
// DROP DATABASE during concurrency could cause the DatabaseProvider to
|
|
// deadlock and the server databases to become unavailable.
|
|
func TestConcurrentDropDatabase(t *testing.T) {
|
|
t.Parallel()
|
|
var ports DynamicResources
|
|
ports.global = &GlobalPorts
|
|
ports.t = t
|
|
u, err := driver.NewDoltUser()
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
u.Cleanup()
|
|
})
|
|
|
|
rs, err := u.MakeRepoStore()
|
|
require.NoError(t, err)
|
|
|
|
repo, err := rs.MakeRepo("concurrent_drop_database_test")
|
|
require.NoError(t, err)
|
|
|
|
srvSettings := &driver.Server{
|
|
Args: []string{"-P", `{{get_port "server_port"}}`},
|
|
DynamicPort: "server_port",
|
|
}
|
|
server := MakeServer(t, repo, srvSettings, &ports)
|
|
server.DBName = "concurrent_drop_database_test"
|
|
|
|
db, err := server.DB(driver.Connection{User: "root"})
|
|
require.NoError(t, err)
|
|
db.SetMaxIdleConns(0)
|
|
defer func() {
|
|
require.NoError(t, db.Close())
|
|
}()
|
|
ctx := t.Context()
|
|
|
|
eg, ctx := errgroup.WithContext(ctx)
|
|
var numcreates int32 = 0
|
|
const numWriters = 8
|
|
const numDatabasesPerWriter = 12
|
|
startCh := make(chan struct{})
|
|
readyCh := make(chan struct{})
|
|
for i := range numWriters {
|
|
eg.Go(func() error {
|
|
conn, err := db.Conn(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
select {
|
|
case readyCh <- struct{}{}:
|
|
case <-ctx.Done():
|
|
return nil
|
|
}
|
|
select {
|
|
case <-startCh:
|
|
case <-ctx.Done():
|
|
return nil
|
|
}
|
|
for j := range numDatabasesPerWriter {
|
|
if ctx.Err() != nil {
|
|
return context.Cause(ctx)
|
|
}
|
|
database := fmt.Sprintf("db%08d%08d", i, j)
|
|
_, err := conn.ExecContext(ctx, "CREATE DATABASE "+database)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
atomic.AddInt32(&numcreates, 1)
|
|
_, err = conn.ExecContext(ctx, "DROP DATABASE "+database)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
for range numWriters {
|
|
select {
|
|
case <-readyCh:
|
|
case <-ctx.Done():
|
|
// This will fail.
|
|
require.NoError(t, eg.Wait())
|
|
t.FailNow()
|
|
}
|
|
}
|
|
close(startCh)
|
|
require.NoError(t, eg.Wait())
|
|
ctx = t.Context()
|
|
conn, err := db.Conn(ctx)
|
|
require.NoError(t, err)
|
|
defer conn.Close()
|
|
rows, err := conn.QueryContext(ctx, "SHOW DATABASES")
|
|
require.NoError(t, err)
|
|
defer rows.Close()
|
|
var databases []string
|
|
for rows.Next() {
|
|
var db string
|
|
err = rows.Scan(&db)
|
|
require.NoError(t, err)
|
|
databases = append(databases, db)
|
|
}
|
|
require.NoError(t, rows.Err())
|
|
sort.Strings(databases)
|
|
require.Equal(t, []string{"concurrent_drop_database_test", "information_schema", "mysql"}, databases)
|
|
t.Logf("created and dropped %d databases", numcreates)
|
|
}
|