@@ -852,13 +852,10 @@ def sync_task():
852
852
853
853
854
854
@pytest .mark .asyncio
855
- @pytest .mark .skip (reason = "deadlocks" )
856
855
async def test_inner_shield_sync_middleware ():
857
856
"""
858
857
Tests that asyncio.shield is capable of preventing http.disconnect from
859
858
cancelling a django request task when using sync middleware.
860
-
861
- Currently this tests is skipped as it causes a deadlock.
862
859
"""
863
860
864
861
# Hypothetical Django scenario - middleware function is sync
@@ -968,3 +965,157 @@ async def async_task():
968
965
assert task_complete
969
966
970
967
assert task_executed
968
+
969
+
970
+ @pytest .mark .asyncio
971
+ async def test_inner_shield_sync_and_async_middleware ():
972
+ """
973
+ Tests that asyncio.shield is capable of preventing http.disconnect from
974
+ cancelling a django request task when using sync and middleware chained
975
+ together.
976
+ """
977
+
978
+ # Hypothetical Django scenario - middleware function is sync
979
+ def sync_middleware_1 ():
980
+ async_to_sync (async_middleware_2 )()
981
+
982
+ # Hypothetical Django scenario - middleware function is async
983
+ async def async_middleware_2 ():
984
+ await sync_to_async (sync_middleware_3 )()
985
+
986
+ # Hypothetical Django scenario - middleware function is sync
987
+ def sync_middleware_3 ():
988
+ async_to_sync (async_middleware_4 )()
989
+
990
+ # Hypothetical Django scenario - middleware function is async
991
+ async def async_middleware_4 ():
992
+ await sync_to_async (sync_middleware_5 )()
993
+
994
+ # Hypothetical Django scenario - middleware function is sync
995
+ def sync_middleware_5 ():
996
+ async_to_sync (async_view )()
997
+
998
+ task_complete = False
999
+ task_cancel_caught = False
1000
+
1001
+ # Future that completes when subtask cancellation attempt is caught
1002
+ task_blocker = asyncio .Future ()
1003
+
1004
+ async def async_view ():
1005
+ """Async view with a task that is shielded from cancellation."""
1006
+ nonlocal task_complete , task_cancel_caught , task_blocker
1007
+ task = asyncio .create_task (async_task ())
1008
+ try :
1009
+ await asyncio .shield (task )
1010
+ except asyncio .CancelledError :
1011
+ task_cancel_caught = True
1012
+ task_blocker .set_result (True )
1013
+ await task
1014
+ task_complete = True
1015
+
1016
+ task_executed = False
1017
+
1018
+ # Future that completes after subtask is created
1019
+ task_started_future = asyncio .Future ()
1020
+
1021
+ async def async_task ():
1022
+ """Async subtask that should not be canceled when parent is canceled."""
1023
+ nonlocal task_started_future , task_executed , task_blocker
1024
+ task_started_future .set_result (True )
1025
+ await task_blocker
1026
+ task_executed = True
1027
+
1028
+ task_cancel_propagated = False
1029
+
1030
+ async with ThreadSensitiveContext ():
1031
+ task = asyncio .create_task (sync_to_async (sync_middleware_1 )())
1032
+ await task_started_future
1033
+ task .cancel ()
1034
+ try :
1035
+ await task
1036
+ except asyncio .CancelledError :
1037
+ task_cancel_propagated = True
1038
+ assert not task_cancel_propagated
1039
+ assert task_cancel_caught
1040
+ assert task_complete
1041
+
1042
+ assert task_executed
1043
+
1044
+
1045
+ @pytest .mark .asyncio
1046
+ async def test_inner_shield_sync_and_async_middleware_sync_task ():
1047
+ """
1048
+ Tests that asyncio.shield is capable of preventing http.disconnect from
1049
+ cancelling a django request task when using sync and middleware chained
1050
+ together with an async view calling a sync calling an async task through
1051
+ a sync parent.
1052
+ """
1053
+
1054
+ # Hypothetical Django scenario - middleware function is sync
1055
+ def sync_middleware_1 ():
1056
+ async_to_sync (async_middleware_2 )()
1057
+
1058
+ # Hypothetical Django scenario - middleware function is async
1059
+ async def async_middleware_2 ():
1060
+ await sync_to_async (sync_middleware_3 )()
1061
+
1062
+ # Hypothetical Django scenario - middleware function is sync
1063
+ def sync_middleware_3 ():
1064
+ async_to_sync (async_middleware_4 )()
1065
+
1066
+ # Hypothetical Django scenario - middleware function is async
1067
+ async def async_middleware_4 ():
1068
+ await sync_to_async (sync_middleware_5 )()
1069
+
1070
+ # Hypothetical Django scenario - middleware function is sync
1071
+ def sync_middleware_5 ():
1072
+ async_to_sync (async_view )()
1073
+
1074
+ task_complete = False
1075
+ task_cancel_caught = False
1076
+
1077
+ # Future that completes when subtask cancellation attempt is caught
1078
+ task_blocker = asyncio .Future ()
1079
+
1080
+ async def async_view ():
1081
+ """Async view with a task that is shielded from cancellation."""
1082
+ nonlocal task_complete , task_cancel_caught , task_blocker
1083
+ task = asyncio .create_task (sync_to_async (sync_parent )())
1084
+ try :
1085
+ await asyncio .shield (task )
1086
+ except asyncio .CancelledError :
1087
+ task_cancel_caught = True
1088
+ task_blocker .set_result (True )
1089
+ await task
1090
+ task_complete = True
1091
+
1092
+ task_executed = False
1093
+
1094
+ # Future that completes after subtask is created
1095
+ task_started_future = asyncio .Future ()
1096
+
1097
+ def sync_parent ():
1098
+ async_to_sync (async_task )()
1099
+
1100
+ async def async_task ():
1101
+ """Async subtask that should not be canceled when parent is canceled."""
1102
+ nonlocal task_started_future , task_executed , task_blocker
1103
+ task_started_future .set_result (True )
1104
+ await task_blocker
1105
+ task_executed = True
1106
+
1107
+ task_cancel_propagated = False
1108
+
1109
+ async with ThreadSensitiveContext ():
1110
+ task = asyncio .create_task (sync_to_async (sync_middleware_1 )())
1111
+ await task_started_future
1112
+ task .cancel ()
1113
+ try :
1114
+ await task
1115
+ except asyncio .CancelledError :
1116
+ task_cancel_propagated = True
1117
+ assert not task_cancel_propagated
1118
+ assert task_cancel_caught
1119
+ assert task_complete
1120
+
1121
+ assert task_executed
0 commit comments