New option to cap the max wait between reconnection attempts. Still default to 10s. (setMaxWaitBetweenReconnectionRetries) (#108)

This commit is contained in:
Benjamin Sergeant 2019-08-30 12:46:35 -07:00 committed by GitHub
parent 0c1f2252a1
commit 7a73ec7c06
12 changed files with 139 additions and 28 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
build build
*.pyc *.pyc
venv

View File

@ -25,6 +25,7 @@ set( IXWEBSOCKET_SOURCES
ixwebsocket/IXCancellationRequest.cpp ixwebsocket/IXCancellationRequest.cpp
ixwebsocket/IXConnectionState.cpp ixwebsocket/IXConnectionState.cpp
ixwebsocket/IXDNSLookup.cpp ixwebsocket/IXDNSLookup.cpp
ixwebsocket/IXExponentialBackoff.cpp
ixwebsocket/IXHttp.cpp ixwebsocket/IXHttp.cpp
ixwebsocket/IXHttpClient.cpp ixwebsocket/IXHttpClient.cpp
ixwebsocket/IXHttpServer.cpp ixwebsocket/IXHttpServer.cpp
@ -53,6 +54,7 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXCancellationRequest.h ixwebsocket/IXCancellationRequest.h
ixwebsocket/IXConnectionState.h ixwebsocket/IXConnectionState.h
ixwebsocket/IXDNSLookup.h ixwebsocket/IXDNSLookup.h
ixwebsocket/IXExponentialBackoff.h
ixwebsocket/IXHttp.h ixwebsocket/IXHttp.h
ixwebsocket/IXHttpClient.h ixwebsocket/IXHttpClient.h
ixwebsocket/IXHttpServer.h ixwebsocket/IXHttpServer.h

View File

@ -1,6 +1,14 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [5.0.9] - 2019-08-30
New option to cap the max wait between reconnection attempts. Still default to 10s. (setMaxWaitBetweenReconnectionRetries).
```
ws connect --max_wait 5000 ws://example.com # will only wait 5 seconds max between reconnection attempts
```
## [5.0.7] - 2019-08-23 ## [5.0.7] - 2019-08-23
- WebSocket: add new option to pass in extra HTTP headers when connecting. - WebSocket: add new option to pass in extra HTTP headers when connecting.
- `ws connect` add new option (-H, works like [curl](https://stackoverflow.com/questions/356705/how-to-send-a-header-using-a-http-request-through-a-curl-call)) to pass in extra HTTP headers when connecting - `ws connect` add new option (-H, works like [curl](https://stackoverflow.com/questions/356705/how-to-send-a-header-using-a-http-request-through-a-curl-call)) to pass in extra HTTP headers when connecting

View File

@ -187,6 +187,48 @@ headers["foo"] = "bar";
webSocket.setExtraHeaders(headers); webSocket.setExtraHeaders(headers);
``` ```
### Automatic reconnection
Automatic reconnection kicks in when the connection is disconnected without the user consent. This feature is on by default and can be turned off.
```
webSocket.enableAutomaticReconnection(); // turn on
webSocket.disableAutomaticReconnection(); // turn off
bool enabled = webSocket.isAutomaticReconnectionEnabled(); // query state
```
The technique to calculate wait time is called [exponential
backoff](https://docs.aws.amazon.com/general/latest/gr/api-retries.html). Here
are the default waiting times between attempts (from connecting with `ws connect ws://foo.com`)
```
> Connection error: Got bad status connecting to foo.com, status: 301, HTTP Status line: HTTP/1.1 301 Moved Permanently
#retries: 1
Wait time(ms): 100
#retries: 2
Wait time(ms): 200
#retries: 3
Wait time(ms): 400
#retries: 4
Wait time(ms): 800
#retries: 5
Wait time(ms): 1600
#retries: 6
Wait time(ms): 3200
#retries: 7
Wait time(ms): 6400
#retries: 8
Wait time(ms): 10000
```
The waiting time is capped by default at 10s between 2 attempts, but that value can be changed and queried.
```
webSocket.setMaxWaitBetweenReconnectionRetries(5 * 1000); // 5000ms = 5s
uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries();
```
## WebSocket server API ## WebSocket server API
``` ```

View File

@ -0,0 +1,26 @@
/*
* IXExponentialBackoff.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXExponentialBackoff.h"
#include <cmath>
namespace ix
{
uint32_t calculateRetryWaitMilliseconds(
uint32_t retry_count,
uint32_t maxWaitBetweenReconnectionRetries)
{
uint32_t wait_time = std::pow(2, retry_count) * 100;
if (wait_time > maxWaitBetweenReconnectionRetries || wait_time == 0)
{
wait_time = maxWaitBetweenReconnectionRetries;
}
return wait_time;
}
}

View File

@ -0,0 +1,16 @@
/*
* IXExponentialBackoff.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <cstdint>
namespace ix
{
uint32_t calculateRetryWaitMilliseconds(
uint32_t retry_count,
uint32_t maxWaitBetweenReconnectionRetries);
} // namespace ix

View File

@ -7,30 +7,11 @@
#include "IXWebSocket.h" #include "IXWebSocket.h"
#include "IXSetThreadName.h" #include "IXSetThreadName.h"
#include "IXWebSocketHandshake.h" #include "IXWebSocketHandshake.h"
#include "IXExponentialBackoff.h"
#include <cmath> #include <cmath>
#include <cassert> #include <cassert>
namespace
{
uint64_t calculateRetryWaitMilliseconds(uint32_t retry_count)
{
uint64_t wait_time;
if (retry_count <= 6)
{
// max wait_time is 6400 ms (2 ^ 6 = 64)
wait_time = ((uint64_t)std::pow(2, retry_count) * 100L);
}
else
{
wait_time = 10 * 1000; // 10 sec
}
return wait_time;
}
}
namespace ix namespace ix
{ {
OnTrafficTrackerCallback WebSocket::_onTrafficTrackerCallback = nullptr; OnTrafficTrackerCallback WebSocket::_onTrafficTrackerCallback = nullptr;
@ -38,11 +19,13 @@ namespace ix
const int WebSocket::kDefaultPingIntervalSecs(-1); const int WebSocket::kDefaultPingIntervalSecs(-1);
const int WebSocket::kDefaultPingTimeoutSecs(-1); const int WebSocket::kDefaultPingTimeoutSecs(-1);
const bool WebSocket::kDefaultEnablePong(true); const bool WebSocket::kDefaultEnablePong(true);
const uint32_t WebSocket::kDefaultMaxWaitBetweenReconnectionRetries(10 * 1000); // 10s
WebSocket::WebSocket() : WebSocket::WebSocket() :
_onMessageCallback(OnMessageCallback()), _onMessageCallback(OnMessageCallback()),
_stop(false), _stop(false),
_automaticReconnection(true), _automaticReconnection(true),
_maxWaitBetweenReconnectionRetries(kDefaultMaxWaitBetweenReconnectionRetries),
_handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs), _handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs),
_enablePong(kDefaultEnablePong), _enablePong(kDefaultEnablePong),
_pingIntervalSecs(kDefaultPingIntervalSecs), _pingIntervalSecs(kDefaultPingIntervalSecs),
@ -149,6 +132,18 @@ namespace ix
_perMessageDeflateOptions = perMessageDeflateOptions; _perMessageDeflateOptions = perMessageDeflateOptions;
} }
void WebSocket::setMaxWaitBetweenReconnectionRetries(uint32_t maxWaitBetweenReconnectionRetries)
{
std::lock_guard<std::mutex> lock(_configMutex);
_maxWaitBetweenReconnectionRetries = maxWaitBetweenReconnectionRetries;
}
uint32_t WebSocket::getMaxWaitBetweenReconnectionRetries() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _maxWaitBetweenReconnectionRetries;
}
void WebSocket::start() void WebSocket::start()
{ {
if (_thread.joinable()) return; // we've already been started if (_thread.joinable()) return; // we've already been started
@ -276,7 +271,7 @@ namespace ix
if (_automaticReconnection) if (_automaticReconnection)
{ {
duration = millis(calculateRetryWaitMilliseconds(retries++)); duration = millis(calculateRetryWaitMilliseconds(retries++, _maxWaitBetweenReconnectionRetries));
connectErr.wait_time = duration.count(); connectErr.wait_time = duration.count();
connectErr.retries = retries; connectErr.retries = retries;

View File

@ -97,6 +97,8 @@ namespace ix
void enableAutomaticReconnection(); void enableAutomaticReconnection();
void disableAutomaticReconnection(); void disableAutomaticReconnection();
bool isAutomaticReconnectionEnabled() const; bool isAutomaticReconnectionEnabled() const;
void setMaxWaitBetweenReconnectionRetries(uint32_t maxWaitBetweenReconnectionRetries);
uint32_t getMaxWaitBetweenReconnectionRetries() const;
private: private:
WebSocketSendInfo sendMessage(const std::string& text, WebSocketSendInfo sendMessage(const std::string& text,
@ -123,10 +125,14 @@ namespace ix
static OnTrafficTrackerCallback _onTrafficTrackerCallback; static OnTrafficTrackerCallback _onTrafficTrackerCallback;
std::atomic<bool> _stop; std::atomic<bool> _stop;
std::atomic<bool> _automaticReconnection;
std::thread _thread; std::thread _thread;
std::mutex _writeMutex; std::mutex _writeMutex;
// Automatic reconnection
std::atomic<bool> _automaticReconnection;
static const uint32_t kDefaultMaxWaitBetweenReconnectionRetries;
uint32_t _maxWaitBetweenReconnectionRetries;
std::atomic<int> _handshakeTimeoutSecs; std::atomic<int> _handshakeTimeoutSecs;
static const int kDefaultHandShakeTimeoutSecs; static const int kDefaultHandShakeTimeoutSecs;

View File

@ -74,6 +74,12 @@ install_cmake_for_linux:
mkdir -p /tmp/cmake mkdir -p /tmp/cmake
(cd /tmp/cmake ; curl -L -O https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz ; tar zxf cmake-3.14.0-Linux-x86_64.tar.gz) (cd /tmp/cmake ; curl -L -O https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz ; tar zxf cmake-3.14.0-Linux-x86_64.tar.gz)
# python -m venv venv
# source venv/bin/activate
# pip install mkdocs
doc:
./venv/bin/mkdocs build -d ../bsergean.github.io/IXWebSocket/site
.PHONY: test .PHONY: test
.PHONY: build .PHONY: build
.PHONY: ws .PHONY: ws

View File

@ -89,6 +89,7 @@ int main(int argc, char** argv)
int delayMs = -1; int delayMs = -1;
int count = 1; int count = 1;
int jobs = 4; int jobs = 4;
uint32_t maxWaitBetweenReconnectionRetries;
CLI::App* sendApp = app.add_subcommand("send", "Send a file"); CLI::App* sendApp = app.add_subcommand("send", "Send a file");
sendApp->add_option("url", url, "Connection url")->required(); sendApp->add_option("url", url, "Connection url")->required();
@ -113,6 +114,7 @@ int main(int argc, char** argv)
connectApp->add_flag("-d", disableAutomaticReconnection, "Disable Automatic Reconnection"); connectApp->add_flag("-d", disableAutomaticReconnection, "Disable Automatic Reconnection");
connectApp->add_flag("-x", disablePerMessageDeflate, "Disable per message deflate"); connectApp->add_flag("-x", disablePerMessageDeflate, "Disable per message deflate");
connectApp->add_flag("-b", binaryMode, "Send in binary mode"); connectApp->add_flag("-b", binaryMode, "Send in binary mode");
connectApp->add_option("--max_wait", maxWaitBetweenReconnectionRetries, "Max Wait Time between reconnection retries");
CLI::App* chatApp = app.add_subcommand("chat", "Group chat"); CLI::App* chatApp = app.add_subcommand("chat", "Group chat");
chatApp->add_option("url", url, "Connection url")->required(); chatApp->add_option("url", url, "Connection url")->required();
@ -254,7 +256,8 @@ int main(int argc, char** argv)
else if (app.got_subcommand("connect")) else if (app.got_subcommand("connect"))
{ {
ret = ix::ws_connect_main(url, headers, disableAutomaticReconnection, ret = ix::ws_connect_main(url, headers, disableAutomaticReconnection,
disablePerMessageDeflate, binaryMode); disablePerMessageDeflate, binaryMode,
maxWaitBetweenReconnectionRetries);
} }
else if (app.got_subcommand("chat")) else if (app.got_subcommand("chat"))
{ {

View File

@ -34,7 +34,8 @@ namespace ix
const std::string& headers, const std::string& headers,
bool disableAutomaticReconnection, bool disableAutomaticReconnection,
bool disablePerMessageDeflate, bool disablePerMessageDeflate,
bool binaryMode); bool binaryMode,
uint32_t maxWaitBetweenReconnectionRetries);
int ws_receive_main(const std::string& url, bool enablePerMessageDeflate, int delayMs); int ws_receive_main(const std::string& url, bool enablePerMessageDeflate, int delayMs);

View File

@ -21,7 +21,8 @@ namespace ix
const std::string& headers, const std::string& headers,
bool disableAutomaticReconnection, bool disableAutomaticReconnection,
bool disablePerMessageDeflate, bool disablePerMessageDeflate,
bool binaryMode); bool binaryMode,
uint32_t maxWaitBetweenReconnectionRetries);
void subscribe(const std::string& channel); void subscribe(const std::string& channel);
void start(); void start();
@ -44,7 +45,8 @@ namespace ix
const std::string& headers, const std::string& headers,
bool disableAutomaticReconnection, bool disableAutomaticReconnection,
bool disablePerMessageDeflate, bool disablePerMessageDeflate,
bool binaryMode) : bool binaryMode,
uint32_t maxWaitBetweenReconnectionRetries) :
_url(url), _url(url),
_disablePerMessageDeflate(disablePerMessageDeflate), _disablePerMessageDeflate(disablePerMessageDeflate),
_binaryMode(binaryMode) _binaryMode(binaryMode)
@ -53,6 +55,7 @@ namespace ix
{ {
_webSocket.disableAutomaticReconnection(); _webSocket.disableAutomaticReconnection();
} }
_webSocket.setMaxWaitBetweenReconnectionRetries(maxWaitBetweenReconnectionRetries);
_headers = parseHeaders(headers); _headers = parseHeaders(headers);
} }
@ -186,14 +189,16 @@ namespace ix
const std::string& headers, const std::string& headers,
bool disableAutomaticReconnection, bool disableAutomaticReconnection,
bool disablePerMessageDeflate, bool disablePerMessageDeflate,
bool binaryMode) bool binaryMode,
uint32_t maxWaitBetweenReconnectionRetries)
{ {
std::cout << "Type Ctrl-D to exit prompt..." << std::endl; std::cout << "Type Ctrl-D to exit prompt..." << std::endl;
WebSocketConnect webSocketChat(url, WebSocketConnect webSocketChat(url,
headers, headers,
disableAutomaticReconnection, disableAutomaticReconnection,
disablePerMessageDeflate, disablePerMessageDeflate,
binaryMode); binaryMode,
maxWaitBetweenReconnectionRetries);
webSocketChat.start(); webSocketChat.start();
while (true) while (true)