5
5
#if NET
6
6
using System ;
7
7
using System . Collections . Concurrent ;
8
+ using System . Collections . Generic ;
8
9
using System . Data ;
9
10
using System . Data . Common ;
10
11
using System . Diagnostics ;
@@ -96,19 +97,44 @@ internal override bool TryGetConnection(DbConnection owningObject, TaskCompletio
96
97
{
97
98
ThreadPool . QueueUserWorkItem ( async ( _ ) =>
98
99
{
99
- var connection = await GetInternalConnection ( owningObject , userOptions , TimeSpan . Zero , false , CancellationToken . None ) . ConfigureAwait ( false ) ;
100
+ //TODO: use same timespan everywhere and tick down for queueuing and actual connection opening work
101
+ var connection = await GetInternalConnection ( owningObject , userOptions , TimeSpan . FromSeconds ( owningObject . ConnectionTimeout ) , false , CancellationToken . None ) . ConfigureAwait ( false ) ;
102
+ //TODO set transaction if necessary
103
+ PrepareConnection ( owningObject , connection , null ) ;
100
104
taskCompletionSource . SetResult ( connection ) ;
101
105
} ) ;
102
106
connection = null ;
103
107
return false ;
104
108
}
105
109
else
106
110
{
107
- connection = GetInternalConnection ( owningObject , userOptions , TimeSpan . Zero , false , CancellationToken . None ) . Result ;
111
+ //TODO: use same timespan everywhere and tick down for queueuing and actual connection opening work
112
+ connection = GetInternalConnection ( owningObject , userOptions , TimeSpan . FromSeconds ( owningObject . ConnectionTimeout ) , false , CancellationToken . None ) . Result ;
113
+ //TODO set transaction if necessary
114
+ PrepareConnection ( owningObject , connection , null ) ;
108
115
return connection is not null ;
109
116
}
110
117
}
111
118
119
+ private void PrepareConnection ( DbConnection owningObject , DbConnectionInternal obj , Transaction ? transaction )
120
+ {
121
+ lock ( obj )
122
+ { // Protect against Clear and ReclaimEmancipatedObjects, which call IsEmancipated, which is affected by PrePush and PostPop
123
+ obj . PostPop ( owningObject ) ;
124
+ }
125
+ try
126
+ {
127
+ obj . ActivateConnection ( transaction ) ;
128
+ }
129
+ catch
130
+ {
131
+ // if Activate throws an exception
132
+ // put it back in the pool or have it properly disposed of
133
+ this . ReturnInternalConnection ( obj , owningObject ) ;
134
+ throw ;
135
+ }
136
+ }
137
+
112
138
/// <summary>
113
139
/// Creates a new connection to replace an existing connection
114
140
/// </summary>
@@ -125,9 +151,40 @@ internal override DbConnectionInternal ReplaceConnection(DbConnection owningObje
125
151
126
152
}
127
153
128
- internal override void ReturnInternalConnection ( DbConnectionInternal obj , object owningObject )
154
+ internal override void ReturnInternalConnection ( DbConnectionInternal connector , object ? owningObject )
129
155
{
130
- ReturnInternalConnection ( obj ) ;
156
+ // Once a connection is closing (which is the state that we're in at
157
+ // this point in time) you cannot delegate a transaction to or enlist
158
+ // a transaction in it, so we can correctly presume that if there was
159
+ // not a delegated or enlisted transaction to start with, that there
160
+ // will not be a delegated or enlisted transaction once we leave the
161
+ // lock.
162
+
163
+ lock ( connector )
164
+ {
165
+ // Calling PrePush prevents the object from being reclaimed
166
+ // once we leave the lock, because it sets _pooledCount such
167
+ // that it won't appear to be out of the pool. What that
168
+ // means, is that we're now responsible for this connection:
169
+ // it won't get reclaimed if it gets lost.
170
+ connector . PrePush ( owningObject ) ;
171
+
172
+ // TODO: Consider using a Cer to ensure that we mark the object for reclaimation in the event something bad happens?
173
+ }
174
+
175
+ //TODO: verify transaction state
176
+
177
+ if ( ! CheckConnector ( connector ) )
178
+ {
179
+ return ;
180
+ }
181
+
182
+ connector . DeactivateConnection ( ) ;
183
+
184
+ // Statement order is important since we have synchronous completions on the channel.
185
+ Interlocked . Increment ( ref _idleCount ) ;
186
+ var written = _idleConnectorWriter . TryWrite ( connector ) ;
187
+ Debug . Assert ( written ) ;
131
188
}
132
189
133
190
internal override void PutObjectFromTransactedPool ( DbConnectionInternal obj )
@@ -306,7 +363,6 @@ internal async ValueTask<DbConnectionInternal> GetInternalConnection(DbConnectio
306
363
if ( connector != null )
307
364
{
308
365
// TODO: transactions
309
- connector . ActivateConnection ( null ) ;
310
366
return connector ;
311
367
}
312
368
@@ -315,7 +371,6 @@ internal async ValueTask<DbConnectionInternal> GetInternalConnection(DbConnectio
315
371
if ( connector != null )
316
372
{
317
373
// TODO: transactions
318
- connector . ActivateConnection ( null ) ;
319
374
return connector ;
320
375
}
321
376
@@ -360,7 +415,6 @@ internal async ValueTask<DbConnectionInternal> GetInternalConnection(DbConnectio
360
415
if ( CheckIdleConnector ( connector ) )
361
416
{
362
417
// TODO: transactions
363
- connector . ActivateConnection ( null ) ;
364
418
return connector;
365
419
}
366
420
}
@@ -369,8 +423,7 @@ internal async ValueTask<DbConnectionInternal> GetInternalConnection(DbConnectio
369
423
cancellationToken . ThrowIfCancellationRequested ( ) ;
370
424
Debug . Assert ( finalToken . IsCancellationRequested ) ;
371
425
372
- //TODO: exceptions from resource file
373
- throw new Exception( "Pool exhausted", new TimeoutException( ) ) ;
426
+ throw ADP. PooledOpenTimeout( ) ;
374
427
}
375
428
catch ( ChannelClosedException)
376
429
{
@@ -588,6 +641,8 @@ internal readonly struct OpenInternalConnectionState
588
641
throw ADP . InternalError ( ADP . InternalErrorCode . NewObjectCannotBePooled ) ; // CreateObject succeeded, but non-poolable object
589
642
}
590
643
644
+ newConnection . PrePush ( null ) ;
645
+
591
646
int i ;
592
647
for ( i = 0 ; i < state . Pool . MaxPoolSize ; i++ )
593
648
{
@@ -624,25 +679,6 @@ internal readonly struct OpenInternalConnectionState
624
679
}
625
680
}
626
681
627
- /// <inheritdoc/>
628
- internal void ReturnInternalConnection( DbConnectionInternal connector )
629
- {
630
-
631
- //TODO: verify transaction state
632
-
633
- if ( ! CheckConnector ( connector ) )
634
- {
635
- return ;
636
- }
637
-
638
- connector. DeactivateConnection ( ) ;
639
-
640
- // Statement order is important since we have synchronous completions on the channel.
641
- Interlocked. Increment ( ref _idleCount ) ;
642
- var written = _idleConnectorWriter. TryWrite ( connector ) ;
643
- Debug. Assert ( written ) ;
644
- }
645
-
646
682
/// <summary>
647
683
/// Initiates a task to wait on the pruning timer. Spins off child tasks to perform pruning
648
684
/// each time the timer ticks.
@@ -846,7 +882,7 @@ async Task _WarmUp(CancellationToken ct)
846
882
847
883
// The connector has never been used, so it's safe to immediately return it to the
848
884
// pool without resetting it.
849
- ReturnInternalConnection( connector ) ;
885
+ ReturnInternalConnection( connector , null ) ;
850
886
}
851
887
}
852
888
}
0 commit comments