20
20
attach /2 ,
21
21
detach /2 ,
22
22
transfer /3 ,
23
- flow / 4 ,
24
- disposition / 5
23
+ disposition / 5 ,
24
+ flow_link / 4
25
25
]).
26
26
27
+ % % Manual session flow control is currently only used in tests.
28
+ -export ([flow /3 ]).
29
+
27
30
% % Private API
28
31
-export ([start_link /4 ,
29
32
socket_ready /2
51
54
[add /2 ,
52
55
diff /2 ]).
53
56
54
- -define (MAX_SESSION_WINDOW_SIZE , 65535 ).
57
+ % % By default, we want to keep the server's remote-incoming-window large at all times.
58
+ -define (DEFAULT_MAX_INCOMING_WINDOW , 100_000 ).
55
59
-define (UINT_OUTGOING_WINDOW , {uint , ? UINT_MAX }).
56
60
-define (INITIAL_OUTGOING_DELIVERY_ID , ? UINT_MAX ).
57
61
% % "The next-outgoing-id MAY be initialized to an arbitrary value" [2.5.6]
129
133
available = 0 :: non_neg_integer (),
130
134
drain = false :: boolean (),
131
135
partial_transfers :: undefined | {# 'v1_0.transfer' {}, [binary ()]},
132
- auto_flow :: never | {auto , RenewWhenBelow :: pos_integer (), Credit :: pos_integer ()},
136
+ auto_flow :: never | {RenewWhenBelow :: pos_integer (),
137
+ Credit :: pos_integer ()},
133
138
incoming_unsettled = #{} :: #{delivery_number () => ok },
134
139
footer_opt :: footer_opt () | undefined
135
140
}).
140
145
141
146
% % session flow control, see section 2.5.6
142
147
next_incoming_id :: transfer_number () | undefined ,
143
- incoming_window = ? MAX_SESSION_WINDOW_SIZE :: non_neg_integer (),
148
+ % % Can become negative if the peer overshoots our window.
149
+ incoming_window :: integer (),
150
+ auto_flow :: never | {RenewWhenBelow :: pos_integer (),
151
+ NewWindowSize :: pos_integer ()},
144
152
next_outgoing_id = ? INITIAL_OUTGOING_TRANSFER_ID :: transfer_number (),
145
153
remote_incoming_window = 0 :: non_neg_integer (),
146
154
remote_outgoing_window = 0 :: non_neg_integer (),
@@ -200,7 +208,17 @@ transfer(Session, Amqp10Msg, Timeout) ->
200
208
[Transfer | Sections ] = amqp10_msg :to_amqp_records (Amqp10Msg ),
201
209
gen_statem :call (Session , {transfer , Transfer , Sections }, Timeout ).
202
210
203
- flow (Session , Handle , Flow , RenewWhenBelow ) ->
211
+ -spec flow (pid (), non_neg_integer (), never | pos_integer ()) -> ok .
212
+ flow (Session , IncomingWindow , RenewWhenBelow ) when
213
+ % % Check that the RenewWhenBelow value make sense.
214
+ RenewWhenBelow =:= never orelse
215
+ is_integer (RenewWhenBelow ) andalso
216
+ RenewWhenBelow > 0 andalso
217
+ RenewWhenBelow =< IncomingWindow ->
218
+ gen_statem :cast (Session , {flow_session , IncomingWindow , RenewWhenBelow }).
219
+
220
+ -spec flow_link (pid (), link_handle (), # 'v1_0.flow' {}, never | pos_integer ()) -> ok .
221
+ flow_link (Session , Handle , Flow , RenewWhenBelow ) ->
204
222
gen_statem :cast (Session , {flow_link , Handle , Flow , RenewWhenBelow }).
205
223
206
224
% % Sending a disposition on a sender link (with receiver-settle-mode = second)
@@ -239,6 +257,9 @@ init([FromPid, Channel, Reader, ConnConfig]) ->
239
257
channel = Channel ,
240
258
reader = Reader ,
241
259
connection_config = ConnConfig ,
260
+ incoming_window = ? DEFAULT_MAX_INCOMING_WINDOW ,
261
+ auto_flow = {? DEFAULT_MAX_INCOMING_WINDOW div 2 ,
262
+ ? DEFAULT_MAX_INCOMING_WINDOW },
242
263
early_attach_requests = []},
243
264
{ok , unmapped , State }.
244
265
@@ -282,15 +303,15 @@ mapped(cast, 'end', State) ->
282
303
mapped (cast , {flow_link , OutHandle , Flow0 , RenewWhenBelow }, State0 ) ->
283
304
State = send_flow_link (OutHandle , Flow0 , RenewWhenBelow , State0 ),
284
305
{keep_state , State };
285
- mapped (cast , {flow_session , Flow0 = # 'v1_0.flow' { incoming_window = { uint , IncomingWindow }}},
286
- # state { next_incoming_id = NII ,
287
- next_outgoing_id = NOI } = State ) ->
288
- Flow = Flow0 # 'v1_0.flow' {
289
- next_incoming_id = maybe_uint ( NII ) ,
290
- next_outgoing_id = uint ( NOI ) ,
291
- outgoing_window = ? UINT_OUTGOING_WINDOW },
292
- ok = send ( Flow , State ),
293
- {keep_state , State # state { incoming_window = IncomingWindow } };
306
+ mapped (cast , {flow_session , IncomingWindow , RenewWhenBelow }, State0 ) ->
307
+ AutoFlow = case RenewWhenBelow of
308
+ never -> never ;
309
+ _ -> { RenewWhenBelow , IncomingWindow }
310
+ end ,
311
+ State = State0 # state { incoming_window = IncomingWindow ,
312
+ auto_flow = AutoFlow },
313
+ send_flow_session ( State ),
314
+ {keep_state , State };
294
315
mapped (cast , # 'v1_0.end' {} = End , State ) ->
295
316
% % We receive the first end frame, reply and terminate.
296
317
_ = send_end (State ),
@@ -656,35 +677,44 @@ is_bare_message_section(_Section) ->
656
677
657
678
send_flow_link (OutHandle ,
658
679
# 'v1_0.flow' {link_credit = {uint , Credit }} = Flow0 , RenewWhenBelow ,
659
- # state {links = Links ,
660
- next_incoming_id = NII ,
661
- next_outgoing_id = NOI ,
662
- incoming_window = InWin } = State ) ->
680
+ # state {links = Links } = State ) ->
663
681
AutoFlow = case RenewWhenBelow of
664
682
never -> never ;
665
- Limit -> {auto , Limit , Credit }
683
+ _ -> {RenewWhenBelow , Credit }
666
684
end ,
667
685
#{OutHandle := # link {output_handle = H ,
668
686
role = receiver ,
669
687
delivery_count = DeliveryCount ,
670
688
available = Available } = Link } = Links ,
671
- Flow = Flow0 # 'v1_0.flow' {
672
- handle = uint (H ),
673
- % % "This value MUST be set if the peer has received the begin
674
- % % frame for the session, and MUST NOT be set if it has not." [2.7.4]
675
- next_incoming_id = maybe_uint (NII ),
676
- next_outgoing_id = uint (NOI ),
677
- outgoing_window = ? UINT_OUTGOING_WINDOW ,
678
- incoming_window = uint (InWin ),
679
- % % "In the event that the receiving link endpoint has not yet seen the
680
- % % initial attach frame from the sender this field MUST NOT be set." [2.7.4]
681
- delivery_count = maybe_uint (DeliveryCount ),
682
- available = uint (Available )},
689
+ Flow1 = Flow0 # 'v1_0.flow' {
690
+ handle = uint (H ),
691
+ % % "In the event that the receiving link endpoint has not yet seen the
692
+ % % initial attach frame from the sender this field MUST NOT be set." [2.7.4]
693
+ delivery_count = maybe_uint (DeliveryCount ),
694
+ available = uint (Available )},
695
+ Flow = set_flow_session_fields (Flow1 , State ),
683
696
ok = send (Flow , State ),
684
697
State # state {links = Links #{OutHandle =>
685
698
Link # link {link_credit = Credit ,
686
699
auto_flow = AutoFlow }}}.
687
700
701
+ send_flow_session (State ) ->
702
+ Flow = set_flow_session_fields (# 'v1_0.flow' {}, State ),
703
+ ok = send (Flow , State ).
704
+
705
+ set_flow_session_fields (Flow , # state {next_incoming_id = NID ,
706
+ incoming_window = IW ,
707
+ next_outgoing_id = NOI }) ->
708
+ Flow # 'v1_0.flow' {
709
+ % % "This value MUST be set if the peer has received the begin
710
+ % % frame for the session, and MUST NOT be set if it has not." [2.7.4]
711
+ next_incoming_id = maybe_uint (NID ),
712
+ % % IncomingWindow0 can be negative when the sending server overshoots our window.
713
+ % % We must set a floor of 0 in the FLOW frame because field incoming-window is an uint.
714
+ incoming_window = uint (max (0 , IW )),
715
+ next_outgoing_id = uint (NOI ),
716
+ outgoing_window = ? UINT_OUTGOING_WINDOW }.
717
+
688
718
build_frames (Channel , Trf , Bin , MaxPayloadSize , Acc )
689
719
when byte_size (Bin ) =< MaxPayloadSize ->
690
720
T = amqp10_framing :encode_bin (Trf # 'v1_0.transfer' {more = false }),
@@ -1059,17 +1089,21 @@ book_transfer_send(Num, #link{output_handle = Handle} = Link,
1059
1089
links = Links #{Handle => book_link_transfer_send (Link )}}.
1060
1090
1061
1091
book_partial_transfer_received (# state {next_incoming_id = NID ,
1062
- remote_outgoing_window = ROW } = State ) ->
1063
- State # state {next_incoming_id = add (NID , 1 ),
1064
- remote_outgoing_window = ROW - 1 }.
1092
+ incoming_window = IW ,
1093
+ remote_outgoing_window = ROW } = State0 ) ->
1094
+ State = State0 # state {next_incoming_id = add (NID , 1 ),
1095
+ incoming_window = IW - 1 ,
1096
+ remote_outgoing_window = ROW - 1 },
1097
+ maybe_widen_incoming_window (State ).
1065
1098
1066
1099
book_transfer_received (State = # state {connection_config =
1067
1100
#{transfer_limit_margin := Margin }},
1068
1101
# link {link_credit = Margin } = Link ) ->
1069
1102
{transfer_limit_exceeded , Link , State };
1070
1103
book_transfer_received (# state {next_incoming_id = NID ,
1104
+ incoming_window = IW ,
1071
1105
remote_outgoing_window = ROW ,
1072
- links = Links } = State ,
1106
+ links = Links } = State0 ,
1073
1107
# link {output_handle = OutHandle ,
1074
1108
delivery_count = DC ,
1075
1109
link_credit = LC ,
@@ -1079,19 +1113,31 @@ book_transfer_received(#state{next_incoming_id = NID,
1079
1113
% % "the receiver MUST maintain a floor of zero in its
1080
1114
% % calculation of the value of available" [2.6.7]
1081
1115
available = max (0 , Avail - 1 )},
1082
- State1 = State # state {links = Links #{OutHandle => Link1 },
1083
- next_incoming_id = add (NID , 1 ),
1084
- remote_outgoing_window = ROW - 1 },
1116
+ State1 = State0 # state {links = Links #{OutHandle => Link1 },
1117
+ next_incoming_id = add (NID , 1 ),
1118
+ incoming_window = IW - 1 ,
1119
+ remote_outgoing_window = ROW - 1 },
1120
+ State = maybe_widen_incoming_window (State1 ),
1085
1121
case Link1 of
1086
1122
# link {link_credit = 0 ,
1087
1123
auto_flow = never } ->
1088
- {credit_exhausted , Link1 , State1 };
1124
+ {credit_exhausted , Link1 , State };
1089
1125
_ ->
1090
- {ok , Link1 , State1 }
1126
+ {ok , Link1 , State }
1091
1127
end .
1092
1128
1129
+ maybe_widen_incoming_window (
1130
+ State0 = # state {incoming_window = IncomingWindow ,
1131
+ auto_flow = {RenewWhenBelow , NewWindowSize }})
1132
+ when IncomingWindow < RenewWhenBelow ->
1133
+ State = State0 # state {incoming_window = NewWindowSize },
1134
+ send_flow_session (State ),
1135
+ State ;
1136
+ maybe_widen_incoming_window (State ) ->
1137
+ State .
1138
+
1093
1139
auto_flow (# link {link_credit = LC ,
1094
- auto_flow = {auto , RenewWhenBelow , Credit },
1140
+ auto_flow = {RenewWhenBelow , Credit },
1095
1141
output_handle = OutHandle ,
1096
1142
incoming_unsettled = Unsettled },
1097
1143
State )
@@ -1230,6 +1276,7 @@ format_status(Status = #{data := Data0}) ->
1230
1276
remote_channel = RemoteChannel ,
1231
1277
next_incoming_id = NextIncomingId ,
1232
1278
incoming_window = IncomingWindow ,
1279
+ auto_flow = SessionAutoFlow ,
1233
1280
next_outgoing_id = NextOutgoingId ,
1234
1281
remote_incoming_window = RemoteIncomingWindow ,
1235
1282
remote_outgoing_window = RemoteOutgoingWindow ,
@@ -1294,6 +1341,7 @@ format_status(Status = #{data := Data0}) ->
1294
1341
remote_channel => RemoteChannel ,
1295
1342
next_incoming_id => NextIncomingId ,
1296
1343
incoming_window => IncomingWindow ,
1344
+ auto_flow => SessionAutoFlow ,
1297
1345
next_outgoing_id => NextOutgoingId ,
1298
1346
remote_incoming_window => RemoteIncomingWindow ,
1299
1347
remote_outgoing_window => RemoteOutgoingWindow ,
0 commit comments