@@ -871,6 +871,122 @@ def test_pubsub_shardnumsub(self, r):
871871 channels = [(b"foo" , 1 ), (b"bar" , 2 ), (b"baz" , 3 )]
872872 assert r .pubsub_shardnumsub ("foo" , "bar" , "baz" , target_nodes = "all" ) == channels
873873
874+ @pytest .mark .onlycluster
875+ @skip_if_server_version_lt ("7.0.0" )
876+ def test_ssubscribe_multiple_channels_different_nodes (self , r ):
877+ """
878+ Test subscribing to multiple sharded channels on different nodes.
879+ Validates that the generator properly handles multiple node_pubsub_mapping entries.
880+ """
881+ pubsub = r .pubsub ()
882+ channel1 = "test-channel:{0}"
883+ channel2 = "test-channel:{6}"
884+
885+ # Subscribe to first channel
886+ pubsub .ssubscribe (channel1 )
887+ msg = wait_for_message (pubsub , timeout = 1.0 , func = pubsub .get_sharded_message )
888+ assert msg is not None
889+ assert msg ["type" ] == "ssubscribe"
890+
891+ # Subscribe to second channel (likely different node)
892+ pubsub .ssubscribe (channel2 )
893+ msg = wait_for_message (pubsub , timeout = 1.0 , func = pubsub .get_sharded_message )
894+ assert msg is not None
895+ assert msg ["type" ] == "ssubscribe"
896+
897+ # Verify both channels are in shard_channels
898+ assert channel1 .encode () in pubsub .shard_channels
899+ assert channel2 .encode () in pubsub .shard_channels
900+
901+ pubsub .close ()
902+
903+ @pytest .mark .onlycluster
904+ @skip_if_server_version_lt ("7.0.0" )
905+ def test_ssubscribe_multiple_channels_publish_and_read (self , r ):
906+ """
907+ Test publishing to multiple sharded channels and reading messages.
908+ Validates that _sharded_message_generator properly cycles through
909+ multiple node_pubsub_mapping entries.
910+ """
911+ pubsub = r .pubsub ()
912+ channel1 = "test-channel:{0}"
913+ channel2 = "test-channel:{6}"
914+ msg1_data = "message-1"
915+ msg2_data = "message-2"
916+
917+ # Subscribe to both channels
918+ pubsub .ssubscribe (channel1 , channel2 )
919+
920+ # Read subscription confirmations
921+ for _ in range (2 ):
922+ msg = wait_for_message (pubsub , timeout = 1.0 , func = pubsub .get_sharded_message )
923+ assert msg is not None
924+ assert msg ["type" ] == "ssubscribe"
925+
926+ # Publish messages to both channels
927+ r .spublish (channel1 , msg1_data )
928+ r .spublish (channel2 , msg2_data )
929+
930+ # Read messages - should get both messages
931+ messages = []
932+ for _ in range (2 ):
933+ msg = wait_for_message (pubsub , timeout = 1.0 , func = pubsub .get_sharded_message )
934+ assert msg is not None
935+ assert msg ["type" ] == "smessage"
936+ messages .append (msg )
937+
938+ # Verify we got messages from both channels
939+ channels_received = {msg ["channel" ] for msg in messages }
940+ assert channel1 .encode () in channels_received
941+ assert channel2 .encode () in channels_received
942+
943+ pubsub .close ()
944+
945+ @pytest .mark .onlycluster
946+ @skip_if_server_version_lt ("7.0.0" )
947+ def test_generator_handles_concurrent_mapping_changes (self , r ):
948+ """
949+ Test that the generator properly handles mapping changes during iteration.
950+ This validates the fix for the RuntimeError: dictionary changed size during iteration.
951+ """
952+ pubsub = r .pubsub ()
953+ channel1 = "test-channel:{0}"
954+ channel2 = "test-channel:{6}"
955+
956+ # Subscribe to first channel
957+ pubsub .ssubscribe (channel1 )
958+ msg = wait_for_message (pubsub , timeout = 1.0 , func = pubsub .get_sharded_message )
959+ assert msg is not None
960+ assert msg ["type" ] == "ssubscribe"
961+
962+ # Get initial mapping size (if available)
963+ initial_size = 0
964+ if hasattr (pubsub , "node_pubsub_mapping" ):
965+ initial_size = len (pubsub .node_pubsub_mapping )
966+
967+ # Subscribe to second channel (modifies mapping during potential iteration)
968+ pubsub .ssubscribe (channel2 )
969+ msg = wait_for_message (pubsub , timeout = 1.0 , func = pubsub .get_sharded_message )
970+ assert msg is not None
971+ assert msg ["type" ] == "ssubscribe"
972+
973+ # Verify mapping was updated (if available)
974+ if hasattr (pubsub , "node_pubsub_mapping" ):
975+ assert len (pubsub .node_pubsub_mapping ) >= initial_size
976+
977+ # Publish and read messages - should not raise RuntimeError
978+ r .spublish (channel1 , "msg1" )
979+ r .spublish (channel2 , "msg2" )
980+
981+ messages_received = 0
982+ for _ in range (2 ):
983+ msg = wait_for_message (pubsub , timeout = 1.0 , func = pubsub .get_sharded_message )
984+ if msg and msg ["type" ] == "smessage" :
985+ messages_received += 1
986+
987+ assert messages_received == 2
988+ pubsub .close ()
989+
874990
875991class TestPubSubPings :
876992 @skip_if_server_version_lt ("3.0.0" )
0 commit comments