@@ -412,7 +412,109 @@ test_partition_count_decrease() ->
412412 ok = application :stop (wolff ),
413413 ok .
414414
415- test_topic_recreate_test_ () ->
415+ % % The last partition is temporarily missing from metadata response.
416+ % % The producer should retry to recover
417+ last_partition_missing_in_metadata_response_test_ () ->
418+ {timeout , 30 , % % it takes time to alter topic via cli in docker container
419+ fun () -> test_partition_missing_in_metadata_response (2 ) end }.
420+
421+ % % The partition in the middle is temporarily missing from metadata response.
422+ % % The producer should retry to recover
423+ mid_partition_missing_in_metadata_response_test_ () ->
424+ {timeout , 30 , % % it takes time to alter topic via cli in docker container
425+ fun () -> test_partition_missing_in_metadata_response (1 ) end }.
426+
427+ test_partition_missing_in_metadata_response (ThePartition ) ->
428+ ClientId = <<" test-temporarily-malformed-metadata-response" >>,
429+ % % ensure the topic does not exist
430+ Topic = <<" test-topic-5" >>,
431+ delete_topic (Topic ),
432+ Partitions = 3 ,
433+ create_topic (Topic , Partitions ),
434+ io :format (user , " created topic ~s with ~p partitions\n " , [Topic , Partitions ]),
435+ _ = application :stop (wolff ), % % ensure stopped
436+ {ok , _ } = application :ensure_all_started (wolff ),
437+ % % always refresh metadata
438+ ClientCfg = #{min_metadata_refresh_interval => 0 },
439+ {ok , ClientPid } = wolff :ensure_supervised_client (ClientId , ? HOSTS , ClientCfg ),
440+ {ok , Connections } = get_leader_connections (ClientPid , Topic ),
441+ ? assertEqual (Partitions , length (Connections )),
442+ % % always send it to the partition which is going to be missing
443+ Partitioner = fun (_ , _ ) -> ThePartition end ,
444+ ProducerCfg = #{required_acks => all_isr ,
445+ partitioner => Partitioner ,
446+ reconnect_delay_ms => 0 ,
447+ % % 0 means no aut-refresh
448+ partition_count_refresh_interval_seconds => 0
449+ },
450+ {ok , Producers } = wolff :ensure_supervised_producers (ClientId , Topic , ProducerCfg ),
451+ ? assertEqual (Partitions , wolff_producers :get_partition_cnt (ClientId , ? NO_GROUP , Topic )),
452+ Msg = #{key => ? KEY , value => <<" value" >>},
453+ ? assertMatch ({ThePartition , _ }, wolff :send_sync (Producers , [Msg ], 3000 )),
454+ % % mock a bad
455+ % % Kill partition leader connection
456+ Pid = get_partition_leader_connection (ClientPid , Topic , ThePartition ),
457+ exit (Pid , kill ),
458+ % % mock kafka_protcol to return metadata with ThePartition missing
459+ meck :new (kpro , [passthrough , no_history ]),
460+ meck :expect (kpro , request_sync ,
461+ fun (Connection , Req , Timeout ) ->
462+ {ok , Rsp } = meck :passthrough ([Connection , Req , Timeout ]),
463+ case Rsp of
464+ # kpro_rsp {msg = #{topics := [#{partitions := PM0 } = TopicMeta ]} = Meta } ->
465+ PM = lists :filter (fun (#{partition_index := P }) -> P =/= ThePartition end , PM0 ),
466+ NewRsp = Rsp # kpro_rsp {msg = Meta #{topics := [TopicMeta #{partitions := PM }]}},
467+ {ok , NewRsp };
468+ _ ->
469+ {ok , Rsp }
470+ end
471+ end ),
472+ ? assertNot (is_pid (get_partition_leader_connection (ClientPid , Topic , ThePartition ))),
473+ % % the request will be buffered, but the call times out
474+ Msg2 = #{key => ? KEY , value => <<" v" >>},
475+ ? assertError (timeout , wolff :send_sync (Producers , [Msg2 ], 100 )),
476+ % % Wait for auto recover
477+ Tester = self (),
478+ meck :expect (kpro , send ,
479+ fun (Conn , Req ) ->
480+ Tester ! {sent , Conn },
481+ meck :passthrough ([Conn , Req ])
482+ end ),
483+ % % Now remove the injected error (malformed metadata response)
484+ % % wolff_producer should now be able to recover
485+ meck :expect (kpro , request_sync ,
486+ fun (Connection , Req , Timeout ) ->
487+ meck :passthrough ([Connection , Req , Timeout ])
488+ end ),
489+ receive
490+ {sent , NewConn } ->
491+ ? assertEqual (NewConn , get_partition_leader_connection (ClientPid , Topic , ThePartition ))
492+ after
493+ 2000 ->
494+ % % wolff_producer retry delay is 0, but it randomize with extra 0-1000ms
495+ error (timeout )
496+ end ,
497+ meck :unload (kpro ),
498+ ok = fetch_and_match (ClientPid , Topic , ThePartition , 0 , [Msg , Msg2 ]),
499+ % % cleanup
500+ ok = wolff :stop_and_delete_supervised_producers (Producers ),
501+ ? assertEqual ([], supervisor :which_children (wolff_producers_sup )),
502+ ok = wolff :stop_and_delete_supervised_client (ClientId ),
503+ ? assertEqual ([], supervisor :which_children (wolff_client_sup )),
504+ ok = application :stop (wolff ),
505+ ok .
506+
507+ get_partition_leader_connection (Client , Topic , Partition ) ->
508+ ok = wolff_client :recv_leader_connection (Client , ? NO_GROUP , Topic , Partition , self (), all_partitions ),
509+ receive
510+ {leader_connection , Pid } ->
511+ Pid
512+ after
513+ 20000 ->
514+ error (timeout )
515+ end .
516+
517+ topic_recreate_test_ () ->
416518 {timeout , 30 , % % it takes time to alter topic via cli in docker container
417519 fun test_topic_recreate /0 }.
418520
0 commit comments