@@ -29,12 +29,14 @@ import (
29
29
"github.com/cockroachdb/cockroach/pkg/sql/enum"
30
30
"github.com/cockroachdb/cockroach/pkg/sql/isql"
31
31
"github.com/cockroachdb/cockroach/pkg/sql/sqlliveness"
32
+ "github.com/cockroachdb/cockroach/pkg/sql/sqlliveness/sqllivenesstestutils"
32
33
"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
33
34
"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
34
35
"github.com/cockroachdb/cockroach/pkg/util/admission"
35
36
"github.com/cockroachdb/cockroach/pkg/util/hlc"
36
37
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
37
38
"github.com/cockroachdb/cockroach/pkg/util/log"
39
+ "github.com/cockroachdb/cockroach/pkg/util/syncutil"
38
40
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
39
41
"github.com/cockroachdb/errors"
40
42
"github.com/cockroachdb/logtags"
@@ -1606,3 +1608,119 @@ func TestLeaseCountDetailSessionBased(t *testing.T) {
1606
1608
require .Equal (t , 1 , detail .numSQLInstances )
1607
1609
require .Equal (t , 0 , detail .sampleSQLInstanceID )
1608
1610
}
1611
+
1612
+ // fakeSessionProvider session provider that only overloads the Session function
1613
+ // with a callback.
1614
+ type fakeSessionProvider struct {
1615
+ syncutil.Mutex
1616
+ sqlliveness.Provider
1617
+ getSession func () * sqllivenesstestutils.FakeSession
1618
+ }
1619
+
1620
+ var _ sqlliveness.Provider = & fakeSessionProvider {}
1621
+
1622
+ // Session implements sqlliveness.Provider
1623
+ func (p * fakeSessionProvider ) Session (ctx context.Context ) (sqlliveness.Session , error ) {
1624
+ p .Lock ()
1625
+ defer p .Unlock ()
1626
+ if f := p .getSession (); f != nil {
1627
+ return f , nil
1628
+ }
1629
+ return p .Provider .Session (ctx )
1630
+ }
1631
+
1632
+ // TestLeaseManagerSessionIDChanges validates that the lease manager can acquire
1633
+ // and release leases properly even if the SessionID changes. This can happen
1634
+ // during a fail over scenario, where a new SessionID could be picked up. Which,
1635
+ // should cause us to reacquire leases.
1636
+ func TestLeaseManagerSessionIDChanges (t * testing.T ) {
1637
+ defer leaktest .AfterTest (t )()
1638
+ defer log .Scope (t ).Close (t )
1639
+
1640
+ srv , sqlDB , kvDB := serverutils .StartServer (
1641
+ t , base.TestServerArgs {
1642
+ // Avoid using tenants since async tenant migration steps can acquire
1643
+ // leases on our user tables.
1644
+ DefaultTestTenant : base .TestNeedsTightIntegrationBetweenAPIsAndTestingKnobs ,
1645
+ })
1646
+ defer srv .Stopper ().Stop (context .Background ())
1647
+ s := srv .ApplicationLayer ()
1648
+ leaseManager := s .LeaseManager ().(* Manager )
1649
+
1650
+ runner := sqlutils .MakeSQLRunner (sqlDB )
1651
+ runner .Exec (t , `SET CLUSTER SETTING sql.stats.automatic_collection.enabled = false` )
1652
+ runner .Exec (t , `SET CLUSTER SETTING sql.catalog.descriptor_wait_for_initial_version.enabled = false` )
1653
+
1654
+ runner .Exec (t , `
1655
+ CREATE DATABASE t;
1656
+ CREATE TABLE t.test (k CHAR PRIMARY KEY, v CHAR);
1657
+ ` )
1658
+ tableDesc := desctestutils .TestingGetPublicTableDescriptor (kvDB , s .Codec (), "t" , "test" )
1659
+
1660
+ getLatestLeasedDesc := func () * descriptorVersionState {
1661
+ state := leaseManager .findDescriptorState (tableDesc .GetID (), false )
1662
+ state .mu .Lock ()
1663
+ defer state .mu .Unlock ()
1664
+ descState := state .mu .active .findNewest ()
1665
+ return descState
1666
+ }
1667
+
1668
+ // Set up a fake session provider that will keep changing IDs and every session
1669
+ // will instantly expire. This is like having a zero duration lease in the expiry
1670
+ // model.
1671
+ var nextSessionID atomic.Int64
1672
+ var enableHook atomic.Bool
1673
+
1674
+ fs := fakeSessionProvider {
1675
+ Provider : leaseManager .storage .livenessProvider ,
1676
+ getSession : func () * sqllivenesstestutils.FakeSession {
1677
+ if ! enableHook .Load () {
1678
+ return nil
1679
+ }
1680
+ now := s .Clock ().Now ()
1681
+ return & sqllivenesstestutils.FakeSession {
1682
+ SessionID : sqlliveness .SessionID (fmt .Sprintf ("session-%d" , nextSessionID .Load ())),
1683
+ ExpTS : now ,
1684
+ StartTS : now ,
1685
+ }
1686
+ },
1687
+ }
1688
+
1689
+ // Replace the session provider which only returns expired leases.
1690
+ leaseManager .storage .livenessProvider = & fs
1691
+ defer func () {
1692
+ // Restore the original provider so valid session IDs
1693
+ // are assigned.
1694
+ leaseManager .storage .livenessProvider = fs .Provider
1695
+ }()
1696
+
1697
+ // Repeatedly lease the same descriptor, with the session ID continuously changing
1698
+ // and each one always being expired. This validates that in fail over scenarios,
1699
+ // nothing bad happens if the session is expired.
1700
+ ctx := context .Background ()
1701
+ var previousSessionID sqlliveness.SessionID
1702
+ var previousExpiry hlc.Timestamp
1703
+ for count := 0 ; count < 10 ; count ++ {
1704
+ enableHook .Swap (true )
1705
+ nextSessionID .Add (1 )
1706
+ now := s .Clock ().Now ()
1707
+ desc , err := leaseManager .Acquire (ctx , now , tableDesc .GetID ())
1708
+ require .NoError (t , err )
1709
+ // We expect a new session ID each time, and the descriptor
1710
+ // to be expired.
1711
+ newSessionID := getLatestLeasedDesc ().getSessionID ()
1712
+ newExpiry := getLatestLeasedDesc ().getExpiration (ctx )
1713
+ require .NotEqualf (t , previousSessionID , newSessionID , "session ID should not match" )
1714
+ require .Truef (t , previousExpiry .Less (newExpiry ), "session expiry should be later." )
1715
+ // Disable the hook before the lease query.
1716
+ enableHook .Swap (false )
1717
+ // Sanity: Validate that system.lease only has a single lease with new
1718
+ // id.
1719
+ runner .CheckQueryResults (t , fmt .Sprintf ("SELECT session_id FROM system.lease WHERE desc_id=%d" , tableDesc .GetID ()),
1720
+ [][]string {{string (newSessionID .UnsafeBytes ())}})
1721
+ previousExpiry = newExpiry
1722
+ previousSessionID = newSessionID
1723
+ desc .Release (ctx )
1724
+ }
1725
+
1726
+ }
0 commit comments