Skip to content

Commit c38a0a4

Browse files
authored
Disconnect the network before reconnects. (#77)
1 parent b288d6f commit c38a0a4

12 files changed

+166
-99
lines changed

src/client.toit

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// found in the LICENSE file.
44
55
import log
6+
import net
7+
import tls
68
import .full_client
79
import .session_options
810
import .last_will
@@ -22,7 +24,7 @@ class Client:
2224
Constructs a new routing MQTT client.
2325
2426
The $transport parameter is used to send messages and is usually a TCP socket instance.
25-
See $TcpTransport.
27+
See $(constructor --host), $Client.tls, and $TcpTransport.
2628
2729
The $routes must be a map from topic (of type $string) to callback. After the client started, it
2830
will automatically subscribe to all topics in the map (with a max-qos of 1). If the broker already
@@ -40,6 +42,36 @@ class Client:
4042
max_qos := 1
4143
subscription_callbacks_.set topic (CallbackEntry_ callback max_qos)
4244

45+
/**
46+
Variant of $(constructor --transport) that connects to the given $host:$port over TCP.
47+
*/
48+
constructor
49+
--host /string
50+
--port /int = 1883
51+
--net_open /Lambda? = (:: net.open)
52+
--logger /log.Logger = log.default
53+
--routes /Map = {:}:
54+
transport := TcpTransport --host=host --port=port --net_open=net_open
55+
return Client --transport=transport --logger=logger --routes=routes
56+
57+
/**
58+
Variant of $(constructor --host) that supports TLS.
59+
*/
60+
constructor.tls
61+
--host /string
62+
--port /int = 8883
63+
--net_open /Lambda? = (:: net.open)
64+
--root_certificates /List = []
65+
--server_name /string? = null
66+
--certificate /tls.Certificate? = null
67+
--logger /log.Logger = log.default
68+
--routes /Map = {:}:
69+
transport := TcpTransport.tls --host=host --port=port --net_open=net_open
70+
--root_certificates=root_certificates
71+
--server_name=server_name
72+
--certificate=certificate
73+
return Client --transport=transport --logger=logger --routes=routes
74+
4375
/**
4476
Variant of $(start --options).
4577

src/full_client.toit

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ interface ReconnectionStrategy:
198198
The strategy should first call $send_connect, followed by a $receive_connect_ack. If
199199
the connection is unsuccessful, it may retry.
200200
201+
The $disconnect_transport may be called before attempting a reconnect. It shuts down the
202+
network, thus increasing the chance of a successful reconnect. However, a complete
203+
network reconnect takes more time. We recommend to try at least once to reconnect without
204+
a disconnect first, and then call $disconnect_transport for later attempts.
205+
201206
The $receive_connect_ack block returns whether the broker had a session for this client.
202207
203208
The $is_initial_connection is true if this is the first time the client connects to the broker.
@@ -206,6 +211,7 @@ interface ReconnectionStrategy:
206211
transport/ActivityMonitoringTransport
207212
--is_initial_connection /bool
208213
[--reconnect_transport]
214+
[--disconnect_transport]
209215
[--send_connect]
210216
[--receive_connect_ack]
211217
[--disconnect]
@@ -280,6 +286,7 @@ abstract class DefaultReconnectionStrategyBase implements ReconnectionStrategy:
280286
do_connect transport/ActivityMonitoringTransport
281287
--reuse_connection/bool=false
282288
[--reconnect_transport]
289+
[--disconnect_transport]
283290
[--send_connect]
284291
[--receive_connect_ack]:
285292
attempt_counter := -1
@@ -306,6 +313,7 @@ abstract class DefaultReconnectionStrategyBase implements ReconnectionStrategy:
306313
sleep_duration = attempt_delays_[attempt_counter - 1]
307314
else:
308315
sleep_duration = delay_lambda_.call attempt_counter
316+
disconnect_transport.call
309317
closed_signal_.wait --timeout=sleep_duration
310318
if is_closed: return null
311319
logger_.debug "Attempting to (re)connect"
@@ -327,6 +335,7 @@ abstract class DefaultReconnectionStrategyBase implements ReconnectionStrategy:
327335
transport/ActivityMonitoringTransport
328336
--is_initial_connection /bool
329337
[--reconnect_transport]
338+
[--disconnect_transport]
330339
[--send_connect]
331340
[--receive_connect_ack]
332341
[--disconnect]
@@ -364,6 +373,7 @@ class DefaultCleanSessionReconnectionStrategy extends DefaultReconnectionStrateg
364373
transport/ActivityMonitoringTransport
365374
--is_initial_connection /bool
366375
[--reconnect_transport]
376+
[--disconnect_transport]
367377
[--send_connect]
368378
[--receive_connect_ack]
369379
[--disconnect]:
@@ -372,6 +382,7 @@ class DefaultCleanSessionReconnectionStrategy extends DefaultReconnectionStrateg
372382
session_exists := do_connect transport
373383
--reuse_connection = is_initial_connection
374384
--reconnect_transport = reconnect_transport
385+
--disconnect_transport = disconnect_transport
375386
--send_connect = send_connect
376387
--receive_connect_ack = receive_connect_ack
377388
if session_exists:
@@ -402,12 +413,14 @@ class DefaultSessionReconnectionStrategy extends DefaultReconnectionStrategyBase
402413
transport/ActivityMonitoringTransport
403414
--is_initial_connection /bool
404415
[--reconnect_transport]
416+
[--disconnect_transport]
405417
[--send_connect]
406418
[--receive_connect_ack]
407419
[--disconnect]:
408420
session_exists := do_connect transport
409421
--reuse_connection = is_initial_connection
410422
--reconnect_transport = reconnect_transport
423+
--disconnect_transport = disconnect_transport
411424
--send_connect = send_connect
412425
--receive_connect_ack = receive_connect_ack
413426

@@ -468,12 +481,14 @@ class TenaciousReconnectionStrategy extends DefaultReconnectionStrategyBase:
468481
transport/ActivityMonitoringTransport
469482
--is_initial_connection /bool
470483
[--reconnect_transport]
484+
[--disconnect_transport]
471485
[--send_connect]
472486
[--receive_connect_ack]
473487
[--disconnect]:
474488
session_exists := do_connect transport
475489
--reuse_connection = is_initial_connection
476490
--reconnect_transport = reconnect_transport
491+
--disconnect_transport = disconnect_transport
477492
--send_connect = send_connect
478493
--receive_connect_ack = receive_connect_ack
479494

@@ -1136,6 +1151,8 @@ class FullClient:
11361151
--reconnect_transport = :
11371152
transport_.reconnect
11381153
connection_ = Connection_ transport_ --keep_alive=session_.options.keep_alive
1154+
--disconnect_transport = :
1155+
transport_.disconnect
11391156
--send_connect = :
11401157
packet := ConnectPacket session_.options.client_id
11411158
--clean_session=session_.options.clean_session
@@ -1186,4 +1203,3 @@ class FullClient:
11861203
if reconnection_strategy_: reconnection_strategy_.close
11871204
connection_.close
11881205
if not handling_latch_.has_value: handling_latch_.set false
1189-

src/tcp.toit

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,28 @@ class TcpTransport implements Transport BrokerTransport:
2525
constructor socket/tcp.Socket:
2626
socket_ = socket
2727

28+
/** Deprecated. Use $(constructor --net_open --host) instead. */
2829
constructor network/net.Interface --host/string --port/int=1883:
29-
return ReconnectingTransport_ network --host=host --port=port
30+
return ReconnectingTransport_ network --net_open=null --host=host --port=port
3031

32+
constructor --net_open/Lambda --host/string --port/int=1883:
33+
return ReconnectingTransport_ null --net_open=net_open --host=host --port=port
34+
35+
/** Deprecated. Use $(TcpTransport.tls --net_open --host) instead. */
3136
constructor.tls network/net.Interface --host/string --port/int=8883
3237
--root_certificates/List=[]
3338
--server_name/string?=null
3439
--certificate/tls.Certificate?=null:
35-
return ReconnectingTlsTransport_ network --host=host --port=port
40+
return ReconnectingTlsTransport_ network --net_open=null --host=host --port=port
41+
--root_certificates=root_certificates
42+
--server_name=server_name
43+
--certificate=certificate
44+
45+
constructor.tls --net_open/Lambda --host/string --port/int=8883
46+
--root_certificates/List=[]
47+
--server_name/string?=null
48+
--certificate/tls.Certificate?=null:
49+
return ReconnectingTlsTransport_ null --net_open=net_open --host=host --port=port
3650
--root_certificates=root_certificates
3751
--server_name=server_name
3852
--certificate=certificate
@@ -58,17 +72,24 @@ class TcpTransport implements Transport BrokerTransport:
5872
reconnect -> none:
5973
throw "UNSUPPORTED"
6074

75+
disconnect -> none:
76+
throw "UNSUPPORTED"
77+
6178
class ReconnectingTransport_ extends TcpTransport:
6279
// Reconnection information.
63-
network_ /net.Interface
80+
network_ /net.Interface? := null
6481
host_ /string
6582
port_ /int
83+
open_ /Lambda?
6684

6785
reconnecting_mutex_ /monitor.Mutex := monitor.Mutex
6886

69-
constructor .network_ --host/string --port/int=1883:
87+
constructor .network_ --net_open/Lambda? --host/string --port/int=1883:
88+
if not network_ and not net_open: throw "Either network or net_open must be provided"
89+
if network_ and net_open: throw "Only one of network or net_open must be provided"
7090
host_ = host
7191
port_ = port
92+
open_ = net_open
7293
super.from_subclass_ null
7394
reconnect
7495

@@ -78,7 +99,15 @@ class ReconnectingTransport_ extends TcpTransport:
7899
read -> ByteArray?:
79100
return socket_.read
80101

102+
close -> none:
103+
super
104+
// Only close the network if we were the ones who opened it.
105+
if open_ and network_:
106+
network_.close
107+
network_ = null
108+
81109
reconnect:
110+
if not network_: network_ = open_.call
82111
old_socket := socket_
83112
reconnecting_mutex_.do:
84113
if not identical old_socket socket_: return
@@ -105,19 +134,23 @@ class ReconnectingTransport_ extends TcpTransport:
105134
supports_reconnect -> bool:
106135
return true
107136

137+
disconnect:
138+
if not open_: return
139+
if network_: network_.close
140+
108141
class ReconnectingTlsTransport_ extends ReconnectingTransport_:
109142
certificate_ /tls.Certificate?
110143
server_name_ /string?
111144
root_certificates_ /List
112145

113-
constructor network/net.Interface --host/string --port/int
146+
constructor network/net.Interface? --net_open/Lambda? --host/string --port/int
114147
--root_certificates/List=[]
115148
--server_name/string?=null
116149
--certificate/tls.Certificate?=null:
117150
root_certificates_ = root_certificates
118151
server_name_ = server_name
119152
certificate_ = certificate
120-
super network --host=host --port=port
153+
super network --net_open=net_open --host=host --port=port
121154

122155
new_connection_ -> tcp.Socket:
123156
socket := network_.tcp_connect host_ port_

src/transport.toit

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ interface Transport implements reader.Reader:
4444
*/
4545
reconnect -> none
4646

47+
/**
48+
Disconnects the transport.
49+
50+
This can be called before doing a $reconnect. It will close the network, if
51+
that's supported.
52+
Disconnecting is not the same as closing. A transport can be disconnected
53+
and still be open.
54+
*/
55+
disconnect -> none
56+
4757
/**
4858
Whether the transport is closed.
4959
If it $supports_reconnect then calling $reconnect reopens the transport.
@@ -99,5 +109,8 @@ class ActivityMonitoringTransport implements Transport:
99109
reconnect -> none:
100110
wrapped_transport_.reconnect
101111

112+
disconnect -> none:
113+
wrapped_transport_.disconnect
114+
102115
is_closed -> bool:
103116
return wrapped_transport_.is_closed

tests/broker_mosquitto.toit

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ with_mosquitto --logger/log.Logger [block]:
9090
sleep --ms=(50*i)
9191

9292
try:
93-
block.call:: mqtt.TcpTransport network --host="localhost" --port=port
93+
block.call:: mqtt.TcpTransport --net_open=(:: net.open) --host="localhost" --port=port
9494
finally: | is_exception _ |
9595
pid := mosquitto_fork_data[3]
9696
logger.info "killing mosquitto server"
@@ -106,4 +106,4 @@ Can sometimes be useful, as the logging is better.
106106
*/
107107
with_external_mosquitto --logger/log.Logger [block]:
108108
network := net.open
109-
block.call:: mqtt.TcpTransport network --host="localhost" --port=1883
109+
block.call:: mqtt.TcpTransport --net_open=(:: net.open) --host="localhost" --port=1883

tests/max_inflight_test.toit

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class TestTransport implements mqtt.Transport:
1919
wrapped_ /mqtt.Transport
2020

2121
on_reconnect /Lambda? := null
22+
on_disconnect /Lambda? := null
2223
on_write /Lambda? := null
2324
on_read /Lambda? := null
2425

@@ -40,6 +41,10 @@ class TestTransport implements mqtt.Transport:
4041
if on_reconnect: on_reconnect.call
4142
wrapped_.reconnect
4243

44+
disconnect -> none:
45+
if on_disconnect: on_disconnect.call
46+
wrapped_.disconnect
47+
4348
is_closed -> bool: return wrapped_.is_closed
4449

4550
/**

tests/package.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ packages:
77
path: ..
88
pkg-host:
99
url: github.com/toitlang/pkg-host
10+
name: host
1011
version: 1.6.0
1112
hash: d05b91390e76c3543a9968b042aed330210bafa4

tests/ping_test.toit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class SlowTransport implements mqtt.Transport:
6666
close -> none: wrapped_.close
6767
supports_reconnect -> bool: return wrapped_.supports_reconnect
6868
reconnect -> none: wrapped_.reconnect
69+
disconnect -> none: wrapped_.disconnect
6970
is_closed -> bool: return wrapped_.is_closed
7071

7172
/**

0 commit comments

Comments
 (0)