@@ -1734,54 +1734,68 @@ internal void ResetCancellation()
17341734 internal void PerformUserCancellation( )
17351735 {
17361736 var connection = Connection;
1737- if ( connection is null || connection . ConnectorBindingScope == ConnectorBindingScope . Reader )
1737+ if ( connection is null || connection . ConnectorBindingScope == ConnectorBindingScope . Reader || UserCancellationRequested )
17381738 return;
17391739
1740- // Take the lock first to make sure there is no concurrent Break.
1741- // We should be safe to take it as Break only take it to set the state.
1742- lock ( SyncObj )
1740+ // Run the cancellation on a threadpool
1741+ // Because PerformUserCancellation is used as a callback for CancellationToken, which can trigger synchronously
1742+ // And this can lead to a deadlock
1743+ // See #5032
1744+ Task. Run ( ( ) =>
17431745 {
1744- // The connector is dead, exit gracefully.
1745- if ( ! IsConnected )
1746- return;
1747- // The connector is still alive, take the CancelLock before exiting SingleUseLock.
1748- // If a break will happen after, it's going to wait for the cancellation to complete.
1749- Monitor. Enter ( CancelLock ) ;
1750- }
1751-
1752- // Wait before we've read all responses for the prepended queries
1753- // as we can't gracefully handle their cancellation.
1754- // Break makes sure that it's going to be set even if we fail while reading them.
1755- ReadingPrependedMessagesMRE. Wait ( ) ;
1746+ try
1747+ {
1748+ // Take the lock first to make sure there is no concurrent Break.
1749+ // We should be safe to take it as Break only take it to set the state.
1750+ lock ( SyncObj )
1751+ {
1752+ // The connector is dead, exit gracefully.
1753+ if ( ! IsConnected )
1754+ return ;
1755+ // The connector is still alive, take the CancelLock before exiting SingleUseLock.
1756+ // If a break will happen after, it's going to wait for the cancellation to complete.
1757+ Monitor . Enter ( CancelLock ) ;
1758+ }
17561759
1757- try
1758- {
1759- _userCancellationRequested = true;
1760+ // Wait before we've read all responses for the prepended queries
1761+ // as we can't gracefully handle their cancellation.
1762+ // Break makes sure that it's going to be set even if we fail while reading them.
1763+ ReadingPrependedMessagesMRE . Wait ( ) ;
17601764
1761- if ( AttemptPostgresCancellation && SupportsPostgresCancellation )
1762- {
1763- var cancellationTimeout = Settings. CancellationTimeout ;
1764- if ( PerformPostgresCancellation ( ) && cancellationTimeout >= 0 )
1765+ try
17651766 {
1766- if ( cancellationTimeout > 0 )
1767+ _userCancellationRequested = true ;
1768+
1769+ if ( AttemptPostgresCancellation && SupportsPostgresCancellation )
17671770 {
1768- UserTimeout = cancellationTimeout;
1769- ReadBuffer. Timeout = TimeSpan . FromMilliseconds ( cancellationTimeout ) ;
1770- ReadBuffer. Cts . CancelAfter ( cancellationTimeout ) ;
1771+ var cancellationTimeout = Settings . CancellationTimeout ;
1772+ if ( PerformPostgresCancellation ( ) && cancellationTimeout >= 0 )
1773+ {
1774+ if ( cancellationTimeout > 0 )
1775+ {
1776+ UserTimeout = cancellationTimeout ;
1777+ ReadBuffer . Timeout = TimeSpan . FromMilliseconds ( cancellationTimeout ) ;
1778+ ReadBuffer . Cts . CancelAfter ( cancellationTimeout ) ;
1779+ }
1780+
1781+ return ;
1782+ }
17711783 }
17721784
1773- return ;
1785+ UserTimeout = - 1 ;
1786+ ReadBuffer . Timeout = _cancelImmediatelyTimeout ;
1787+ ReadBuffer . Cts . Cancel ( ) ;
1788+ }
1789+ finally
1790+ {
1791+ Monitor . Exit ( CancelLock ) ;
17741792 }
17751793 }
1776-
1777- UserTimeout = - 1 ;
1778- ReadBuffer . Timeout = _cancelImmediatelyTimeout ;
1779- ReadBuffer . Cts . Cancel ( ) ;
1780- }
1781- finally
1782- {
1783- Monitor . Exit ( CancelLock ) ;
1784- }
1794+ catch
1795+ {
1796+ // Ignore
1797+ }
1798+ } ) ;
17851799 }
17861800
17871801 /// <summary>
0 commit comments