8080using JetBrains . Annotations ;
8181using Npgsql . BackendMessages ;
8282using Npgsql . Logging ;
83+ using Npgsql . PostgresTypes ;
8384using Npgsql . Schema ;
8485using Npgsql . TypeHandlers ;
8586using NpgsqlTypes ;
160161using System . Threading ;
161162using System . Threading . Tasks ;
162163using JetBrains . Annotations ;
164+ using Npgsql . PostgresTypes ;
163165using Npgsql . TypeHandlers ;
164166using System . Threading ;
165167using System . Threading . Tasks ;
185187using System . IO ;
186188using System . Runtime . InteropServices ;
187189using System . Text ;
190+ using System . Threading ;
191+ using System . Threading . Tasks ;
188192using JetBrains . Annotations ;
189193using System . Threading ;
190194using System . Threading . Tasks ;
@@ -265,14 +269,14 @@ async Task SendAsync(PopulateMethod populateMethod, CancellationToken cancellati
265269 {
266270 var directBuf = new DirectBuffer ( ) ;
267271 var completed = populateMethod ( ref directBuf ) ;
268- await _connector . SendBufferAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
272+ await _connector . SendBufferAsync ( cancellationToken ) ;
269273 if ( completed )
270274 break ; // Sent all messages
271275 // The following is an optimization hack for writing large byte arrays without passing
272276 // through our buffer
273277 if ( directBuf . Buffer != null )
274278 {
275- await _connector . WriteBuffer . DirectWriteAsync ( directBuf . Buffer , directBuf . Offset , directBuf . Size == 0 ? directBuf . Buffer . Length : directBuf . Size , cancellationToken ) . ConfigureAwait ( false ) ;
279+ await _connector . WriteBuffer . DirectWriteAsync ( directBuf . Buffer , directBuf . Offset , directBuf . Size == 0 ? directBuf . Buffer . Length : directBuf . Size , cancellationToken ) ;
276280 directBuf . Buffer = null ;
277281 directBuf . Size = 0 ;
278282 }
@@ -282,9 +286,24 @@ async Task SendAsync(PopulateMethod populateMethod, CancellationToken cancellati
282286 // We've send all the messages for the first statement in a multistatement command.
283287 // If we continue blocking writes for the rest of the messages, we risk a deadlock where
284288 // PostgreSQL sends large results for the first statement, while we're sending large
285- // parameter data for the second. To avoid this, switch to async sends.
286- // See #641
287- RemainingSendTask = SendRemaining ( populateMethod , CancellationToken . None ) ;
289+ // parameter data for the second. To avoid this, switch to async sends. See #641
290+ // When performing async sends here, our regular async code will do ConfigureAwait(false),
291+ // sending continuations to the thread pool. However, this is a synchronous operation -
292+ // so a deadlock may occur where TP threads synchronously block on database input which won't
293+ // become because async continuations can't run (TP is starved).
294+ // To work around this, we send all async continuations to a special synchronization context
295+ // which executes them on a special thread.
296+ var callerSyncContext = SynchronizationContext . Current ;
297+ SynchronizationContext . SetSynchronizationContext ( SingleThreadSynchronizationContext ) ;
298+ try
299+ {
300+ RemainingSendTask = SendRemaining ( populateMethod , CancellationToken . None ) ;
301+ }
302+ finally
303+ {
304+ SynchronizationContext . SetSynchronizationContext ( callerSyncContext ) ;
305+ }
306+
288307 return ;
289308 }
290309 }
@@ -293,7 +312,7 @@ async Task SendAsync(PopulateMethod populateMethod, CancellationToken cancellati
293312 async Task < int > ExecuteNonQueryInternalAsync ( CancellationToken cancellationToken )
294313 {
295314 var connector = CheckReadyAndGetConnector ( ) ;
296- using ( connector . StartUserAction ( ) )
315+ using ( connector . StartUserAction ( this ) )
297316 {
298317 Log . Trace ( "ExecuteNonQuery" , connector . Id ) ;
299318 // Optimization: unprepared unparameterized non-queries can go through the PostgreSQL
@@ -315,7 +334,7 @@ async Task<int> ExecuteNonQueryInternalAsync(CancellationToken cancellationToken
315334 async Task < object > ExecuteScalarInternalAsync ( CancellationToken cancellationToken )
316335 {
317336 var connector = CheckReadyAndGetConnector ( ) ;
318- using ( connector . StartUserAction ( ) )
337+ using ( connector . StartUserAction ( this ) )
319338 {
320339 Log . Trace ( "ExecuteNonScalar" , connector . Id ) ;
321340 using ( var reader = Execute ( CommandBehavior . SequentialAccess | CommandBehavior . SingleRow ) )
@@ -326,7 +345,7 @@ async Task<object> ExecuteScalarInternalAsync(CancellationToken cancellationToke
326345 async Task < NpgsqlDataReader > ExecuteDbDataReaderInternalAsync ( CommandBehavior behavior , CancellationToken cancellationToken )
327346 {
328347 var connector = CheckReadyAndGetConnector ( ) ;
329- connector . StartUserAction ( ) ;
348+ connector . StartUserAction ( this ) ;
330349 try
331350 {
332351 Log . Trace ( "ExecuteReader" , connector . Id ) ;
0 commit comments