Skip to content

Commit d1d7d7b

Browse files
committed
Optionally notify client app with AMQP 1.0 performative
This commit notifies the client app with the AMQP performative if connection config `notify_with_performative` is set to `true`. This allows the client app to learn about all fields including properties and capabilities returned by the AMQP server.
1 parent 3d668fd commit d1d7d7b

File tree

7 files changed

+158
-48
lines changed

7 files changed

+158
-48
lines changed

deps/amqp10_client/src/amqp10_client.erl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ begin_session_sync(Connection, Timeout) when is_pid(Connection) ->
144144
receive
145145
{amqp10_event, {session, Session, begun}} ->
146146
{ok, Session};
147+
{amqp10_event, {session, Session, {begun, #'v1_0.begin'{}}}} ->
148+
{ok, Session};
147149
{amqp10_event, {session, Session, {ended, Err}}} ->
148150
{error, Err}
149151
after Timeout -> session_timeout
@@ -186,6 +188,8 @@ attach_sender_link_sync(Session, Name, Target, SettleMode, Durability) ->
186188
receive
187189
{amqp10_event, {link, Ref, attached}} ->
188190
{ok, Ref};
191+
{amqp10_event, {link, Ref, {attached, #'v1_0.attach'{}}}} ->
192+
{ok, Ref};
189193
{amqp10_event, {link, Ref, {detached, Err}}} ->
190194
{error, Err}
191195
after ?TIMEOUT -> link_timeout

deps/amqp10_client/src/amqp10_client_connection.erl

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
notify => pid() | none, % the pid to send connection events to
6464
notify_when_opened => pid() | none,
6565
notify_when_closed => pid() | none,
66+
notify_with_performative => boolean(),
6667
%% incoming maximum frame size set by our client application
6768
max_frame_size => pos_integer(), % TODO: constrain to large than 512
6869
%% outgoing maximum frame size set by AMQP peer in OPEN performative
@@ -253,7 +254,7 @@ hdr_sent({call, From}, begin_session,
253254
{keep_state, State1}.
254255

255256
open_sent(_EvtType, #'v1_0.open'{max_frame_size = MaybeMaxFrameSize,
256-
idle_time_out = Timeout},
257+
idle_time_out = Timeout} = Open,
257258
#state{pending_session_reqs = PendingSessionReqs,
258259
config = Config} = State0) ->
259260
State = case Timeout of
@@ -278,7 +279,7 @@ open_sent(_EvtType, #'v1_0.open'{max_frame_size = MaybeMaxFrameSize,
278279
_ = gen_statem:reply(From, Ret),
279280
S2
280281
end, State1, PendingSessionReqs),
281-
ok = notify_opened(Config),
282+
ok = notify_opened(Config, Open),
282283
{next_state, opened, State2#state{pending_session_reqs = []}};
283284
open_sent({call, From}, begin_session,
284285
#state{pending_session_reqs = PendingSessionReqs} = State) ->
@@ -292,19 +293,18 @@ opened(_EvtType, heartbeat, State = #state{idle_time_out = T}) ->
292293
ok = send_heartbeat(State),
293294
{ok, Tmr} = start_heartbeat_timer(T),
294295
{keep_state, State#state{heartbeat_timer = Tmr}};
295-
opened(_EvtType, {close, Reason}, State = #state{config = Config}) ->
296+
opened(_EvtType, {close, Reason}, State) ->
296297
%% We send the first close frame and wait for the reply.
297298
%% TODO: stop all sessions writing
298299
%% We could still accept incoming frames (See: 2.4.6)
299-
ok = notify_closed(Config, Reason),
300300
case send_close(State, Reason) of
301301
ok -> {next_state, close_sent, State};
302302
{error, closed} -> {stop, normal, State};
303303
Error -> {stop, Error, State}
304304
end;
305-
opened(_EvtType, #'v1_0.close'{error = Error}, State = #state{config = Config}) ->
305+
opened(_EvtType, #'v1_0.close'{} = Close, State = #state{config = Config}) ->
306306
%% We receive the first close frame, reply and terminate.
307-
ok = notify_closed(Config, translate_err(Error)),
307+
ok = notify_closed(Config, Close),
308308
_ = send_close(State, none),
309309
{stop, normal, State};
310310
opened({call, From}, begin_session, State) ->
@@ -329,7 +329,8 @@ close_sent(_EvtType, {'DOWN', _Ref, process, ReaderPid, _},
329329
#state{reader = ReaderPid} = State) ->
330330
%% if the reader exits we probably wont receive a close frame
331331
{stop, normal, State};
332-
close_sent(_EvtType, #'v1_0.close'{}, State) ->
332+
close_sent(_EvtType, #'v1_0.close'{} = Close, State = #state{config = Config}) ->
333+
ok = notify_closed(Config, Close),
333334
%% TODO: we should probably set up a timer before this to ensure
334335
%% we close down event if no reply is received
335336
{stop, normal, State}.
@@ -489,25 +490,45 @@ socket_shutdown({tcp, Socket}, How) ->
489490
socket_shutdown({ssl, Socket}, How) ->
490491
ssl:shutdown(Socket, How).
491492

492-
notify_opened(#{notify_when_opened := none}) ->
493-
ok;
494-
notify_opened(#{notify_when_opened := Pid}) when is_pid(Pid) ->
495-
Pid ! amqp10_event(opened),
493+
notify_opened(#{notify_when_opened := none}, _) ->
496494
ok;
497-
notify_opened(#{notify := Pid}) when is_pid(Pid) ->
498-
Pid ! amqp10_event(opened),
499-
ok;
500-
notify_opened(_) ->
495+
notify_opened(#{notify_when_opened := Pid} = Config, Perf)
496+
when is_pid(Pid) ->
497+
notify_opened0(Config, Pid, Perf);
498+
notify_opened(#{notify := Pid} = Config, Perf)
499+
when is_pid(Pid) ->
500+
notify_opened0(Config, Pid, Perf);
501+
notify_opened(_, _) ->
502+
ok.
503+
504+
notify_opened0(Config, Pid, Perf) ->
505+
Evt = case Config of
506+
#{notify_with_performative := true} ->
507+
{opened, Perf};
508+
_ ->
509+
opened
510+
end,
511+
Pid ! amqp10_event(Evt),
501512
ok.
502513

503514
notify_closed(#{notify_when_closed := none}, _Reason) ->
504515
ok;
505516
notify_closed(#{notify := none}, _Reason) ->
506517
ok;
507-
notify_closed(#{notify_when_closed := Pid}, Reason) when is_pid(Pid) ->
508-
Pid ! amqp10_event({closed, Reason}),
518+
notify_closed(#{notify_when_closed := Pid} = Config, Reason)
519+
when is_pid(Pid) ->
520+
notify_closed0(Config, Pid, Reason);
521+
notify_closed(#{notify := Pid} = Config, Reason)
522+
when is_pid(Pid) ->
523+
notify_closed0(Config, Pid, Reason).
524+
525+
notify_closed0(#{notify_with_performative := true}, Pid, Perf = #'v1_0.close'{}) ->
526+
Pid ! amqp10_event({closed, Perf}),
527+
ok;
528+
notify_closed0(_, Pid, #'v1_0.close'{error = Error}) ->
529+
Pid ! amqp10_event({closed, translate_err(Error)}),
509530
ok;
510-
notify_closed(#{notify := Pid}, Reason) when is_pid(Pid) ->
531+
notify_closed0(_, Pid, Reason) ->
511532
Pid ! amqp10_event({closed, Reason}),
512533
ok.
513534

deps/amqp10_client/src/amqp10_client_session.erl

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ unmapped({call, From}, {attach, Attach},
254254
begin_sent(cast, #'v1_0.begin'{remote_channel = {ushort, RemoteChannel},
255255
next_outgoing_id = {uint, NOI},
256256
incoming_window = {uint, InWindow},
257-
outgoing_window = {uint, OutWindow}},
257+
outgoing_window = {uint, OutWindow}} = Begin,
258258
#state{early_attach_requests = EARs} = State) ->
259259

260260
State1 = State#state{remote_channel = RemoteChannel},
@@ -264,7 +264,7 @@ begin_sent(cast, #'v1_0.begin'{remote_channel = {ushort, RemoteChannel},
264264
S2
265265
end, State1, EARs),
266266

267-
ok = notify_session_begun(State2),
267+
ok = notify_session_begun(Begin, State2),
268268

269269
{next_state, mapped, State2#state{early_attach_requests = [],
270270
next_incoming_id = NOI,
@@ -291,18 +291,17 @@ mapped(cast, {flow_session, Flow0 = #'v1_0.flow'{incoming_window = {uint, Incomi
291291
outgoing_window = ?UINT_OUTGOING_WINDOW},
292292
ok = send(Flow, State),
293293
{keep_state, State#state{incoming_window = IncomingWindow}};
294-
mapped(cast, #'v1_0.end'{error = Err}, State) ->
294+
mapped(cast, #'v1_0.end'{} = End, State) ->
295295
%% We receive the first end frame, reply and terminate.
296296
_ = send_end(State),
297297
% TODO: send notifications for links?
298-
Reason = reason(Err),
299-
ok = notify_session_ended(State, Reason),
298+
ok = notify_session_ended(End, State),
300299
{stop, normal, State};
301300
mapped(cast, #'v1_0.attach'{name = {utf8, Name},
302301
initial_delivery_count = IDC,
303302
handle = {uint, InHandle},
304303
role = PeerRoleBool,
305-
max_message_size = MaybeMaxMessageSize},
304+
max_message_size = MaybeMaxMessageSize} = Attach,
306305
#state{links = Links, link_index = LinkIndex,
307306
link_handle_index = LHI} = State0) ->
308307

@@ -311,7 +310,7 @@ mapped(cast, #'v1_0.attach'{name = {utf8, Name},
311310
LinkIndexKey = {OurRole, Name},
312311
#{LinkIndexKey := OutHandle} = LinkIndex,
313312
#{OutHandle := Link0} = Links,
314-
ok = notify_link_attached(Link0),
313+
ok = notify_link_attached(Link0, Attach, State0),
315314

316315
{DeliveryCount, MaxMessageSize} =
317316
case Link0 of
@@ -334,13 +333,11 @@ mapped(cast, #'v1_0.attach'{name = {utf8, Name},
334333
link_index = maps:remove(LinkIndexKey, LinkIndex),
335334
link_handle_index = LHI#{InHandle => OutHandle}},
336335
{keep_state, State};
337-
mapped(cast, #'v1_0.detach'{handle = {uint, InHandle},
338-
error = Err},
336+
mapped(cast, #'v1_0.detach'{handle = {uint, InHandle}} = Detach,
339337
#state{links = Links, link_handle_index = LHI} = State0) ->
340338
with_link(InHandle, State0,
341339
fun (#link{output_handle = OutHandle} = Link, State) ->
342-
Reason = reason(Err),
343-
ok = notify_link_detached(Link, Reason),
340+
ok = notify_link_detached(Link, Detach, State),
344341
{keep_state,
345342
State#state{links = maps:remove(OutHandle, Links),
346343
link_handle_index = maps:remove(InHandle, LHI)}}
@@ -552,9 +549,8 @@ mapped(_EvtType, Msg, _State) ->
552549
[Msg, 10]),
553550
keep_state_and_data.
554551

555-
end_sent(_EvtType, #'v1_0.end'{error = Err}, State) ->
556-
Reason = reason(Err),
557-
ok = notify_session_ended(State, Reason),
552+
end_sent(_EvtType, #'v1_0.end'{} = End, State) ->
553+
ok = notify_session_ended(End, State),
558554
{stop, normal, State};
559555
end_sent(_EvtType, _Frame, _State) ->
560556
% just drop frames here
@@ -989,22 +985,51 @@ maybe_notify_link_credit(#link{role = sender,
989985
maybe_notify_link_credit(_Old, _New) ->
990986
ok.
991987

992-
notify_link_attached(Link) ->
993-
notify_link(Link, attached).
994-
995-
notify_link_detached(Link, Reason) ->
988+
notify_link_attached(Link, Perf, #state{connection_config = Cfg}) ->
989+
What = case Cfg of
990+
#{notify_with_performative := true} ->
991+
{attached, Perf};
992+
_ ->
993+
attached
994+
end,
995+
notify_link(Link, What).
996+
997+
notify_link_detached(Link,
998+
Perf = #'v1_0.detach'{error = Err},
999+
#state{connection_config = Cfg}) ->
1000+
Reason = case Cfg of
1001+
#{notify_with_performative := true} ->
1002+
Perf;
1003+
_ ->
1004+
reason(Err)
1005+
end,
9961006
notify_link(Link, {detached, Reason}).
9971007

9981008
notify_link(#link{notify = Pid, ref = Ref}, What) ->
9991009
Evt = {amqp10_event, {link, Ref, What}},
10001010
Pid ! Evt,
10011011
ok.
10021012

1003-
notify_session_begun(#state{notify = Pid}) ->
1004-
Pid ! amqp10_session_event(begun),
1013+
notify_session_begun(Perf, #state{notify = Pid,
1014+
connection_config = Cfg}) ->
1015+
Evt = case Cfg of
1016+
#{notify_with_performative := true} ->
1017+
{begun, Perf};
1018+
_ ->
1019+
begun
1020+
end,
1021+
Pid ! amqp10_session_event(Evt),
10051022
ok.
10061023

1007-
notify_session_ended(#state{notify = Pid}, Reason) ->
1024+
notify_session_ended(Perf = #'v1_0.end'{error = Err},
1025+
#state{notify = Pid,
1026+
connection_config = Cfg}) ->
1027+
Reason = case Cfg of
1028+
#{notify_with_performative := true} ->
1029+
Perf;
1030+
_ ->
1031+
reason(Err)
1032+
end,
10081033
Pid ! amqp10_session_event({ended, Reason}),
10091034
ok.
10101035

deps/amqp10_client/test/system_SUITE.erl

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ all() ->
3030

3131
groups() ->
3232
[
33-
{rabbitmq, [], shared()},
33+
{rabbitmq, [], shared() ++ [notify_with_performative]},
3434
{activemq, [], shared()},
3535
{rabbitmq_strict, [], [
3636
basic_roundtrip_tls,
@@ -458,6 +458,52 @@ transfer_id_vs_delivery_id(Config) ->
458458
?assertEqual(serial_number:add(amqp10_msg:delivery_id(RcvMsg1), 1),
459459
amqp10_msg:delivery_id(RcvMsg2)).
460460

461+
notify_with_performative(Config) ->
462+
Hostname = ?config(rmq_hostname, Config),
463+
Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp),
464+
465+
OpenConf = #{?FUNCTION_NAME => true,
466+
address => Hostname,
467+
port => Port,
468+
sasl => anon},
469+
470+
{ok, Connection} = amqp10_client:open_connection(OpenConf),
471+
receive {amqp10_event, {connection, Connection, {opened, #'v1_0.open'{}}}} -> ok
472+
after 5000 -> ct:fail({missing_event, ?LINE})
473+
end,
474+
475+
{ok, Session1} = amqp10_client:begin_session(Connection),
476+
receive {amqp10_event, {session, Session1, {begun, #'v1_0.begin'{}}}} -> ok
477+
after 5000 -> ct:fail({missing_event, ?LINE})
478+
end,
479+
480+
{ok, Sender1} = amqp10_client:attach_sender_link(Session1, <<"sender 1">>, <<"/exchanges/amq.fanout">>),
481+
receive {amqp10_event, {link, Sender1, {attached, #'v1_0.attach'{}}}} -> ok
482+
after 5000 -> ct:fail({missing_event, ?LINE})
483+
end,
484+
485+
ok = amqp10_client:detach_link(Sender1),
486+
receive {amqp10_event, {link, Sender1, {detached, #'v1_0.detach'{}}}} -> ok
487+
after 5000 -> ct:fail({missing_event, ?LINE})
488+
end,
489+
490+
ok = amqp10_client:end_session(Session1),
491+
receive {amqp10_event, {session, Session1, {ended, #'v1_0.end'{}}}} -> ok
492+
after 5000 -> ct:fail({missing_event, ?LINE})
493+
end,
494+
495+
%% Test that the amqp10_client:*_sync functions work.
496+
{ok, Session2} = amqp10_client:begin_session_sync(Connection),
497+
{ok, Sender2} = amqp10_client:attach_sender_link_sync(Session2, <<"sender 2">>, <<"/exchanges/amq.fanout">>),
498+
ok = amqp10_client:detach_link(Sender2),
499+
ok = amqp10_client:end_session(Session2),
500+
flush(),
501+
502+
ok = amqp10_client:close_connection(Connection),
503+
receive {amqp10_event, {connection, Connection, {closed, #'v1_0.close'{}}}} -> ok
504+
after 5000 -> ct:fail({missing_event, ?LINE})
505+
end.
506+
461507
% a message is sent before the link attach is guaranteed to
462508
% have completed and link credit granted
463509
% also queue a link detached immediately after transfer
@@ -832,8 +878,10 @@ incoming_heartbeat(Config) ->
832878
Hostname = ?config(mock_host, Config),
833879
Port = ?config(mock_port, Config),
834880
OpenStep = fun({0 = Ch, #'v1_0.open'{}, _Pay}) ->
835-
{Ch, [#'v1_0.open'{container_id = {utf8, <<"mock">>},
836-
idle_time_out = {uint, 0}}]}
881+
{Ch, [#'v1_0.open'{
882+
container_id = {utf8, <<"mock">>},
883+
%% The server doesn't expect any heartbeats from us (client).
884+
idle_time_out = {uint, 0}}]}
837885
end,
838886

839887
CloseStep = fun({0 = Ch, #'v1_0.close'{error = _TODO}, _Pay}) ->
@@ -847,20 +895,25 @@ incoming_heartbeat(Config) ->
847895
MockRef = monitor(process, MockPid),
848896
ok = mock_server:set_steps(Mock, Steps),
849897
CConf = #{address => Hostname, port => Port, sasl => ?config(sasl, Config),
850-
idle_time_out => 1000, notify => self()},
898+
%% If the server does not send any traffic to us (client), we will expect
899+
%% our client to close the connection after 1 second because
900+
%% "the value in idle-time-out SHOULD be half the peer's actual timeout threshold."
901+
idle_time_out => 500,
902+
notify => self()},
851903
{ok, Connection} = amqp10_client:open_connection(CConf),
904+
%% We expect our client to initiate closing the connection
905+
%% and the server to reply with a close frame.
852906
receive
853907
{amqp10_event,
854908
{connection, Connection0,
855-
{closed, {resource_limit_exceeded, <<"remote idle-time-out">>}}}}
909+
{closed, _}}}
856910
when Connection0 =:= Connection ->
857911
ok
858912
after 5000 ->
859913
exit(incoming_heartbeat_assert)
860914
end,
861915
demonitor(MockRef).
862916

863-
864917
%%% HELPERS
865918
%%%
866919

deps/rabbit/test/amqp_client_SUITE.erl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4611,9 +4611,7 @@ idle_time_out_on_client(Config) ->
46114611
receive
46124612
{amqp10_event,
46134613
{connection, Connection,
4614-
{closed,
4615-
{resource_limit_exceeded,
4616-
<<"remote idle-time-out">>}}}} -> ok
4614+
{closed, _}}} -> ok
46174615
after 5000 ->
46184616
ct:fail({missing_event, ?LINE})
46194617
end,

deps/rabbit/test/amqp_system_SUITE.erl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ groups() ->
5252
%% Testsuite setup/teardown.
5353
%% -------------------------------------------------------------------
5454

55+
suite() ->
56+
[
57+
{timetrap, {minutes, 3}}
58+
].
59+
5560
init_per_suite(Config) ->
5661
rabbit_ct_helpers:log_environment(),
5762
Config.

0 commit comments

Comments
 (0)