|
15 | 15 | package crossks |
16 | 16 |
|
17 | 17 | import ( |
| 18 | + "context" |
18 | 19 | "fmt" |
19 | 20 | "testing" |
| 21 | + "time" |
20 | 22 |
|
21 | 23 | "github.com/ngaut/pools" |
22 | 24 | "github.com/pingcap/tidb/pkg/config/kerneltype" |
| 25 | + "github.com/pingcap/tidb/pkg/ddl/schemaver" |
23 | 26 | "github.com/pingcap/tidb/pkg/infoschema/validatorapi" |
24 | 27 | "github.com/pingcap/tidb/pkg/keyspace" |
25 | 28 | "github.com/pingcap/tidb/pkg/kv" |
26 | 29 | "github.com/pingcap/tidb/pkg/testkit/testfailpoint" |
27 | 30 | "github.com/pingcap/tidb/pkg/util" |
28 | 31 | "github.com/stretchr/testify/require" |
| 32 | + clientv3 "go.etcd.io/etcd/client/v3" |
29 | 33 | ) |
30 | 34 |
|
31 | 35 | type runtimeHandleTestStore struct { |
32 | 36 | kv.Storage |
33 | | - ks string |
| 37 | + ks string |
| 38 | + closeCount int |
34 | 39 | } |
35 | 40 |
|
36 | 41 | func (s *runtimeHandleTestStore) GetKeyspace() string { |
37 | 42 | return s.ks |
38 | 43 | } |
39 | 44 |
|
| 45 | +func (s *runtimeHandleTestStore) Close() error { |
| 46 | + s.closeCount++ |
| 47 | + return nil |
| 48 | +} |
| 49 | + |
40 | 50 | type runtimeHandleTestSessPool struct { |
41 | 51 | util.DestroyableSessionPool |
42 | 52 | closeCount int |
| 53 | + onClose func() |
43 | 54 | } |
44 | 55 |
|
45 | 56 | func (p *runtimeHandleTestSessPool) Close() { |
46 | 57 | p.closeCount++ |
| 58 | + if p.onClose != nil { |
| 59 | + p.onClose() |
| 60 | + } |
47 | 61 | } |
48 | 62 |
|
49 | 63 | func newRuntimeHandleTestManager(targetKS string) (*Manager, *runtimeEntry, *runtimeHandleTestStore, *runtimeHandleTestSessPool) { |
50 | 64 | mgr := NewManager(&runtimeHandleTestStore{ks: keyspace.System}) |
51 | 65 | targetStore := &runtimeHandleTestStore{ks: targetKS} |
52 | 66 | sessPool := &runtimeHandleTestSessPool{} |
53 | 67 | entry := &runtimeEntry{ |
54 | | - sessMgr: &SessionManager{ |
55 | | - store: targetStore, |
56 | | - sessPool: sessPool, |
57 | | - }, |
| 68 | + sessMgr: newRuntimeHandleTestSessionManager(targetStore, sessPool), |
58 | 69 | activeHolders: make(map[string]struct{}), |
59 | 70 | } |
60 | 71 | mgr.runtimes[targetKS] = entry |
61 | 72 | return mgr, entry, targetStore, sessPool |
62 | 73 | } |
63 | 74 |
|
| 75 | +func newRuntimeHandleTestSessionManager(targetStore *runtimeHandleTestStore, sessPool *runtimeHandleTestSessPool) *SessionManager { |
| 76 | + ctx, cancel := context.WithCancel(context.Background()) |
| 77 | + return &SessionManager{ |
| 78 | + ctx: ctx, |
| 79 | + cancel: cancel, |
| 80 | + exitCh: make(chan struct{}), |
| 81 | + store: targetStore, |
| 82 | + etcdCli: clientv3.NewCtxClient(context.Background()), |
| 83 | + schemaVerSyncer: schemaver.NewMemSyncer(), |
| 84 | + sessPool: sessPool, |
| 85 | + } |
| 86 | +} |
| 87 | + |
64 | 88 | func unusedRuntimeHandleFactoryGetter(t *testing.T) func(string, validatorapi.Validator) pools.Factory { |
65 | 89 | return func(string, validatorapi.Validator) pools.Factory { |
66 | 90 | t.Fatal("test should use the pre-seeded runtime entry") |
@@ -238,3 +262,126 @@ func TestAcquireRuntimeHandle(t *testing.T) { |
238 | 262 | require.Zero(t, sessPool.closeCount) |
239 | 263 | }) |
240 | 264 | } |
| 265 | + |
| 266 | +func TestEvictRuntime(t *testing.T) { |
| 267 | + if kerneltype.IsClassic() { |
| 268 | + t.Skip("cross keyspace runtime acquire is supported only in nextgen kernel") |
| 269 | + } |
| 270 | + |
| 271 | + t.Run("skips active holders", func(t *testing.T) { |
| 272 | + targetKS := "ks-evict-active" |
| 273 | + mgr, entry, _, sessPool := newRuntimeHandleTestManager(targetKS) |
| 274 | + factoryGetter := unusedRuntimeHandleFactoryGetter(t) |
| 275 | + |
| 276 | + first, err := mgr.Acquire(targetKS, "holder-1", factoryGetter) |
| 277 | + require.NoError(t, err) |
| 278 | + second, err := mgr.Acquire(targetKS, "holder-2", factoryGetter) |
| 279 | + require.NoError(t, err) |
| 280 | + |
| 281 | + first.Release() |
| 282 | + entry.lastReleaseAt = time.Now().Add(-crossKSRuntimeIdleTimeout - time.Second) |
| 283 | + mgr.sweepIdleRuntimes(crossKSRuntimeIdleTimeout) |
| 284 | + |
| 285 | + _, ok := mgr.Get(targetKS) |
| 286 | + require.True(t, ok) |
| 287 | + require.Contains(t, entry.activeHolders, "holder-2") |
| 288 | + require.Zero(t, sessPool.closeCount) |
| 289 | + second.Release() |
| 290 | + }) |
| 291 | + |
| 292 | + t.Run("closes idle entry outside manager lock", func(t *testing.T) { |
| 293 | + targetKS := "ks-evict-idle" |
| 294 | + mgr, entry, targetStore, sessPool := newRuntimeHandleTestManager(targetKS) |
| 295 | + factoryGetter := unusedRuntimeHandleFactoryGetter(t) |
| 296 | + sessPool.onClose = func() { |
| 297 | + require.True(t, mgr.mu.TryLock()) |
| 298 | + mgr.mu.Unlock() |
| 299 | + } |
| 300 | + |
| 301 | + handle, err := mgr.Acquire(targetKS, "holder-1", factoryGetter) |
| 302 | + require.NoError(t, err) |
| 303 | + handle.Release() |
| 304 | + entry.lastReleaseAt = time.Now().Add(-crossKSRuntimeIdleTimeout - time.Second) |
| 305 | + |
| 306 | + mgr.sweepIdleRuntimes(crossKSRuntimeIdleTimeout) |
| 307 | + |
| 308 | + _, ok := mgr.Get(targetKS) |
| 309 | + require.False(t, ok) |
| 310 | + require.Equal(t, 1, sessPool.closeCount) |
| 311 | + require.Equal(t, 1, targetStore.closeCount) |
| 312 | + }) |
| 313 | + |
| 314 | + t.Run("reacquire creates new runtime", func(t *testing.T) { |
| 315 | + targetKS := "ks-evict-reacquire" |
| 316 | + mgr, entry, oldStore, oldSessPool := newRuntimeHandleTestManager(targetKS) |
| 317 | + factoryGetter := unusedRuntimeHandleFactoryGetter(t) |
| 318 | + createCount := 0 |
| 319 | + testfailpoint.EnableCall(t, "github.com/pingcap/tidb/pkg/domain/crossks/mockCreateSessionManager", |
| 320 | + func(createSessionManager *func(string, func(string, validatorapi.Validator) pools.Factory) (*SessionManager, error)) { |
| 321 | + *createSessionManager = func(ks string, _ func(string, validatorapi.Validator) pools.Factory) (*SessionManager, error) { |
| 322 | + createCount++ |
| 323 | + newStore := &runtimeHandleTestStore{ks: ks} |
| 324 | + newSessPool := &runtimeHandleTestSessPool{} |
| 325 | + return newRuntimeHandleTestSessionManager(newStore, newSessPool), nil |
| 326 | + } |
| 327 | + }, |
| 328 | + ) |
| 329 | + |
| 330 | + handle, err := mgr.Acquire(targetKS, "holder-1", factoryGetter) |
| 331 | + require.NoError(t, err) |
| 332 | + handle.Release() |
| 333 | + entry.lastReleaseAt = time.Now().Add(-crossKSRuntimeIdleTimeout - time.Second) |
| 334 | + |
| 335 | + mgr.sweepIdleRuntimes(crossKSRuntimeIdleTimeout) |
| 336 | + require.Equal(t, 1, oldSessPool.closeCount) |
| 337 | + require.Equal(t, 1, oldStore.closeCount) |
| 338 | + |
| 339 | + reacquired, err := mgr.Acquire(targetKS, "holder-2", factoryGetter) |
| 340 | + require.NoError(t, err) |
| 341 | + require.NotSame(t, oldStore, reacquired.Store()) |
| 342 | + require.Equal(t, 1, createCount) |
| 343 | + reacquired.Release() |
| 344 | + }) |
| 345 | +} |
| 346 | + |
| 347 | +func TestRuntimeHandleManagerCloseClosesAllEntriesRegardlessOfIdleTimeout(t *testing.T) { |
| 348 | + firstKS := "ks-close-runtime-1" |
| 349 | + secondKS := "ks-close-runtime-2" |
| 350 | + mgr, firstEntry, firstStore, firstSessPool := newRuntimeHandleTestManager(firstKS) |
| 351 | + secondStore := &runtimeHandleTestStore{ks: secondKS} |
| 352 | + secondSessPool := &runtimeHandleTestSessPool{} |
| 353 | + mgr.runtimes[secondKS] = &runtimeEntry{ |
| 354 | + sessMgr: newRuntimeHandleTestSessionManager(secondStore, secondSessPool), |
| 355 | + activeHolders: make(map[string]struct{}), |
| 356 | + } |
| 357 | + firstEntry.lastReleaseAt = time.Now() |
| 358 | + |
| 359 | + mgr.Close() |
| 360 | + |
| 361 | + require.Empty(t, mgr.GetAllKeyspace()) |
| 362 | + require.Equal(t, 1, firstSessPool.closeCount) |
| 363 | + require.Equal(t, 1, firstStore.closeCount) |
| 364 | + require.Equal(t, 1, secondSessPool.closeCount) |
| 365 | + require.Equal(t, 1, secondStore.closeCount) |
| 366 | +} |
| 367 | + |
| 368 | +func TestGCLoopExitsWhenContextCancelled(t *testing.T) { |
| 369 | + mgr := NewManager(&runtimeHandleTestStore{ks: keyspace.System}) |
| 370 | + ctx, cancel := context.WithCancel(context.Background()) |
| 371 | + done := make(chan struct{}) |
| 372 | + go func() { |
| 373 | + mgr.RunSystemKSGCLoop(ctx) |
| 374 | + close(done) |
| 375 | + }() |
| 376 | + |
| 377 | + cancel() |
| 378 | + |
| 379 | + require.Eventually(t, func() bool { |
| 380 | + select { |
| 381 | + case <-done: |
| 382 | + return true |
| 383 | + default: |
| 384 | + return false |
| 385 | + } |
| 386 | + }, 5*time.Second, 10*time.Millisecond) |
| 387 | +} |
0 commit comments