2
2
% % License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
% % file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
% %
5
- % % Copyright (c) 2007-2021 VMware, Inc. or its affiliates. All rights reserved.
5
+ % % Copyright (c) 2007-2022 VMware, Inc. or its affiliates. All rights reserved.
6
6
% %
7
7
8
8
-module (rabbit_stream_coordinator ).
23
23
-export ([recover /0 ,
24
24
add_replica /2 ,
25
25
delete_replica /2 ,
26
- register_listener /1 ]).
26
+ register_listener /1 ,
27
+ register_local_member_listener /1 ]).
27
28
28
29
-export ([new_stream /2 ,
29
30
delete_stream /2 ]).
39
40
-export ([log_overview /1 ]).
40
41
-export ([replay /1 ]).
41
42
43
+ % % for testing and debugging
44
+ -export ([eval_listeners /3 ,
45
+ state /0 ]).
46
+
42
47
-rabbit_boot_step ({? MODULE ,
43
48
[{description , " Restart stream coordinator" },
44
49
{mfa , {? MODULE , recover , []}},
68
73
{delete_replica , stream_id (), #{node := node ()}} |
69
74
{policy_changed , stream_id (), #{queue := amqqueue :amqqueue ()}} |
70
75
{register_listener , #{pid := pid (),
76
+ node := node (),
71
77
stream_id := stream_id (),
72
- queue_ref := queue_ref () }} |
78
+ type := leader | local_member }} |
73
79
{action_failed , stream_id (), #{index := ra :index (),
74
80
node := node (),
75
81
epoch := osiris :epoch (),
@@ -165,6 +171,15 @@ policy_changed(Q) when ?is_amqqueue(Q) ->
165
171
StreamId = maps :get (name , amqqueue :get_type_state (Q )),
166
172
process_command ({policy_changed , StreamId , #{queue => Q }}).
167
173
174
+ % % for debugging
175
+ state () ->
176
+ case ra :local_query ({? MODULE , node ()}, fun (State ) -> State end ) of
177
+ {ok , {_ , Res }, _ } ->
178
+ Res ;
179
+ Any ->
180
+ Any
181
+ end .
182
+
168
183
local_pid (StreamId ) when is_list (StreamId ) ->
169
184
MFA = {? MODULE , query_local_pid , [StreamId , node ()]},
170
185
case ra :local_query ({? MODULE , node ()}, MFA ) of
@@ -248,6 +263,16 @@ register_listener(Q) when ?is_amqqueue(Q)->
248
263
#{pid => self (),
249
264
stream_id => StreamId }}).
250
265
266
+ -spec register_local_member_listener (amqqueue :amqqueue ()) ->
267
+ {error , term ()} | {ok , ok | stream_not_found , atom () | {atom (), atom ()}}.
268
+ register_local_member_listener (Q ) when ? is_amqqueue (Q ) ->
269
+ #{name := StreamId } = amqqueue :get_type_state (Q ),
270
+ process_command ({register_listener ,
271
+ #{pid => self (),
272
+ node => node (self ()),
273
+ stream_id => StreamId ,
274
+ type => local_member }}).
275
+
251
276
process_command (Cmd ) ->
252
277
Servers = ensure_coordinator_started (),
253
278
process_command (Servers , Cmd ).
@@ -313,7 +338,7 @@ all_coord_members() ->
313
338
Nodes = rabbit_mnesia :cluster_nodes (running ) -- [node ()],
314
339
[{? MODULE , Node } || Node <- [node () | Nodes ]].
315
340
316
- version () -> 1 .
341
+ version () -> 2 .
317
342
318
343
which_module (_ ) ->
319
344
? MODULE .
@@ -323,7 +348,8 @@ init(_Conf) ->
323
348
324
349
-spec apply (map (), command (), state ()) ->
325
350
{state (), term (), ra_machine :effects ()}.
326
- apply (#{index := _Idx } = Meta0 , {_CmdTag , StreamId , #{}} = Cmd ,
351
+ apply (#{index := _Idx , machine_version := MachineVersion } = Meta0 ,
352
+ {_CmdTag , StreamId , #{}} = Cmd ,
327
353
#? MODULE {streams = Streams0 ,
328
354
monitors = Monitors0 } = State0 ) ->
329
355
Stream0 = maps :get (StreamId , Streams0 , undefined ),
@@ -341,10 +367,10 @@ apply(#{index := _Idx} = Meta0, {_CmdTag, StreamId, #{}} = Cmd,
341
367
case Stream1 of
342
368
undefined ->
343
369
return (Meta , State0 #? MODULE {streams = maps :remove (StreamId , Streams0 )},
344
- Reply , inform_listeners_eol (Stream0 ));
370
+ Reply , inform_listeners_eol (MachineVersion , Stream0 ));
345
371
_ ->
346
372
{Stream2 , Effects0 } = evaluate_stream (Meta , Stream1 , []),
347
- {Stream3 , Effects1 } = eval_listeners (Stream2 , Effects0 ),
373
+ {Stream3 , Effects1 } = eval_listeners (MachineVersion , Stream2 , Effects0 ),
348
374
{Stream , Effects2 } = eval_retention (Meta , Stream3 , Effects1 ),
349
375
{Monitors , Effects } = ensure_monitors (Stream , Monitors0 , Effects2 ),
350
376
return (Meta ,
@@ -354,10 +380,10 @@ apply(#{index := _Idx} = Meta0, {_CmdTag, StreamId, #{}} = Cmd,
354
380
Reply ->
355
381
return (Meta , State0 , Reply , [])
356
382
end ;
357
- apply (Meta , {down , Pid , Reason } = Cmd ,
383
+ apply (#{ machine_version : = MachineVersion } = Meta , {down , Pid , Reason } = Cmd ,
358
384
#? MODULE {streams = Streams0 ,
359
- listeners = Listeners0 ,
360
- monitors = Monitors0 } = State ) ->
385
+ monitors = Monitors0 ,
386
+ listeners = StateListeners0 } = State ) ->
361
387
362
388
Effects0 = case Reason of
363
389
noconnection ->
@@ -366,10 +392,10 @@ apply(Meta, {down, Pid, Reason} = Cmd,
366
392
[]
367
393
end ,
368
394
case maps :take (Pid , Monitors0 ) of
369
- {{StreamId , listener }, Monitors } ->
370
- Listeners = case maps :take (StreamId , Listeners0 ) of
395
+ {{StreamId , listener }, Monitors } when MachineVersion < 2 ->
396
+ Listeners = case maps :take (StreamId , StateListeners0 ) of
371
397
error ->
372
- Listeners0 ;
398
+ StateListeners0 ;
373
399
{Pids0 , Listeners1 } ->
374
400
case maps :remove (Pid , Pids0 ) of
375
401
Pids when map_size (Pids ) == 0 ->
@@ -380,6 +406,23 @@ apply(Meta, {down, Pid, Reason} = Cmd,
380
406
end ,
381
407
return (Meta , State #? MODULE {listeners = Listeners ,
382
408
monitors = Monitors }, ok , Effects0 );
409
+ {{PidStreams , listener }, Monitors } when MachineVersion >= 2 ->
410
+ Streams = maps :fold (
411
+ fun (StreamId , _ , Acc ) ->
412
+ case Acc of
413
+ #{StreamId := Stream = # stream {listeners = Listeners0 }} ->
414
+ Listeners = maps :fold (fun ({P , _ } = K , _ , A ) when P == Pid ->
415
+ maps :remove (K , A );
416
+ (K , V , A ) ->
417
+ A #{K => V }
418
+ end , #{}, Listeners0 ),
419
+ Acc #{StreamId => Stream # stream {listeners = Listeners }};
420
+ _ ->
421
+ Acc
422
+ end
423
+ end , Streams0 , PidStreams ),
424
+ return (Meta , State #? MODULE {streams = Streams ,
425
+ monitors = Monitors }, ok , Effects0 );
383
426
{{StreamId , member }, Monitors1 } ->
384
427
case Streams0 of
385
428
#{StreamId := Stream0 } ->
@@ -398,19 +441,53 @@ apply(Meta, {down, Pid, Reason} = Cmd,
398
441
error ->
399
442
return (Meta , State , ok , Effects0 )
400
443
end ;
401
- apply (Meta , {register_listener , #{pid := Pid ,
402
- stream_id := StreamId }},
444
+ apply (#{machine_version := MachineVersion } = Meta ,
445
+ {register_listener , #{pid := Pid ,
446
+ stream_id := StreamId } = Args },
403
447
#? MODULE {streams = Streams ,
404
- monitors = Monitors0 } = State0 ) ->
405
- case Streams of
406
- #{StreamId := # stream {listeners = Listeners0 } = Stream0 } ->
448
+ monitors = Monitors0 } = State0 ) when MachineVersion =< 1 ->
449
+ Type = maps :get (type , Args , leader ),
450
+ case {Streams , Type } of
451
+ {#{StreamId := # stream {listeners = Listeners0 } = Stream0 }, leader } ->
407
452
Stream1 = Stream0 # stream {listeners = maps :put (Pid , undefined , Listeners0 )},
408
- {Stream , Effects } = eval_listeners (Stream1 , []),
453
+ {Stream , Effects } = eval_listeners (MachineVersion , Stream1 , []),
409
454
Monitors = maps :put (Pid , {StreamId , listener }, Monitors0 ),
410
455
return (Meta ,
411
456
State0 #? MODULE {streams = maps :put (StreamId , Stream , Streams ),
412
457
monitors = Monitors }, ok ,
413
458
[{monitor , process , Pid } | Effects ]);
459
+ {#{StreamId := _Stream }, local_member } ->
460
+ % % registering a local member listener does not change the state in v1
461
+ return (Meta , State0 , ok , []);
462
+ _ ->
463
+ return (Meta , State0 , stream_not_found , [])
464
+ end ;
465
+
466
+ apply (#{machine_version := MachineVersion } = Meta ,
467
+ {register_listener , #{pid := Pid ,
468
+ stream_id := StreamId } = Args },
469
+ #? MODULE {streams = Streams ,
470
+ monitors = Monitors0 } = State0 ) when MachineVersion >= 2 ->
471
+ Node = maps :get (node , Args , node (Pid )),
472
+ Type = maps :get (type , Args , leader ),
473
+
474
+ case Streams of
475
+ #{StreamId := # stream {listeners = Listeners0 } = Stream0 } ->
476
+ {LKey , LValue } =
477
+ case Type of
478
+ leader ->
479
+ {{Pid , leader }, undefined };
480
+ local_member ->
481
+ {{Pid , member }, {Node , undefined }}
482
+ end ,
483
+ Stream1 = Stream0 # stream {listeners = maps :put (LKey , LValue , Listeners0 )},
484
+ {Stream , Effects } = eval_listeners (MachineVersion , Stream1 , []),
485
+ {PidStreams , listener } = maps :get (Pid , Monitors0 , {#{}, listener }),
486
+ Monitors = maps :put (Pid , {PidStreams #{StreamId => ok }, listener }, Monitors0 ),
487
+ return (Meta ,
488
+ State0 #? MODULE {streams = maps :put (StreamId , Stream , Streams ),
489
+ monitors = Monitors }, ok ,
490
+ [{monitor , process , Pid } | Effects ]);
414
491
_ ->
415
492
return (Meta , State0 , stream_not_found , [])
416
493
end ;
@@ -438,7 +515,43 @@ apply(Meta, {nodeup, Node} = Cmd,
438
515
end , {Streams0 , Effects0 }, Streams0 ),
439
516
return (Meta , State #? MODULE {monitors = Monitors ,
440
517
streams = Streams }, ok , Effects );
441
- apply (Meta , {machine_version , _From , _To }, State ) ->
518
+ apply (Meta , {machine_version , From = 1 , To = 2 }, State = #? MODULE {streams = Streams0 ,
519
+ monitors = Monitors0 }) ->
520
+ rabbit_log :info (" Stream coordinator machine version changes from ~p to ~p , updating state." ,
521
+ [From , To ]),
522
+ % % conversion from old state to new state
523
+ % % additional operation: the stream listeners are never collected in the previous version
524
+ % % so we'll emit monitors for all listener PIDs
525
+ % % this way we'll get the DOWN event for dead listener PIDs and
526
+ % % we'll clean the stream listeners the in the DOWN event callback
527
+
528
+ % % transform the listeners of each stream and accumulate listener PIDs
529
+ {Streams1 , Listeners } =
530
+ maps :fold (fun (S , # stream {listeners = L0 } = S0 , {StreamAcc , GlobalListAcc }) ->
531
+ {L1 , GlobalListAcc1 } = maps :fold (
532
+ fun (ListPid , LeaderPid , {LAcc , GLAcc }) ->
533
+ {LAcc #{{ListPid , leader } => LeaderPid },
534
+ GLAcc #{ListPid => S }}
535
+ end , {#{}, GlobalListAcc }, L0 ),
536
+ {StreamAcc #{S => S0 # stream {listeners = L1 }}, GlobalListAcc1 }
537
+ end , {#{}, #{}}, Streams0 ),
538
+ % % accumulate monitors for the map and create the effects to emit the monitors
539
+ {ExtraMonitors , Effects } = maps :fold (fun (P , StreamId , {MAcc , EAcc }) ->
540
+ {MAcc #{P => {StreamId , listener }},
541
+ [{monitor , process , P } | EAcc ]}
542
+ end , {#{}, []}, Listeners ),
543
+ Monitors1 = maps :merge (Monitors0 , ExtraMonitors ),
544
+ Monitors2 = maps :fold (fun (P , {StreamId , listener }, Acc ) ->
545
+ Acc #{P => {#{StreamId => ok }, listener }};
546
+ (P , V , Acc ) ->
547
+ Acc #{P => V }
548
+ end , #{}, Monitors1 ),
549
+ return (Meta , State #? MODULE {streams = Streams1 ,
550
+ monitors = Monitors2 ,
551
+ listeners = undefined }, ok , Effects );
552
+ apply (Meta , {machine_version , From , To }, State ) ->
553
+ rabbit_log :info (" Stream coordinator machine version changes from ~p to ~p , no state changes required." ,
554
+ [From , To ]),
442
555
return (Meta , State , ok , []);
443
556
apply (Meta , UnkCmd , State ) ->
444
557
rabbit_log :debug (" ~s : unknown command ~W " ,
@@ -1240,21 +1353,35 @@ update_stream0(#{system_time := _Ts},
1240
1353
update_stream0 (_Meta , _Cmd , undefined ) ->
1241
1354
undefined .
1242
1355
1243
- inform_listeners_eol (# stream {target = deleted ,
1356
+ inform_listeners_eol (MachineVersion , # stream {target = deleted ,
1244
1357
listeners = Listeners ,
1245
1358
queue_ref = QRef
1246
- }) ->
1359
+ }) when MachineVersion =< 1 ->
1247
1360
lists :map (fun (Pid ) ->
1248
1361
{send_msg , Pid ,
1249
1362
{queue_event , QRef , eol },
1250
1363
cast }
1251
1364
end , maps :keys (Listeners ));
1252
- inform_listeners_eol (_ ) ->
1365
+ inform_listeners_eol (MachineVersion ,
1366
+ # stream {target = deleted ,
1367
+ listeners = Listeners ,
1368
+ queue_ref = QRef }) when MachineVersion >= 2 ->
1369
+ LPidsMap = maps :fold (fun ({P , _ }, _V , Acc ) ->
1370
+ Acc #{P => ok }
1371
+ end , #{}, Listeners ),
1372
+ lists :map (fun (Pid ) ->
1373
+ {send_msg ,
1374
+ Pid ,
1375
+ {queue_event , QRef , eol },
1376
+ cast }
1377
+ end , maps :keys (LPidsMap ));
1378
+ inform_listeners_eol (_ , _ ) ->
1253
1379
[].
1254
1380
1255
- eval_listeners (# stream {listeners = Listeners0 ,
1256
- queue_ref = QRef ,
1257
- members = Members } = Stream , Effects0 ) ->
1381
+ eval_listeners (MachineVersion , # stream {listeners = Listeners0 ,
1382
+ queue_ref = QRef ,
1383
+ members = Members } = Stream , Effects0 )
1384
+ when MachineVersion =< 1 ->
1258
1385
case find_leader (Members ) of
1259
1386
{# member {state = {running , _ , LeaderPid }}, _ } ->
1260
1387
% % a leader is running, check all listeners to see if any of them
@@ -1273,7 +1400,60 @@ eval_listeners(#stream{listeners = Listeners0,
1273
1400
{Stream # stream {listeners = Listeners }, Effects };
1274
1401
_ ->
1275
1402
{Stream , Effects0 }
1276
- end .
1403
+ end ;
1404
+ eval_listeners (MachineVersion , # stream {listeners = Listeners0 ,
1405
+ queue_ref = QRef ,
1406
+ members = Members } = Stream0 , Effects0 ) when MachineVersion >= 2 ->
1407
+ % % Iterating over stream listeners.
1408
+ % % Returning the new map of listeners and the effects (notification of changes)
1409
+ {Listeners1 , Effects1 } =
1410
+ maps :fold (fun ({P , leader }, ListLPid0 , {Lsts0 , Effs0 }) ->
1411
+ % % iterating over member to find the leader
1412
+ {ListLPid1 , Effs1 } =
1413
+ maps :fold (fun (_N , # member {state = {running , _ , LeaderPid },
1414
+ role = {writer , _ },
1415
+ target = T }, A )
1416
+ when ListLPid0 == LeaderPid , T /= deleted ->
1417
+ % % it's the leader, same PID, nothing to do
1418
+ A ;
1419
+ (_N , # member {state = {running , _ , LeaderPid },
1420
+ role = {writer , _ },
1421
+ target = T }, {_ , Efs })
1422
+ when T /= deleted ->
1423
+ % % it's the leader, not same PID, assign the new leader, add effect
1424
+ {LeaderPid , [{send_msg , P ,
1425
+ {queue_event , QRef ,
1426
+ {stream_leader_change , LeaderPid }},
1427
+ cast } | Efs ]};
1428
+ (_N , _M , Acc ) ->
1429
+ % % it's not the leader, nothing to do
1430
+ Acc
1431
+ end , {ListLPid0 , Effs0 }, Members ),
1432
+ {Lsts0 #{{P , leader } => ListLPid1 }, Effs1 };
1433
+ ({P , member }, {ListNode , ListMPid0 }, {Lsts0 , Effs0 }) ->
1434
+ % % listening to a member on a given node
1435
+ % % iterating over the members to find the member on this node
1436
+ {ListMPid1 , Effs1 } =
1437
+ maps :fold (fun (MNode , # member {state = {running , _ , MemberPid }, target = T }, Acc )
1438
+ when ListMPid0 == MemberPid , ListNode == MNode , T /= deleted ->
1439
+ % % it's the local member of this listener
1440
+ % % it has not changed, nothing to do
1441
+ Acc ;
1442
+ (MNode , # member {state = {running , _ , MemberPid }, target = T }, {_ , Efs })
1443
+ when ListNode == MNode , T /= deleted ->
1444
+ % % it's the local member of this listener
1445
+ % % the PID is not the same, updating it in the listener, add effect
1446
+ {MemberPid , [{send_msg , P ,
1447
+ {queue_event , QRef ,
1448
+ {stream_local_member_change , MemberPid }},
1449
+ cast } | Efs ]};
1450
+ (_N , _M , Acc ) ->
1451
+ % % not a replica, nothing to do
1452
+ Acc
1453
+ end , {ListMPid0 , Effs0 }, Members ),
1454
+ {Lsts0 #{{P , member } => {ListNode , ListMPid1 }}, Effs1 }
1455
+ end , {Listeners0 , Effects0 }, Listeners0 ),
1456
+ {Stream0 # stream {listeners = Listeners1 }, Effects1 }.
1277
1457
1278
1458
eval_retention (#{index := Idx } = Meta ,
1279
1459
# stream {conf = #{retention := Ret } = Conf ,
0 commit comments