@@ -19,6 +19,7 @@ import (
1919 "time"
2020
2121 "github.com/pingcap/ticdc/heartbeatpb"
22+ "github.com/pingcap/ticdc/logservice/logpuller/regionlock"
2223 "github.com/stretchr/testify/require"
2324 "github.com/tikv/client-go/v2/tikv"
2425)
@@ -38,7 +39,9 @@ func createTestRegionInfo(subID SubscriptionID, regionID uint64) regionInfo {
3839 span : span ,
3940 }
4041
41- return newRegionInfo (verID , span , nil , subscribedSpan , false )
42+ region := newRegionInfo (verID , span , nil , subscribedSpan , false )
43+ region .lockedRangeState = & regionlock.LockedRangeState {}
44+ return region
4245}
4346
4447func TestRequestCacheAdd_NormalCase (t * testing.T ) {
@@ -77,14 +80,7 @@ func TestRequestCacheAdd_ForceFlag(t *testing.T) {
7780 require .False (t , ok )
7881 require .NoError (t , err )
7982
80- // With force=true, it should still fail because the channel is full
81- // The force flag only bypasses the pendingCount check, not the channel capacity
82- region3 := createTestRegionInfo (1 , 3 )
83- ok , err = cache .add (ctx , region3 , true )
84- require .False (t , ok )
85- require .NoError (t , err )
86-
87- // consume the pending queue ann add with force
83+ // Move the normal request to sentRequests so the pending queue has room.
8884 req , err := cache .pop (ctx )
8985 require .NoError (t , err )
9086 require .NotNil (t , req )
@@ -93,15 +89,91 @@ func TestRequestCacheAdd_ForceFlag(t *testing.T) {
9389 cache .markSent (req )
9490 require .Equal (t , 1 , cache .getPendingCount ())
9591
92+ // A forced data request can use one extra slot.
93+ region3 := createTestRegionInfo (1 , 3 )
9694 ok , err = cache .add (ctx , region3 , true )
9795 require .True (t , ok )
9896 require .NoError (t , err )
99- // It is 2 since region1 is unresolved
10097 require .Equal (t , 2 , cache .getPendingCount ())
10198
102- // resolve region1
103- cache .resolve (region1 .subscribedSpan .subID , region1 .verID .GetID ())
99+ // No additional forced data request can exceed the N+1 ceiling.
100+ req , err = cache .pop (ctx )
101+ require .NoError (t , err )
102+ cache .markSent (req )
103+ region4 := createTestRegionInfo (1 , 4 )
104+ ok , err = cache .add (ctx , region4 , true )
105+ require .False (t , ok )
106+ require .NoError (t , err )
107+ require .Equal (t , 2 , cache .getPendingCount ())
108+
109+ // Stop/control requests keep their existing bypass and remain accounted.
110+ stopRegion := createTestRegionInfo (2 , 5 )
111+ stopRegion .lockedRangeState = nil
112+ ok , err = cache .add (ctx , stopRegion , true )
113+ require .True (t , ok )
114+ require .NoError (t , err )
115+ require .Equal (t , 3 , cache .getPendingCount ())
116+
117+ stopReq , err := cache .pop (ctx )
118+ require .NoError (t , err )
119+ cache .markSent (stopReq )
120+ cache .markStopped (stopReq .regionInfo .subscribedSpan .subID , stopReq .regionInfo .verID .GetID ())
121+ require .Equal (t , 2 , cache .getPendingCount ())
122+
123+ require .True (t , cache .resolve (region1 .subscribedSpan .subID , region1 .verID .GetID ()))
104124 require .Equal (t , 1 , cache .getPendingCount ())
125+ ok , err = cache .add (ctx , region4 , true )
126+ require .True (t , ok )
127+ require .NoError (t , err )
128+ require .Equal (t , 2 , cache .getPendingCount ())
129+ }
130+
131+ func TestRequestCacheAddRollsBackReservedSlot (t * testing.T ) {
132+ cache := newRequestCache (1 )
133+ cache .pendingQueue <- newRegionReq (createTestRegionInfo (1 , 1 ))
134+
135+ ctx , cancel := context .WithCancel (context .Background ())
136+ cancel ()
137+ ok , err := cache .add (ctx , createTestRegionInfo (1 , 2 ), false )
138+ require .False (t , ok )
139+ require .ErrorIs (t , err , context .Canceled )
140+ require .Equal (t , 0 , cache .getPendingCount ())
141+ }
142+
143+ func TestRequestCacheConcurrentForcedAddsStayWithinCeiling (t * testing.T ) {
144+ const normalLimit = 10
145+ cache := newRequestCache (normalLimit )
146+ ctx := context .Background ()
147+
148+ for i := range normalLimit {
149+ ok , err := cache .add (ctx , createTestRegionInfo (1 , uint64 (i + 1 )), false )
150+ require .True (t , ok )
151+ require .NoError (t , err )
152+ }
153+ for range normalLimit {
154+ req , err := cache .pop (ctx )
155+ require .NoError (t , err )
156+ cache .markSent (req )
157+ }
158+
159+ const addCount = 20
160+ results := make (chan bool , addCount )
161+ for i := range addCount {
162+ go func (regionID uint64 ) {
163+ ok , err := cache .add (ctx , createTestRegionInfo (1 , regionID ), true )
164+ require .NoError (t , err )
165+ results <- ok
166+ }(uint64 (normalLimit + i + 1 ))
167+ }
168+
169+ successes := 0
170+ for range addCount {
171+ if <- results {
172+ successes ++
173+ }
174+ }
175+ require .Equal (t , 1 , successes )
176+ require .Equal (t , normalLimit + 1 , cache .getPendingCount ())
105177}
106178
107179func TestRequestCacheAdd_ContextCancellation (t * testing.T ) {
0 commit comments