15
15
using System . IO ;
16
16
using System . Linq ;
17
17
using System . Threading ;
18
+ using System . Threading . Tasks ;
19
+ using Nito . AsyncEx ;
18
20
using ServiceStack . Caching ;
19
21
using ServiceStack . Logging ;
20
22
using ServiceStack . Text ;
@@ -34,6 +36,9 @@ public partial class PooledRedisClientManager
34
36
private const string PoolTimeoutError =
35
37
"Redis Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use." ;
36
38
39
+ private AsyncManualResetEvent readAsyncEvent ;
40
+ private AsyncManualResetEvent writeAsyncEvent ;
41
+
37
42
protected readonly int PoolSizeMultiplier = 20 ;
38
43
public int RecheckPoolAfterMs = 100 ;
39
44
public int ? PoolTimeout { get ; set ; }
@@ -42,7 +47,7 @@ public partial class PooledRedisClientManager
42
47
public int ? SocketReceiveTimeout { get ; set ; }
43
48
public int ? IdleTimeOutSecs { get ; set ; }
44
49
public bool AssertAccessOnlyOnSameThread { get ; set ; }
45
-
50
+
46
51
/// <summary>
47
52
/// Gets or sets object key prefix.
48
53
/// </summary>
@@ -212,13 +217,136 @@ protected virtual void OnStart()
212
217
this . Start ( ) ;
213
218
}
214
219
220
+ private void pulseAllRead ( )
221
+ {
222
+ readAsyncEvent ? . Set ( ) ;
223
+ readAsyncEvent ? . Reset ( ) ;
224
+ Monitor . PulseAll ( readClients ) ;
225
+ }
226
+
227
+ private void pulseAllWrite ( )
228
+ {
229
+ writeAsyncEvent ? . Set ( ) ;
230
+ writeAsyncEvent ? . Reset ( ) ;
231
+ Monitor . PulseAll ( writeClients ) ;
232
+ }
233
+
234
+ private async Task < bool > waitForWriter ( int msTimeout )
235
+ {
236
+ if ( writeAsyncEvent == null ) // If we're not doing async, no need to create this till we need it.
237
+ writeAsyncEvent = new AsyncManualResetEvent ( false ) ;
238
+ var cts = new CancellationTokenSource ( TimeSpan . FromMilliseconds ( msTimeout ) ) ;
239
+ try
240
+ {
241
+ await writeAsyncEvent . WaitAsync ( cts . Token ) ;
242
+ }
243
+ catch ( OperationCanceledException ) { return false ; }
244
+ return true ;
245
+ }
246
+
215
247
/// <summary>
216
248
/// Returns a Read/Write client (The default) using the hosts defined in ReadWriteHosts
217
249
/// </summary>
218
250
/// <returns></returns>
219
- public IRedisClient GetClient ( ) => GetClient ( false ) ;
251
+ public IRedisClient GetClient ( ) => GetClientBlocking ( ) ;
220
252
221
- private RedisClient GetClient ( bool forAsync )
253
+ private async ValueTask < IRedisClientAsync > GetClientAsync ( )
254
+ {
255
+ try
256
+ {
257
+ var inactivePoolIndex = - 1 ;
258
+ do
259
+ {
260
+ RedisClient inActiveClient ;
261
+ lock ( writeClients )
262
+ {
263
+ AssertValidReadWritePool ( ) ;
264
+
265
+ // If it's -1, then we want to try again after a delay of some kind. So if it's NOT negative one, process it...
266
+ if ( ( inactivePoolIndex = GetInActiveWriteClient ( out inActiveClient ) ) != - 1 )
267
+ {
268
+ //inActiveClient != null only for Valid InActive Clients
269
+ if ( inActiveClient != null )
270
+ {
271
+ WritePoolIndex ++ ;
272
+ inActiveClient . Activate ( ) ;
273
+
274
+ InitClient ( inActiveClient ) ;
275
+
276
+ return inActiveClient ;
277
+ }
278
+ else
279
+ {
280
+ // Still need to be in lock for this!
281
+ break ;
282
+ }
283
+ }
284
+ }
285
+
286
+ if ( PoolTimeout . HasValue )
287
+ {
288
+ // We have a timeout value set - so try to not wait longer than this.
289
+ if ( ! await waitForWriter ( PoolTimeout . Value ) )
290
+ {
291
+ throw new TimeoutException ( PoolTimeoutError ) ;
292
+ }
293
+ }
294
+ else
295
+ {
296
+ // Wait forever, so just retry till we get one.
297
+ await waitForWriter ( RecheckPoolAfterMs ) ;
298
+ }
299
+ } while ( true ) ; // Just keep repeating until we get a slot.
300
+
301
+ //Reaches here when there's no Valid InActive Clients, but we have a slot for one!
302
+ try
303
+ {
304
+ //inactivePoolIndex = index of reservedSlot || index of invalid client
305
+ var existingClient = writeClients [ inactivePoolIndex ] ;
306
+ if ( existingClient != null && existingClient != reservedSlot && existingClient . HadExceptions )
307
+ {
308
+ RedisState . DeactivateClient ( existingClient ) ;
309
+ }
310
+
311
+ var newClient = InitNewClient ( RedisResolver . CreateMasterClient ( inactivePoolIndex ) ) ;
312
+
313
+ //Put all blocking I/O or potential Exceptions before lock
314
+ lock ( writeClients )
315
+ {
316
+ //If existingClient at inactivePoolIndex changed (failover) return new client outside of pool
317
+ if ( writeClients [ inactivePoolIndex ] != existingClient )
318
+ {
319
+ if ( Log . IsDebugEnabled )
320
+ Log . Debug ( "writeClients[inactivePoolIndex] != existingClient: {0}" . Fmt ( writeClients [ inactivePoolIndex ] ) ) ;
321
+
322
+ return newClient ; //return client outside of pool
323
+ }
324
+
325
+ WritePoolIndex ++ ;
326
+ writeClients [ inactivePoolIndex ] = newClient ;
327
+
328
+ return ( ! AssertAccessOnlyOnSameThread )
329
+ ? newClient
330
+ : newClient . LimitAccessToThread ( Thread . CurrentThread . ManagedThreadId , Environment . StackTrace ) ;
331
+ }
332
+ }
333
+ catch
334
+ {
335
+ //Revert free-slot for any I/O exceptions that can throw (before lock)
336
+ lock ( writeClients )
337
+ {
338
+ writeClients [ inactivePoolIndex ] = null ; //free slot
339
+ }
340
+ throw ;
341
+ }
342
+ }
343
+ finally
344
+ {
345
+ RedisState . DisposeExpiredClients ( ) ;
346
+ }
347
+ }
348
+
349
+ private RedisClient GetClientBlocking ( )
222
350
{
223
351
try
224
352
{
@@ -254,7 +382,7 @@ private RedisClient GetClient(bool forAsync)
254
382
255
383
InitClient ( inActiveClient ) ;
256
384
257
- return ( ! AssertAccessOnlyOnSameThread || forAsync )
385
+ return ( ! AssertAccessOnlyOnSameThread )
258
386
? inActiveClient
259
387
: inActiveClient . LimitAccessToThread ( Thread . CurrentThread . ManagedThreadId , Environment . StackTrace ) ;
260
388
}
@@ -290,7 +418,7 @@ private RedisClient GetClient(bool forAsync)
290
418
WritePoolIndex ++ ;
291
419
writeClients [ inactivePoolIndex ] = newClient ;
292
420
293
- return ( ! AssertAccessOnlyOnSameThread || forAsync )
421
+ return ( ! AssertAccessOnlyOnSameThread )
294
422
? newClient
295
423
: newClient . LimitAccessToThread ( Thread . CurrentThread . ManagedThreadId , Environment . StackTrace ) ;
296
424
}
@@ -546,7 +674,7 @@ public void DisposeClient(RedisNativeClient client)
546
674
client . Deactivate ( ) ;
547
675
}
548
676
549
- Monitor . PulseAll ( readClients ) ;
677
+ pulseAllRead ( ) ;
550
678
return ;
551
679
}
552
680
}
@@ -567,16 +695,21 @@ public void DisposeClient(RedisNativeClient client)
567
695
client . Deactivate ( ) ;
568
696
}
569
697
570
- Monitor . PulseAll ( writeClients ) ;
698
+ pulseAllWrite ( ) ;
571
699
return ;
572
700
}
573
701
}
574
702
575
703
//Client not found in any pool, pulse both pools.
576
704
lock ( readClients )
577
- Monitor . PulseAll ( readClients ) ;
705
+ {
706
+ pulseAllRead ( ) ;
707
+ }
708
+
578
709
lock ( writeClients )
579
- Monitor . PulseAll ( writeClients ) ;
710
+ {
711
+ pulseAllWrite ( ) ;
712
+ }
580
713
}
581
714
582
715
/// <summary>
@@ -588,7 +721,7 @@ public void DisposeReadOnlyClient(RedisNativeClient client)
588
721
lock ( readClients )
589
722
{
590
723
client . Deactivate ( ) ;
591
- Monitor . PulseAll ( readClients ) ;
724
+ pulseAllRead ( ) ;
592
725
}
593
726
}
594
727
@@ -601,7 +734,7 @@ public void DisposeWriteClient(RedisNativeClient client)
601
734
lock ( writeClients )
602
735
{
603
736
client . Deactivate ( ) ;
604
- Monitor . PulseAll ( writeClients ) ;
737
+ pulseAllWrite ( ) ;
605
738
}
606
739
}
607
740
0 commit comments