34
34
convert /3 ,
35
35
protocol_state /1 ,
36
36
prepare /2 ,
37
- record_death /3 ,
37
+ record_death /4 ,
38
38
is_death_cycle /2 ,
39
- last_death /1 ,
40
39
death_queue_names /1
41
40
]).
42
41
@@ -356,89 +355,103 @@ protocol_state(BasicMsg) ->
356
355
mc_compat :protocol_state (BasicMsg ).
357
356
358
357
-spec record_death (rabbit_dead_letter :reason (),
359
- SourceQueue :: rabbit_misc :resource_name (),
360
- state ()) -> state ().
358
+ rabbit_misc :resource_name (),
359
+ state (),
360
+ environment ()) -> state ().
361
361
record_death (Reason , SourceQueue ,
362
- #? MODULE {protocol = _Mod ,
363
- data = _Data ,
364
- annotations = Anns0 } = State )
365
- when is_atom ( Reason ) andalso is_binary (SourceQueue ) ->
362
+ #? MODULE {annotations = Anns0 } = State ,
363
+ Env )
364
+ when is_atom ( Reason ) andalso
365
+ is_binary (SourceQueue ) ->
366
366
Key = {SourceQueue , Reason },
367
367
#{? ANN_EXCHANGE := Exchange ,
368
368
? ANN_ROUTING_KEYS := RoutingKeys } = Anns0 ,
369
369
Timestamp = os :system_time (millisecond ),
370
370
Ttl = maps :get (ttl , Anns0 , undefined ),
371
-
372
- ReasonBin = atom_to_binary (Reason ),
373
- DeathAnns = rabbit_misc :maps_put_truthy (ttl , Ttl , #{first_time => Timestamp ,
374
- last_time => Timestamp }),
375
- case maps :get (deaths , Anns0 , undefined ) of
376
- undefined ->
377
- Ds = # deaths {last = Key ,
378
- first = Key ,
379
- records = #{Key => # death {count = 1 ,
380
- exchange = Exchange ,
381
- routing_keys = RoutingKeys ,
382
- anns = DeathAnns }}},
383
- Anns = Anns0 #{<<" x-first-death-reason" >> => ReasonBin ,
371
+ DeathAnns = rabbit_misc :maps_put_truthy (
372
+ ttl , Ttl , #{first_time => Timestamp ,
373
+ last_time => Timestamp }),
374
+ NewDeath = # death {exchange = Exchange ,
375
+ routing_keys = RoutingKeys ,
376
+ count = 1 ,
377
+ anns = DeathAnns },
378
+ Anns = case Anns0 of
379
+ #{deaths := Deaths0 } ->
380
+ Deaths = case Deaths0 of
381
+ # deaths {records = Rs0 } ->
382
+ Rs = maps :update_with (
383
+ Key ,
384
+ fun (Death ) ->
385
+ update_death (Death , Timestamp )
386
+ end ,
387
+ NewDeath ,
388
+ Rs0 ),
389
+ Deaths0 # deaths {last = Key ,
390
+ records = Rs };
391
+ _ ->
392
+ % % Deaths are ordered by recency
393
+ case lists :keytake (Key , 1 , Deaths0 ) of
394
+ {value , {Key , D0 }, Deaths1 } ->
395
+ D = update_death (D0 , Timestamp ),
396
+ [{Key , D } | Deaths1 ];
397
+ false ->
398
+ [{Key , NewDeath } | Deaths0 ]
399
+ end
400
+ end ,
401
+ Anns0 #{<<" x-last-death-reason" >> := atom_to_binary (Reason ),
402
+ <<" x-last-death-queue" >> := SourceQueue ,
403
+ <<" x-last-death-exchange" >> := Exchange ,
404
+ deaths := Deaths };
405
+ _ ->
406
+ Deaths = case Env of
407
+ #{? FF_MC_DEATHS_V2 := false } ->
408
+ # deaths {last = Key ,
409
+ first = Key ,
410
+ records = #{Key => NewDeath }};
411
+ _ ->
412
+ [{Key , NewDeath }]
413
+ end ,
414
+ ReasonBin = atom_to_binary (Reason ),
415
+ Anns0 #{<<" x-first-death-reason" >> => ReasonBin ,
384
416
<<" x-first-death-queue" >> => SourceQueue ,
385
417
<<" x-first-death-exchange" >> => Exchange ,
386
418
<<" x-last-death-reason" >> => ReasonBin ,
387
419
<<" x-last-death-queue" >> => SourceQueue ,
388
- <<" x-last-death-exchange" >> => Exchange
389
- },
390
-
391
- State #? MODULE {annotations = Anns #{deaths => Ds }};
392
- # deaths {records = Rs } = Ds0 ->
393
- Death = # death {count = C ,
394
- anns = DA } = maps :get (Key , Rs ,
395
- # death {exchange = Exchange ,
396
- routing_keys = RoutingKeys ,
397
- anns = DeathAnns }),
398
- Ds = Ds0 # deaths {last = Key ,
399
- records = Rs #{Key =>
400
- Death # death {count = C + 1 ,
401
- anns = DA #{last_time => Timestamp }}}},
402
- Anns = Anns0 #{deaths => Ds ,
403
- <<" x-last-death-reason" >> => ReasonBin ,
404
- <<" x-last-death-queue" >> => SourceQueue ,
405
- <<" x-last-death-exchange" >> => Exchange },
406
- State #? MODULE {annotations = Anns }
407
- end ;
408
- record_death (Reason , SourceQueue , BasicMsg ) ->
409
- mc_compat :record_death (Reason , SourceQueue , BasicMsg ).
410
-
420
+ <<" x-last-death-exchange" >> => Exchange ,
421
+ deaths => Deaths }
422
+ end ,
423
+ State #? MODULE {annotations = Anns };
424
+ record_death (Reason , SourceQueue , BasicMsg , Env ) ->
425
+ mc_compat :record_death (Reason , SourceQueue , BasicMsg , Env ).
426
+
427
+ update_death (# death {count = Count ,
428
+ anns = DeathAnns } = Death , Timestamp ) ->
429
+ Death # death {count = Count + 1 ,
430
+ anns = DeathAnns #{last_time := Timestamp }}.
411
431
412
432
-spec is_death_cycle (rabbit_misc :resource_name (), state ()) -> boolean ().
433
+ is_death_cycle (TargetQueue , #? MODULE {annotations = #{deaths := # deaths {records = Rs }}}) ->
434
+ is_cycle_v1 (TargetQueue , maps :keys (Rs ));
413
435
is_death_cycle (TargetQueue , #? MODULE {annotations = #{deaths := Deaths }}) ->
414
- is_cycle (TargetQueue , maps : keys ( Deaths # deaths . records ) );
436
+ is_cycle_v2 (TargetQueue , Deaths );
415
437
is_death_cycle (_TargetQueue , #? MODULE {}) ->
416
438
false ;
417
439
is_death_cycle (TargetQueue , BasicMsg ) ->
418
440
mc_compat :is_death_cycle (TargetQueue , BasicMsg ).
419
441
442
+ % % Returns death queue names ordered by recency.
420
443
-spec death_queue_names (state ()) -> [rabbit_misc :resource_name ()].
421
- death_queue_names (#? MODULE {annotations = Anns }) ->
422
- case maps :get (deaths , Anns , undefined ) of
423
- undefined ->
424
- [];
425
- # deaths {records = Records } ->
426
- proplists :get_keys (maps :keys (Records ))
427
- end ;
444
+ death_queue_names (#? MODULE {annotations = #{deaths := # deaths {records = Rs }}}) ->
445
+ proplists :get_keys (maps :keys (Rs ));
446
+ death_queue_names (#? MODULE {annotations = #{deaths := Deaths }}) ->
447
+ lists :map (fun ({{Queue , _Reason }, _Death }) ->
448
+ Queue
449
+ end , Deaths );
450
+ death_queue_names (#? MODULE {}) ->
451
+ [];
428
452
death_queue_names (BasicMsg ) ->
429
453
mc_compat :death_queue_names (BasicMsg ).
430
454
431
- -spec last_death (state ()) ->
432
- undefined | {death_key (), # death {}}.
433
- last_death (#? MODULE {annotations = Anns })
434
- when not is_map_key (deaths , Anns ) ->
435
- undefined ;
436
- last_death (#? MODULE {annotations = #{deaths := # deaths {last = Last ,
437
- records = Rs }}}) ->
438
- {Last , maps :get (Last , Rs )};
439
- last_death (BasicMsg ) ->
440
- mc_compat :last_death (BasicMsg ).
441
-
442
455
-spec prepare (read | store , state ()) -> state ().
443
456
prepare (For , #? MODULE {protocol = Proto ,
444
457
data = Data } = State ) ->
@@ -448,24 +461,38 @@ prepare(For, State) ->
448
461
449
462
% % INTERNAL
450
463
451
- % % if there is a death with a source queue that is the same as the target
464
+ is_cycle_v2 (TargetQueue , Deaths ) ->
465
+ case lists :splitwith (fun ({{SourceQueue , _Reason }, # death {}}) ->
466
+ SourceQueue =/= TargetQueue
467
+ end , Deaths ) of
468
+ {_ , []} ->
469
+ false ;
470
+ {L , [H | _ ]} ->
471
+ % % There is a cycle, but we only want to drop the message
472
+ % % if the cycle is "fully automatic", i.e. without a client
473
+ % % expliclity rejecting the message somewhere in the cycle.
474
+ lists :all (fun ({{_SourceQueue , Reason }, _Death }) ->
475
+ Reason =/= rejected
476
+ end , [H | L ])
477
+ end .
478
+
479
+ % % The desired v1 behaviour is the following:
480
+ % % "If there is a death with a source queue that is the same as the target
452
481
% % queue name and there are no newer deaths with the 'rejected' reason then
453
- % % consider this a cycle
454
- is_cycle (_Queue , []) ->
482
+ % % consider this a cycle."
483
+ % % However, the correct death order cannot be reliably determined in v1.
484
+ % % deaths_v2 fixes this bug.
485
+ is_cycle_v1 (_Queue , []) ->
455
486
false ;
456
- is_cycle (_Queue , [{_Q , rejected } | _ ]) ->
487
+ is_cycle_v1 (_Queue , [{_Q , rejected } | _ ]) ->
457
488
% % any rejection breaks the cycle
458
489
false ;
459
- is_cycle (Queue , [{Queue , Reason } | _ ])
490
+ is_cycle_v1 (Queue , [{Queue , Reason } | _ ])
460
491
when Reason =/= rejected ->
461
492
true ;
462
- is_cycle (Queue , [_ | Rem ]) ->
463
- is_cycle (Queue , Rem ).
493
+ is_cycle_v1 (Queue , [_ | Rem ]) ->
494
+ is_cycle_v1 (Queue , Rem ).
464
495
465
496
set_received_at_timestamp (Anns ) ->
466
497
Millis = os :system_time (millisecond ),
467
498
Anns #{? ANN_RECEIVED_AT_TIMESTAMP => Millis }.
468
-
469
- -ifdef (TEST ).
470
- -include_lib (" eunit/include/eunit.hrl" ).
471
- -endif .
0 commit comments