@@ -912,6 +912,136 @@ async def query_record(index):
912
912
if process .exitcode is None :
913
913
pytest .fail ("Process did not terminate" )
914
914
915
+
916
+ def blocking_test_func_broken_on_update (
917
+ self , device_name , conn , use_asyncio
918
+ ):
919
+
920
+ builder .SetDeviceName (device_name )
921
+
922
+ count_rec = builder .longIn ("BLOCKING-COUNTER" , initial_value = 0 )
923
+
924
+ async def async_blocking_broken_on_update (new_val ):
925
+ """on_update function that always throws an exception"""
926
+ log ("CHILD: blocking_broken_on_update starting" )
927
+ completed_count = count_rec .get () + 1
928
+ count_rec .set (completed_count )
929
+ log (
930
+ f"CHILD: blocking_update_func updated count: { completed_count } " ,
931
+ )
932
+ raise Exception ("on_update is broken!" )
933
+
934
+ def sync_blocking_broken_on_update (new_val ):
935
+ """on_update function that always throws an exception"""
936
+ log ("CHILD: blocking_broken_on_update starting" )
937
+ completed_count = count_rec .get () + 1
938
+ count_rec .set (completed_count )
939
+ log (
940
+ f"CHILD: blocking_update_func updated count: { completed_count } " ,
941
+ )
942
+ raise Exception ("on_update is broken!" )
943
+
944
+ if use_asyncio :
945
+ on_update_callback = async_blocking_broken_on_update
946
+ else :
947
+ on_update_callback = sync_blocking_broken_on_update
948
+
949
+ builder .longOut (
950
+ "BLOCKING-BROKEN-ON-UPDATE" ,
951
+ on_update = on_update_callback ,
952
+ always_update = True ,
953
+ blocking = True
954
+ )
955
+
956
+ if use_asyncio :
957
+ dispatcher = asyncio_dispatcher .AsyncioDispatcher ()
958
+ else :
959
+ dispatcher = None
960
+ builder .LoadDatabase ()
961
+ softioc .iocInit (dispatcher )
962
+
963
+ conn .send ("R" ) # "Ready"
964
+
965
+ log ("CHILD: Sent R over Connection to Parent" )
966
+
967
+ # Keep process alive while main thread runs CAGET
968
+ if not use_asyncio :
969
+ log ("CHILD: Beginning cothread poll_list" )
970
+ import cothread
971
+ cothread .poll_list ([(conn .fileno (), cothread .POLLIN )], TIMEOUT )
972
+ if conn .poll (TIMEOUT ):
973
+ val = conn .recv ()
974
+ assert val == "D" , "Did not receive expected Done character"
975
+
976
+ log ("CHILD: Received exit command, child exiting" )
977
+
978
+ @requires_cothread
979
+ @pytest .mark .asyncio
980
+ @pytest .mark .parametrize ("use_asyncio" , [True , False ])
981
+ async def test_blocking_broken_on_update (self , use_asyncio ):
982
+ """Test that a blocking record with an on_update record that will
983
+ always throw an exception will not permanently block record processing.
984
+
985
+ Runs using both cothread and asyncio dispatchers in the IOC."""
986
+ ctx = get_multiprocessing_context ()
987
+
988
+ parent_conn , child_conn = ctx .Pipe ()
989
+
990
+ device_name = create_random_prefix ()
991
+
992
+ process = ctx .Process (
993
+ target = self .blocking_test_func_broken_on_update ,
994
+ args = (device_name , child_conn , use_asyncio ),
995
+ )
996
+
997
+ process .start ()
998
+
999
+ log ("PARENT: Child started, waiting for R command" )
1000
+
1001
+ from aioca import caget , caput
1002
+
1003
+ try :
1004
+ # Wait for message that IOC has started
1005
+ select_and_recv (parent_conn , "R" )
1006
+
1007
+ log ("PARENT: received R command" )
1008
+
1009
+ assert await caget (device_name + ":BLOCKING-COUNTER" ) == 0
1010
+
1011
+ log ("PARENT: BLOCKING-COUNTER was 0" )
1012
+
1013
+ await caput (
1014
+ device_name + ":BLOCKING-BROKEN-ON-UPDATE" ,
1015
+ 1 ,
1016
+ wait = True ,
1017
+ timeout = TIMEOUT
1018
+ )
1019
+
1020
+ assert await caget (device_name + ":BLOCKING-COUNTER" ) == 1
1021
+
1022
+ await caput (
1023
+ device_name + ":BLOCKING-BROKEN-ON-UPDATE" ,
1024
+ 2 ,
1025
+ wait = True ,
1026
+ timeout = TIMEOUT
1027
+ )
1028
+
1029
+ assert await caget (device_name + ":BLOCKING-COUNTER" ) == 2
1030
+
1031
+
1032
+ finally :
1033
+ # Clear the cache before stopping the IOC stops
1034
+ # "channel disconnected" error messages
1035
+ aioca_cleanup ()
1036
+
1037
+ log ("PARENT: Sending Done command to child" )
1038
+ parent_conn .send ("D" ) # "Done"
1039
+ process .join (timeout = TIMEOUT )
1040
+ log (f"PARENT: Join completed with exitcode { process .exitcode } " )
1041
+ if process .exitcode is None :
1042
+ pytest .fail ("Process did not terminate" )
1043
+
1044
+
915
1045
class TestGetSetField :
916
1046
"""Tests related to get_field and set_field on records"""
917
1047
0 commit comments