From 77c7fdc636925de88199a23107afa981c56b4be8 Mon Sep 17 00:00:00 2001 From: Dimon4eg Date: Wed, 8 May 2019 22:02:56 +0300 Subject: [PATCH 01/53] Added IXWebSocketPoll class --- CMakeLists.txt | 2 + ixwebsocket/IXWebSocket.h | 5 ++ ixwebsocket/IXWebSocketPoll.cpp | 108 ++++++++++++++++++++++++++++++++ ixwebsocket/IXWebSocketPoll.h | 52 +++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 ixwebsocket/IXWebSocketPoll.cpp create mode 100644 ixwebsocket/IXWebSocketPoll.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f86fcf95..9860e023 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ set( IXWEBSOCKET_SOURCES ixwebsocket/IXCancellationRequest.cpp ixwebsocket/IXNetSystem.cpp ixwebsocket/IXWebSocket.cpp + ixwebsocket/IXWebSocketPoll.cpp ixwebsocket/IXWebSocketServer.cpp ixwebsocket/IXWebSocketTransport.cpp ixwebsocket/IXWebSocketHandshake.cpp @@ -54,6 +55,7 @@ set( IXWEBSOCKET_HEADERS ixwebsocket/IXNetSystem.h ixwebsocket/IXProgressCallback.h ixwebsocket/IXWebSocket.h + ixwebsocket/IXWebSocketPoll.h ixwebsocket/IXWebSocketServer.h ixwebsocket/IXWebSocketTransport.h ixwebsocket/IXWebSocketHandshake.h diff --git a/ixwebsocket/IXWebSocket.h b/ixwebsocket/IXWebSocket.h index 8c6bf702..e655d196 100644 --- a/ixwebsocket/IXWebSocket.h +++ b/ixwebsocket/IXWebSocket.h @@ -113,6 +113,11 @@ namespace ix WebSocketSendInfo ping(const std::string& text); void close(); + /** + * Set callback to receive websocket messages. + * + * Be aware: your callback will be executed from websocket's internal thread! + */ void setOnMessageCallback(const OnMessageCallback& callback); static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback); static void resetTrafficTrackerCallback(); diff --git a/ixwebsocket/IXWebSocketPoll.cpp b/ixwebsocket/IXWebSocketPoll.cpp new file mode 100644 index 00000000..9634e198 --- /dev/null +++ b/ixwebsocket/IXWebSocketPoll.cpp @@ -0,0 +1,108 @@ +/* + * IXWebSocketPoll.cpp + * Author: Korchynskyi Dmytro + * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. + */ + +#include "IXWebSocketPoll.h" + +namespace ix +{ + + WebSocketPoll::WebSocketPoll(WebSocket* websocket) + { + } + + WebSocketPoll::~WebSocketPoll() + { + if (!_messages.empty()) + { + + } + + bindWebsocket(nullptr); + } + + void WebSocketPoll::bindWebsocket(WebSocket * websocket) + { + if (_websocket != websocket) + { + // unbind old + if (_websocket) + { + _websocket->setOnMessageCallback(nullptr); + } + + _websocket = websocket; + + // bind new + if (_websocket) + { + _websocket->setOnMessageCallback([this]( + WebSocketMessageType type, + const std::string& str, + size_t wireSize, + const WebSocketErrorInfo& errorInfo, + const WebSocketOpenInfo& openInfo, + const WebSocketCloseInfo& closeInfo) + { + MessageDataPtr message(new Message()); + + message->type = type; + message->str = str; + message->wireSize = wireSize; + message->errorInfo = errorInfo; + message->openInfo = openInfo; + message->closeInfo = closeInfo; + + _messagesMutex.lock(); + _messages.emplace_back(std::move(message)); + _messagesMutex.unlock(); + }); + } + } + } + + void WebSocketPoll::setOnMessageCallback(const OnMessageCallback& callback) + { + _onMessageUserCallback = callback; + } + + WebSocketPoll::MessageDataPtr WebSocketPoll::popMessage() + { + MessageDataPtr message; + + _messagesMutex.lock(); + if (!_messages.empty()) + { + message = std::move(_messages.front()); + _messages.pop_front(); + } + _messagesMutex.unlock(); + + return message; + } + + void WebSocketPoll::poll(int count) + { + if (!_onMessageUserCallback) + return; + + MessageDataPtr message; + + while (count > 0 && (message = popMessage())) + { + _onMessageUserCallback( + message->type, + message->str, + message->wireSize, + message->errorInfo, + message->openInfo, + message->closeInfo + ); + + --count; + } + } + +} diff --git a/ixwebsocket/IXWebSocketPoll.h b/ixwebsocket/IXWebSocketPoll.h new file mode 100644 index 00000000..c16eb808 --- /dev/null +++ b/ixwebsocket/IXWebSocketPoll.h @@ -0,0 +1,52 @@ +/* + * IXWebSocketPoll.h + * Author: Korchynskyi Dmytro + * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include "IXWebSocket.h" +#include +#include +#include + +namespace ix +{ + /** + * A helper class to dispatch websocket message callbacks in your thread. + */ + class WebSocketPoll + { + public: + WebSocketPoll(WebSocket* websocket = nullptr); + ~WebSocketPoll(); + + void bindWebsocket(WebSocket* websocket); + + void setOnMessageCallback(const OnMessageCallback& callback); + + void poll(int count = 512); + + protected: + struct Message + { + WebSocketMessageType type; + std::string str; + size_t wireSize; + WebSocketErrorInfo errorInfo; + WebSocketOpenInfo openInfo; + WebSocketCloseInfo closeInfo; + }; + + using MessageDataPtr = std::shared_ptr; + + MessageDataPtr popMessage(); + + private: + WebSocket* _websocket = nullptr; + OnMessageCallback _onMessageUserCallback; + std::mutex _messagesMutex; + std::list _messages; + }; +} From 7fb1b65ddd272e092b617da6f19308e9dd7c06d1 Mon Sep 17 00:00:00 2001 From: Dimon4eg Date: Wed, 8 May 2019 22:24:39 +0300 Subject: [PATCH 02/53] qf --- ixwebsocket/IXWebSocketPoll.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ixwebsocket/IXWebSocketPoll.cpp b/ixwebsocket/IXWebSocketPoll.cpp index 9634e198..53d07d5f 100644 --- a/ixwebsocket/IXWebSocketPoll.cpp +++ b/ixwebsocket/IXWebSocketPoll.cpp @@ -11,13 +11,14 @@ namespace ix WebSocketPoll::WebSocketPoll(WebSocket* websocket) { + bindWebsocket(websocket); } WebSocketPoll::~WebSocketPoll() { if (!_messages.empty()) { - + // not handled all messages } bindWebsocket(nullptr); From d6e9b61c8e3ab02525265646f32b6b6de0521677 Mon Sep 17 00:00:00 2001 From: dimon4eg Date: Thu, 9 May 2019 00:09:51 +0300 Subject: [PATCH 03/53] Rename to WebSocketMessageQueue --- CMakeLists.txt | 4 ++-- ...bSocketPoll.cpp => IXWebSocketMessageQueue.cpp} | 14 +++++++------- ...IXWebSocketPoll.h => IXWebSocketMessageQueue.h} | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) rename ixwebsocket/{IXWebSocketPoll.cpp => IXWebSocketMessageQueue.cpp} (84%) rename ixwebsocket/{IXWebSocketPoll.h => IXWebSocketMessageQueue.h} (89%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9860e023..cebf10e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,7 @@ set( IXWEBSOCKET_SOURCES ixwebsocket/IXCancellationRequest.cpp ixwebsocket/IXNetSystem.cpp ixwebsocket/IXWebSocket.cpp - ixwebsocket/IXWebSocketPoll.cpp + ixwebsocket/IXWebSocketMessageQueue.cpp ixwebsocket/IXWebSocketServer.cpp ixwebsocket/IXWebSocketTransport.cpp ixwebsocket/IXWebSocketHandshake.cpp @@ -55,7 +55,7 @@ set( IXWEBSOCKET_HEADERS ixwebsocket/IXNetSystem.h ixwebsocket/IXProgressCallback.h ixwebsocket/IXWebSocket.h - ixwebsocket/IXWebSocketPoll.h + ixwebsocket/IXWebSocketMessageQueue.h ixwebsocket/IXWebSocketServer.h ixwebsocket/IXWebSocketTransport.h ixwebsocket/IXWebSocketHandshake.h diff --git a/ixwebsocket/IXWebSocketPoll.cpp b/ixwebsocket/IXWebSocketMessageQueue.cpp similarity index 84% rename from ixwebsocket/IXWebSocketPoll.cpp rename to ixwebsocket/IXWebSocketMessageQueue.cpp index 53d07d5f..25a66d67 100644 --- a/ixwebsocket/IXWebSocketPoll.cpp +++ b/ixwebsocket/IXWebSocketMessageQueue.cpp @@ -4,17 +4,17 @@ * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. */ -#include "IXWebSocketPoll.h" +#include "IXWebSocketMessageQueue.h" namespace ix { - WebSocketPoll::WebSocketPoll(WebSocket* websocket) + WebSocketMessageQueue::WebSocketMessageQueue(WebSocket* websocket) { bindWebsocket(websocket); } - WebSocketPoll::~WebSocketPoll() + WebSocketMessageQueue::~WebSocketMessageQueue() { if (!_messages.empty()) { @@ -24,7 +24,7 @@ namespace ix bindWebsocket(nullptr); } - void WebSocketPoll::bindWebsocket(WebSocket * websocket) + void WebSocketMessageQueue::bindWebsocket(WebSocket * websocket) { if (_websocket != websocket) { @@ -64,12 +64,12 @@ namespace ix } } - void WebSocketPoll::setOnMessageCallback(const OnMessageCallback& callback) + void WebSocketMessageQueue::setOnMessageCallback(const OnMessageCallback& callback) { _onMessageUserCallback = callback; } - WebSocketPoll::MessageDataPtr WebSocketPoll::popMessage() + WebSocketMessageQueue::MessageDataPtr WebSocketMessageQueue::popMessage() { MessageDataPtr message; @@ -84,7 +84,7 @@ namespace ix return message; } - void WebSocketPoll::poll(int count) + void WebSocketMessageQueue::poll(int count) { if (!_onMessageUserCallback) return; diff --git a/ixwebsocket/IXWebSocketPoll.h b/ixwebsocket/IXWebSocketMessageQueue.h similarity index 89% rename from ixwebsocket/IXWebSocketPoll.h rename to ixwebsocket/IXWebSocketMessageQueue.h index c16eb808..46507527 100644 --- a/ixwebsocket/IXWebSocketPoll.h +++ b/ixwebsocket/IXWebSocketMessageQueue.h @@ -16,11 +16,11 @@ namespace ix /** * A helper class to dispatch websocket message callbacks in your thread. */ - class WebSocketPoll + class WebSocketMessageQueue { public: - WebSocketPoll(WebSocket* websocket = nullptr); - ~WebSocketPoll(); + WebSocketMessageQueue(WebSocket* websocket = nullptr); + ~WebSocketMessageQueue(); void bindWebsocket(WebSocket* websocket); From 469d127d619c32f8012b17f8bab20a5abdf1c448 Mon Sep 17 00:00:00 2001 From: dimon4eg Date: Thu, 9 May 2019 00:16:37 +0300 Subject: [PATCH 04/53] update comments --- ixwebsocket/IXWebSocket.h | 8 +++----- ixwebsocket/IXWebSocketMessageQueue.cpp | 4 ++-- ixwebsocket/IXWebSocketMessageQueue.h | 10 +++++----- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/ixwebsocket/IXWebSocket.h b/ixwebsocket/IXWebSocket.h index e655d196..c1d8c0e4 100644 --- a/ixwebsocket/IXWebSocket.h +++ b/ixwebsocket/IXWebSocket.h @@ -113,12 +113,10 @@ namespace ix WebSocketSendInfo ping(const std::string& text); void close(); - /** - * Set callback to receive websocket messages. - * - * Be aware: your callback will be executed from websocket's internal thread! - */ + // Set callback to receive websocket messages. + // Be aware: your callback will be executed from websocket's internal thread! void setOnMessageCallback(const OnMessageCallback& callback); + static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback); static void resetTrafficTrackerCallback(); diff --git a/ixwebsocket/IXWebSocketMessageQueue.cpp b/ixwebsocket/IXWebSocketMessageQueue.cpp index 25a66d67..b7c074f7 100644 --- a/ixwebsocket/IXWebSocketMessageQueue.cpp +++ b/ixwebsocket/IXWebSocketMessageQueue.cpp @@ -1,7 +1,7 @@ /* - * IXWebSocketPoll.cpp + * IXWebSocketMessageQueue.cpp * Author: Korchynskyi Dmytro - * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. + * Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved. */ #include "IXWebSocketMessageQueue.h" diff --git a/ixwebsocket/IXWebSocketMessageQueue.h b/ixwebsocket/IXWebSocketMessageQueue.h index 46507527..961f57e2 100644 --- a/ixwebsocket/IXWebSocketMessageQueue.h +++ b/ixwebsocket/IXWebSocketMessageQueue.h @@ -1,7 +1,7 @@ /* - * IXWebSocketPoll.h + * IXWebSocketMessageQueue.h * Author: Korchynskyi Dmytro - * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. + * Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved. */ #pragma once @@ -13,9 +13,9 @@ namespace ix { - /** - * A helper class to dispatch websocket message callbacks in your thread. - */ + // + // A helper class to dispatch websocket message callbacks in your thread. + // class WebSocketMessageQueue { public: From 57562b234f371aac90552c2f95bcfb3b1146dc88 Mon Sep 17 00:00:00 2001 From: dimon4eg Date: Thu, 9 May 2019 00:20:26 +0300 Subject: [PATCH 05/53] use lock_guard --- ixwebsocket/IXWebSocketMessageQueue.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ixwebsocket/IXWebSocketMessageQueue.cpp b/ixwebsocket/IXWebSocketMessageQueue.cpp index b7c074f7..a62757bf 100644 --- a/ixwebsocket/IXWebSocketMessageQueue.cpp +++ b/ixwebsocket/IXWebSocketMessageQueue.cpp @@ -56,9 +56,10 @@ namespace ix message->openInfo = openInfo; message->closeInfo = closeInfo; - _messagesMutex.lock(); - _messages.emplace_back(std::move(message)); - _messagesMutex.unlock(); + { + std::lock_guard lock(_messagesMutex); + _messages.emplace_back(std::move(message)); + } }); } } @@ -72,14 +73,13 @@ namespace ix WebSocketMessageQueue::MessageDataPtr WebSocketMessageQueue::popMessage() { MessageDataPtr message; + std::lock_guard lock(_messagesMutex); - _messagesMutex.lock(); if (!_messages.empty()) { message = std::move(_messages.front()); _messages.pop_front(); } - _messagesMutex.unlock(); return message; } From d0a41f389414a4d8e1808013eabc37642d767825 Mon Sep 17 00:00:00 2001 From: dimon4eg Date: Thu, 9 May 2019 00:23:16 +0300 Subject: [PATCH 06/53] simplify bindWebsocket --- ixwebsocket/IXWebSocketMessageQueue.cpp | 61 ++++++++++++------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/ixwebsocket/IXWebSocketMessageQueue.cpp b/ixwebsocket/IXWebSocketMessageQueue.cpp index a62757bf..222478d8 100644 --- a/ixwebsocket/IXWebSocketMessageQueue.cpp +++ b/ixwebsocket/IXWebSocketMessageQueue.cpp @@ -26,42 +26,41 @@ namespace ix void WebSocketMessageQueue::bindWebsocket(WebSocket * websocket) { - if (_websocket != websocket) + if (_websocket == websocket) return; + + // unbind old + if (_websocket) { - // unbind old - if (_websocket) - { - _websocket->setOnMessageCallback(nullptr); - } + _websocket->setOnMessageCallback(nullptr); + } - _websocket = websocket; + _websocket = websocket; - // bind new - if (_websocket) + // bind new + if (_websocket) + { + _websocket->setOnMessageCallback([this]( + WebSocketMessageType type, + const std::string& str, + size_t wireSize, + const WebSocketErrorInfo& errorInfo, + const WebSocketOpenInfo& openInfo, + const WebSocketCloseInfo& closeInfo) { - _websocket->setOnMessageCallback([this]( - WebSocketMessageType type, - const std::string& str, - size_t wireSize, - const WebSocketErrorInfo& errorInfo, - const WebSocketOpenInfo& openInfo, - const WebSocketCloseInfo& closeInfo) + MessageDataPtr message(new Message()); + + message->type = type; + message->str = str; + message->wireSize = wireSize; + message->errorInfo = errorInfo; + message->openInfo = openInfo; + message->closeInfo = closeInfo; + { - MessageDataPtr message(new Message()); - - message->type = type; - message->str = str; - message->wireSize = wireSize; - message->errorInfo = errorInfo; - message->openInfo = openInfo; - message->closeInfo = closeInfo; - - { - std::lock_guard lock(_messagesMutex); - _messages.emplace_back(std::move(message)); - } - }); - } + std::lock_guard lock(_messagesMutex); + _messages.emplace_back(std::move(message)); + } + }); } } From 7ecaf1f982b852ef971b3fe209825ad407578a07 Mon Sep 17 00:00:00 2001 From: dimon4eg Date: Thu, 9 May 2019 01:05:47 +0300 Subject: [PATCH 07/53] rename ptr --- ixwebsocket/IXWebSocketMessageQueue.cpp | 8 ++++---- ixwebsocket/IXWebSocketMessageQueue.h | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ixwebsocket/IXWebSocketMessageQueue.cpp b/ixwebsocket/IXWebSocketMessageQueue.cpp index 222478d8..62e759bd 100644 --- a/ixwebsocket/IXWebSocketMessageQueue.cpp +++ b/ixwebsocket/IXWebSocketMessageQueue.cpp @@ -47,7 +47,7 @@ namespace ix const WebSocketOpenInfo& openInfo, const WebSocketCloseInfo& closeInfo) { - MessageDataPtr message(new Message()); + MessagePtr message(new Message()); message->type = type; message->str = str; @@ -69,9 +69,9 @@ namespace ix _onMessageUserCallback = callback; } - WebSocketMessageQueue::MessageDataPtr WebSocketMessageQueue::popMessage() + WebSocketMessageQueue::MessagePtr WebSocketMessageQueue::popMessage() { - MessageDataPtr message; + MessagePtr message; std::lock_guard lock(_messagesMutex); if (!_messages.empty()) @@ -88,7 +88,7 @@ namespace ix if (!_onMessageUserCallback) return; - MessageDataPtr message; + MessagePtr message; while (count > 0 && (message = popMessage())) { diff --git a/ixwebsocket/IXWebSocketMessageQueue.h b/ixwebsocket/IXWebSocketMessageQueue.h index 961f57e2..543189e9 100644 --- a/ixwebsocket/IXWebSocketMessageQueue.h +++ b/ixwebsocket/IXWebSocketMessageQueue.h @@ -39,14 +39,14 @@ namespace ix WebSocketCloseInfo closeInfo; }; - using MessageDataPtr = std::shared_ptr; + using MessagePtr = std::shared_ptr; - MessageDataPtr popMessage(); + MessagePtr popMessage(); private: WebSocket* _websocket = nullptr; OnMessageCallback _onMessageUserCallback; std::mutex _messagesMutex; - std::list _messages; + std::list _messages; }; } From 78f59b42076f9ca061bc16bb7f6c94620468a4e0 Mon Sep 17 00:00:00 2001 From: dimon4eg Date: Sun, 12 May 2019 01:50:41 +0300 Subject: [PATCH 08/53] added message queue test --- test/CMakeLists.txt | 1 + test/IXWebSocketMessageQTest.cpp | 186 +++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 test/IXWebSocketMessageQTest.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4bf5b784..dd47a970 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -39,6 +39,7 @@ set (SOURCES IXWebSocketPingTest.cpp IXWebSocketTestConnectionDisconnection.cpp IXUrlParserTest.cpp + IXWebSocketMessageQTest.cpp ) # Some unittest don't work on windows yet diff --git a/test/IXWebSocketMessageQTest.cpp b/test/IXWebSocketMessageQTest.cpp new file mode 100644 index 00000000..837efe15 --- /dev/null +++ b/test/IXWebSocketMessageQTest.cpp @@ -0,0 +1,186 @@ +/* + * IXWebSocketServerTest.cpp + * Author: Korchynskyi Dmytro + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include +#include +#include + +#include "IXTest.h" +#include "catch.hpp" +#include + +using namespace ix; + +namespace +{ + bool startServer(ix::WebSocketServer& server) + { + server.setOnConnectionCallback( + [&server](std::shared_ptr webSocket, + std::shared_ptr connectionState) + { + webSocket->setOnMessageCallback( + [connectionState, &server](ix::WebSocketMessageType messageType, + const std::string & str, + size_t wireSize, + const ix::WebSocketErrorInfo & error, + const ix::WebSocketOpenInfo & openInfo, + const ix::WebSocketCloseInfo & closeInfo) + { + if (messageType == ix::WebSocketMessageType::Open) + { + Logger() << "New connection"; + connectionState->computeId(); + Logger() << "id: " << connectionState->getId(); + Logger() << "Uri: " << openInfo.uri; + Logger() << "Headers:"; + for (auto it : openInfo.headers) + { + Logger() << it.first << ": " << it.second; + } + } + else if (messageType == ix::WebSocketMessageType::Close) + { + Logger() << "Closed connection"; + } + else if (messageType == ix::WebSocketMessageType::Message) + { + Logger() << "Message received: " << str; + + for (auto&& client : server.getClients()) + { + client->send(str); + } + } + } + ); + } + ); + + auto res = server.listen(); + if (!res.first) + { + Logger() << res.second; + return false; + } + + server.start(); + return true; + } + + class MsgQTestClient + { + public: + MsgQTestClient() + { + msgQ.bindWebsocket(&ws); + + msgQ.setOnMessageCallback([this](WebSocketMessageType messageType, + const std::string & str, + size_t wireSize, + const WebSocketErrorInfo & error, + const WebSocketOpenInfo & openInfo, + const WebSocketCloseInfo & closeInfo) + { + REQUIRE(mainThreadId == std::this_thread::get_id()); + + std::stringstream ss; + if (messageType == WebSocketMessageType::Open) + { + log("client connected"); + sendNextMessage(); + } + else if (messageType == WebSocketMessageType::Close) + { + log("client disconnected"); + } + else if (messageType == WebSocketMessageType::Error) + { + ss << "Error ! " << error.reason; + log(ss.str()); + testDone = true; + } + else if (messageType == WebSocketMessageType::Pong) + { + ss << "Received pong message " << str; + log(ss.str()); + } + else if (messageType == WebSocketMessageType::Ping) + { + ss << "Received ping message " << str; + log(ss.str()); + } + else if (messageType == WebSocketMessageType::Message) + { + REQUIRE(str.compare("Hey dude!") == 0); + ++receivedCount; + ss << "Received message " << str; + log(ss.str()); + sendNextMessage(); + } + else + { + ss << "Invalid WebSocketMessageType"; + log(ss.str()); + testDone = true; + } + }); + } + + void sendNextMessage() + { + if (receivedCount >= 3) + { + testDone = true; + } + else + { + auto info = ws.sendText("Hey dude!"); + if (info.success) + log("sent message"); + else + log("send failed"); + } + } + + void run(const std::string& url) + { + mainThreadId = std::this_thread::get_id(); + testDone = false; + receivedCount = 0; + + ws.setUrl(url); + ws.start(); + + while (!testDone) + { + msgQ.poll(); + msleep(50); + } + } + + private: + WebSocket ws; + WebSocketMessageQueue msgQ; + bool testDone = false; + uint32_t receivedCount = 0; + std::thread::id mainThreadId; + }; +} + +TEST_CASE("Websocket_message_queue", "[websocket_message_q]") +{ + SECTION("Send several messages") + { + int port = getFreePort(); + WebSocketServer server(port); + REQUIRE(startServer(server)); + + MsgQTestClient testClient; + testClient.run("ws://127.0.0.1:" + std::to_string(port)); + } + +} From fc75b13faeea98da3dabc7b38c082bdf801d2bff Mon Sep 17 00:00:00 2001 From: dimon4eg Date: Sun, 12 May 2019 19:57:31 +0300 Subject: [PATCH 09/53] update test --- test/IXWebSocketMessageQTest.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/IXWebSocketMessageQTest.cpp b/test/IXWebSocketMessageQTest.cpp index 837efe15..034a2ac0 100644 --- a/test/IXWebSocketMessageQTest.cpp +++ b/test/IXWebSocketMessageQTest.cpp @@ -1,5 +1,5 @@ /* - * IXWebSocketServerTest.cpp + * IXWebSocketMessageQTest.cpp * Author: Korchynskyi Dmytro * Copyright (c) 2019 Machine Zone. All rights reserved. */ @@ -135,6 +135,7 @@ namespace if (receivedCount >= 3) { testDone = true; + succeeded = true; } else { @@ -162,12 +163,15 @@ namespace } } + bool isSucceeded() const { return succeeded; } + private: WebSocket ws; WebSocketMessageQueue msgQ; bool testDone = false; uint32_t receivedCount = 0; std::thread::id mainThreadId; + bool succeeded = false; }; } @@ -181,6 +185,7 @@ TEST_CASE("Websocket_message_queue", "[websocket_message_q]") MsgQTestClient testClient; testClient.run("ws://127.0.0.1:" + std::to_string(port)); + REQUIRE(testClient.isSucceeded()); } } From bb484414b15a755d63b59b8fa9558ee818862422 Mon Sep 17 00:00:00 2001 From: dimon4eg Date: Sun, 12 May 2019 20:00:15 +0300 Subject: [PATCH 10/53] update comment --- ixwebsocket/IXWebSocket.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ixwebsocket/IXWebSocket.h b/ixwebsocket/IXWebSocket.h index eda20141..283b9e16 100644 --- a/ixwebsocket/IXWebSocket.h +++ b/ixwebsocket/IXWebSocket.h @@ -116,6 +116,7 @@ namespace ix // Set callback to receive websocket messages. // Be aware: your callback will be executed from websocket's internal thread! + // To receive message events in your thread, look at WebSocketMessageQueue class void setOnMessageCallback(const OnMessageCallback& callback); static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback); From bf8abcbf4ada57b16f25ba6167905652aead79ac Mon Sep 17 00:00:00 2001 From: dimon4eg Date: Sun, 12 May 2019 20:05:28 +0300 Subject: [PATCH 11/53] fix warnings --- ixwebsocket/IXWebSocketMessageQueue.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ixwebsocket/IXWebSocketMessageQueue.cpp b/ixwebsocket/IXWebSocketMessageQueue.cpp index 6823b01c..e7ad967e 100644 --- a/ixwebsocket/IXWebSocketMessageQueue.cpp +++ b/ixwebsocket/IXWebSocketMessageQueue.cpp @@ -33,12 +33,12 @@ namespace ix { // set dummy callback just to avoid crash _websocket->setOnMessageCallback([]( - WebSocketMessageType type, - const std::string & str, - size_t wireSize, - const WebSocketErrorInfo & errorInfo, - const WebSocketOpenInfo & openInfo, - const WebSocketCloseInfo & closeInfo) + WebSocketMessageType, + const std::string &, + size_t, + const WebSocketErrorInfo &, + const WebSocketOpenInfo &, + const WebSocketCloseInfo &) {}); } From b24e4334f6f9d804874cf8afbf87d4d4c38e27fc Mon Sep 17 00:00:00 2001 From: dimon4eg Date: Sun, 12 May 2019 20:16:02 +0300 Subject: [PATCH 12/53] correct style --- ixwebsocket/IXWebSocketMessageQueue.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ixwebsocket/IXWebSocketMessageQueue.cpp b/ixwebsocket/IXWebSocketMessageQueue.cpp index e7ad967e..10a9da1f 100644 --- a/ixwebsocket/IXWebSocketMessageQueue.cpp +++ b/ixwebsocket/IXWebSocketMessageQueue.cpp @@ -34,11 +34,11 @@ namespace ix // set dummy callback just to avoid crash _websocket->setOnMessageCallback([]( WebSocketMessageType, - const std::string &, + const std::string&, size_t, - const WebSocketErrorInfo &, - const WebSocketOpenInfo &, - const WebSocketCloseInfo &) + const WebSocketErrorInfo&, + const WebSocketOpenInfo&, + const WebSocketCloseInfo&) {}); } From 6467f98241d26ddfbf06991b953fe66c43b19630 Mon Sep 17 00:00:00 2001 From: dimon4eg Date: Sun, 12 May 2019 20:59:18 +0300 Subject: [PATCH 13/53] add setOnMessageCallback with r-value --- ixwebsocket/IXWebSocketMessageQueue.cpp | 5 +++++ ixwebsocket/IXWebSocketMessageQueue.h | 1 + 2 files changed, 6 insertions(+) diff --git a/ixwebsocket/IXWebSocketMessageQueue.cpp b/ixwebsocket/IXWebSocketMessageQueue.cpp index 10a9da1f..64e9ddb6 100644 --- a/ixwebsocket/IXWebSocketMessageQueue.cpp +++ b/ixwebsocket/IXWebSocketMessageQueue.cpp @@ -76,6 +76,11 @@ namespace ix { _onMessageUserCallback = callback; } + + void WebSocketMessageQueue::setOnMessageCallback(OnMessageCallback&& callback) + { + _onMessageUserCallback = std::move(callback); + } WebSocketMessageQueue::MessagePtr WebSocketMessageQueue::popMessage() { diff --git a/ixwebsocket/IXWebSocketMessageQueue.h b/ixwebsocket/IXWebSocketMessageQueue.h index 543189e9..b8b85c25 100644 --- a/ixwebsocket/IXWebSocketMessageQueue.h +++ b/ixwebsocket/IXWebSocketMessageQueue.h @@ -25,6 +25,7 @@ namespace ix void bindWebsocket(WebSocket* websocket); void setOnMessageCallback(const OnMessageCallback& callback); + void setOnMessageCallback(OnMessageCallback&& callback); void poll(int count = 512); From d7a0bc212df884623c2a31555da70328e09dce99 Mon Sep 17 00:00:00 2001 From: Dimon4eg Date: Mon, 13 May 2019 04:37:22 +0300 Subject: [PATCH 14/53] Fix run.py (#71) * fix run.py * run.py: fix Windows support * fix test listing --- .gitignore | 1 + test/.gitignore | 7 +-- test/CMakeLists.txt | 10 +++-- test/run.py | 105 ++++++++++++++++++++++---------------------- 4 files changed, 63 insertions(+), 60 deletions(-) diff --git a/.gitignore b/.gitignore index 378eac25..6f97ca1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ build +*.pyc diff --git a/test/.gitignore b/test/.gitignore index 477ba279..910eaa65 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,9 +1,10 @@ CMakeCache.txt package-lock.json -CMakeFiles -ixwebsocket_unittest -cmake_install.cmake +CMakeFiles +ixwebsocket_unittest +cmake_install.cmake node_modules ixwebsocket Makefile build +ixwebsocket_unittest.xml diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dd47a970..677edd20 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -5,12 +5,11 @@ cmake_minimum_required (VERSION 3.4.1) project (ixwebsocket_unittest) -set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH}) -find_package(Sanitizers) - set (CMAKE_CXX_STANDARD 14) if (NOT WIN32) + set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH}) + find_package(Sanitizers) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") set(CMAKE_LD_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") option(USE_TLS "Add TLS support" ON) @@ -51,7 +50,10 @@ if (NOT WIN32) endif() add_executable(ixwebsocket_unittest ${SOURCES}) -add_sanitizers(ixwebsocket_unittest) + +if (NOT WIN32) + add_sanitizers(ixwebsocket_unittest) +endif() if (APPLE AND USE_TLS) target_link_libraries(ixwebsocket_unittest "-framework foundation" "-framework security") diff --git a/test/run.py b/test/run.py index 32234d42..30b441ce 100755 --- a/test/run.py +++ b/test/run.py @@ -28,9 +28,9 @@ try: except ImportError: hasClick = False - -DEFAULT_EXE = 'ixwebsocket_unittest' - +BUILD_TYPE = 'Debug' +XML_OUTPUT_FILE = 'ixwebsocket_unittest.xml' +TEST_EXE_PATH = None class Command(object): """Run system commands with timeout @@ -65,7 +65,7 @@ class Command(object): return True, self.process.returncode -def runCommand(cmd, assertOnFailure=True, timeout=None): +def runCommand(cmd, abortOnFailure=True, timeout=None): '''Small wrapper to run a command and make sure it succeed''' if timeout is None: @@ -73,16 +73,13 @@ def runCommand(cmd, assertOnFailure=True, timeout=None): print('\nRunning', cmd) command = Command(cmd) - timedout, ret = command.run(timeout) + succeed, ret = command.run(timeout) - if timedout: - print('Unittest timed out') - - msg = 'cmd {} failed with error code {}'.format(cmd, ret) - if ret != 0: + if not succeed or ret != 0: + msg = 'cmd {}\nfailed with error code {}'.format(cmd, ret) print(msg) - if assertOnFailure: - assert False + if abortOnFailure: + sys.exit(-1) def runCMake(sanitizer, buildDir): @@ -91,12 +88,6 @@ def runCMake(sanitizer, buildDir): (remove build sub-folder). ''' - # CMake installed via Self Service ends up here. - cmake_executable = '/Applications/CMake.app/Contents/bin/cmake' - - if not os.path.exists(cmake_executable): - cmake_executable = 'cmake' - sanitizersFlags = { 'asan': '-DSANITIZE_ADDRESS=On', 'ubsan': '-DSANITIZE_UNDEFINED=On', @@ -110,19 +101,22 @@ def runCMake(sanitizer, buildDir): if not os.path.exists(cmakeExecutable): cmakeExecutable = 'cmake' - generator = '"Unix Makefiles"' if platform.system() == 'Windows': - generator = '"NMake Makefiles"' + #generator = '"NMake Makefiles"' + generator = '"Visual Studio 16 2019"' + else: + generator = '"Unix Makefiles"' - fmt = ''' -{cmakeExecutable} -H. \ + CMAKE_BUILD_TYPE = BUILD_TYPE + + fmt = '{cmakeExecutable} -H. \ {sanitizerFlag} \ - -B{buildDir} \ - -DCMAKE_BUILD_TYPE=Debug \ + -B"{buildDir}" \ + -DCMAKE_BUILD_TYPE={CMAKE_BUILD_TYPE} \ -DUSE_TLS=1 \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - -G{generator} -''' + -G{generator}' + cmakeCmd = fmt.format(**locals()) runCommand(cmakeCmd) @@ -133,10 +127,10 @@ def runTest(args, buildDir, xmlOutput, testRunName): if args is None: args = '' - fmt = '{buildDir}/{DEFAULT_EXE} -o {xmlOutput} -n "{testRunName}" -r junit "{args}"' - testCommand = fmt.format(**locals()) + testCommand = '{} -o {} -n "{}" -r junit "{}"'.format(TEST_EXE_PATH, xmlOutput, testRunName, args) + runCommand(testCommand, - assertOnFailure=False) + abortOnFailure=False) def validateTestSuite(xmlOutput): @@ -296,8 +290,7 @@ def executeJobs(jobs): def computeAllTestNames(buildDir): '''Compute all test case names, by executing the unittest in a custom mode''' - executable = os.path.join(buildDir, DEFAULT_EXE) - cmd = '"{}" --list-test-names-only'.format(executable) + cmd = '"{}" --list-test-names-only'.format(TEST_EXE_PATH) names = os.popen(cmd).read().splitlines() names.sort() # Sort test names for execution determinism return names @@ -344,7 +337,7 @@ def generateXmlOutput(results, xmlOutput, testRunName, runTime): }) systemOut = ET.Element('system-out') - systemOut.text = result['output'].decode('utf-8') + systemOut.text = result['output'].decode('utf-8', 'ignore') testCase.append(systemOut) if not result['success']: @@ -365,16 +358,19 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD runCMake(sanitizer, buildDir) # build with make - makeCmd = 'make' - jobs = '-j8' + #makeCmd = 'cmake --build ' + #jobs = '-j8' - if platform.system() == 'Windows': - makeCmd = 'nmake' + #if platform.system() == 'Windows': + # makeCmd = 'nmake' # nmake does not have a -j option - jobs = '' + # jobs = '' - runCommand('{} -C {} {}'.format(makeCmd, buildDir, jobs)) + #runCommand('{} -C {} {}'.format(makeCmd, buildDir, jobs)) + + # build with cmake + runCommand('cmake --build ' + buildDir) if buildOnly: return @@ -409,12 +405,7 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD continue # testName can contains spaces, so we enclose them in double quotes - executable = os.path.join(buildDir, DEFAULT_EXE) - - if platform.system() == 'Windows': - executable += '.exe' - - cmd = '{} "{}" "{}" > "{}" 2>&1'.format(lldb, executable, testName, outputPath) + cmd = '{} "{}" "{}" > "{}" 2>&1'.format(lldb, TEST_EXE_PATH, testName, outputPath) jobs.append({ 'name': testName, @@ -454,8 +445,6 @@ def main(): if not os.path.exists(buildDir): os.makedirs(buildDir) - defaultOutput = DEFAULT_EXE + '.xml' - parser = argparse.ArgumentParser(description='Build and Run the engine unittest') sanitizers = ['tsan', 'asan', 'ubsan', 'none'] @@ -481,14 +470,29 @@ def main(): # Default sanitizer is tsan sanitizer = args.sanitizer - if args.sanitizer is None: + + if args.no_sanitizer: + sanitizer = 'none' + elif args.sanitizer is None: sanitizer = 'tsan' + # Sanitizers display lots of strange errors on Linux on CI, + # which looks like false positives + if platform.system() != 'Darwin': + sanitizer = 'none' + defaultRunName = 'ixengine_{}_{}'.format(platform.system(), sanitizer) - xmlOutput = args.output or defaultOutput + xmlOutput = args.output or XML_OUTPUT_FILE testRunName = args.run_name or os.getenv('IXENGINE_TEST_RUN_NAME') or defaultRunName + global TEST_EXE_PATH + + if platform.system() == 'Windows': + TEST_EXE_PATH = os.path.join(buildDir, BUILD_TYPE, 'ixwebsocket_unittest.exe') + else: + TEST_EXE_PATH = os.path.join(buildDir, 'ixwebsocket_unittest') + if args.list: # catch2 exit with a different error code when requesting the list of files try: @@ -505,11 +509,6 @@ def main(): print('LLDB is only supported on Apple at this point') args.lldb = False - # Sanitizers display lots of strange errors on Linux on CI, - # which looks like false positives - if platform.system() != 'Darwin': - sanitizer = 'none' - return run(args.test, buildDir, sanitizer, xmlOutput, testRunName, args.build_only, args.lldb) From e527ab1613814225df84c6ef7050793d7a1ec7b5 Mon Sep 17 00:00:00 2001 From: Dimon4eg Date: Mon, 13 May 2019 08:21:56 +0300 Subject: [PATCH 15/53] fix for Windows (#69) * fix for Windows * fix condition * make condition only on Windows --- ixwebsocket/IXSocket.cpp | 2 +- ixwebsocket/IXWebSocket.cpp | 3 +-- ixwebsocket/IXWebSocketTransport.cpp | 4 ++++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ixwebsocket/IXSocket.cpp b/ixwebsocket/IXSocket.cpp index e6a67fc2..e2794229 100644 --- a/ixwebsocket/IXSocket.cpp +++ b/ixwebsocket/IXSocket.cpp @@ -73,7 +73,7 @@ namespace ix struct timeval timeout; timeout.tv_sec = timeoutMs / 1000; - timeout.tv_usec = (timeoutMs < 1000) ? 0 : 1000 * (timeoutMs % 1000); + timeout.tv_usec = 1000 * (timeoutMs % 1000); // Compute the highest fd. int sockfd = _sockfd; diff --git a/ixwebsocket/IXWebSocket.cpp b/ixwebsocket/IXWebSocket.cpp index b6ab2c81..0449e694 100644 --- a/ixwebsocket/IXWebSocket.cpp +++ b/ixwebsocket/IXWebSocket.cpp @@ -223,7 +223,6 @@ namespace ix uint32_t retries = 0; millis duration; - ix::WebSocketInitResult status; // Try to connect perpertually while (true) @@ -249,7 +248,7 @@ namespace ix } // Try to connect synchronously - status = connect(_handshakeTimeoutSecs); + ix::WebSocketInitResult status = connect(_handshakeTimeoutSecs); if (!status.success) { diff --git a/ixwebsocket/IXWebSocketTransport.cpp b/ixwebsocket/IXWebSocketTransport.cpp index b8286f6a..503b0c91 100644 --- a/ixwebsocket/IXWebSocketTransport.cpp +++ b/ixwebsocket/IXWebSocketTransport.cpp @@ -303,6 +303,10 @@ namespace ix } } +#ifdef _WIN32 + if (lastingTimeoutDelayInMs <= 0) lastingTimeoutDelayInMs = 20; +#endif + // poll the socket PollResultType pollResult = _socket->poll(lastingTimeoutDelayInMs); From dad2b64e15a4aaac75ca62bc46dcfd0349fd103a Mon Sep 17 00:00:00 2001 From: Kumamon38 Date: Mon, 13 May 2019 18:08:46 +0200 Subject: [PATCH 16/53] save timepoints after connect and not in contructor, adjusted tests (#72) * save timepoints after connect and not in contructor, adjusted tests * move call into setReadyState * more time to detect client close in test --- ixwebsocket/IXWebSocketTransport.cpp | 26 +++++++++++++++++++++----- ixwebsocket/IXWebSocketTransport.h | 2 ++ test/IXWebSocketPingTest.cpp | 8 ++++---- test/IXWebSocketPingTimeoutTest.cpp | 6 +++--- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/ixwebsocket/IXWebSocketTransport.cpp b/ixwebsocket/IXWebSocketTransport.cpp index 503b0c91..415b6360 100644 --- a/ixwebsocket/IXWebSocketTransport.cpp +++ b/ixwebsocket/IXWebSocketTransport.cpp @@ -134,11 +134,6 @@ namespace ix { _pingIntervalOrTimeoutGCDSecs = pingIntervalSecs; } - - if (_pingIntervalOrTimeoutGCDSecs > 0) - { - _nextGCDTimePoint = std::chrono::steady_clock::now() + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs); - } } // Client @@ -225,6 +220,10 @@ namespace ix _closeWireSize = 0; _closeRemote = false; } + else if (readyState == ReadyState::OPEN) + { + initTimePointsAndGCDAfterConnect(); + } _readyState = readyState; } @@ -234,6 +233,23 @@ namespace ix _onCloseCallback = onCloseCallback; } + void WebSocketTransport::initTimePointsAndGCDAfterConnect() + { + { + std::lock_guard lock(_lastSendPingTimePointMutex); + _lastSendPingTimePoint = std::chrono::steady_clock::now(); + } + { + std::lock_guard lock(_lastReceivePongTimePointMutex); + _lastReceivePongTimePoint = std::chrono::steady_clock::now(); + } + + if (_pingIntervalOrTimeoutGCDSecs > 0) + { + _nextGCDTimePoint = std::chrono::steady_clock::now() + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs); + } + } + // Only consider send PING time points for that computation. bool WebSocketTransport::pingIntervalExceeded() { diff --git a/ixwebsocket/IXWebSocketTransport.h b/ixwebsocket/IXWebSocketTransport.h index db569967..22e87c45 100644 --- a/ixwebsocket/IXWebSocketTransport.h +++ b/ixwebsocket/IXWebSocketTransport.h @@ -220,6 +220,8 @@ namespace ix // after calling close(), if no CLOSE frame answer is received back from the remote, we should close the connexion bool closingDelayExceeded(); + void initTimePointsAndGCDAfterConnect(); + void sendCloseFrame(uint16_t code, const std::string& reason); void closeSocketAndSwitchToClosedState(uint16_t code, diff --git a/test/IXWebSocketPingTest.cpp b/test/IXWebSocketPingTest.cpp index c30a99bd..cfcfabdd 100644 --- a/test/IXWebSocketPingTest.cpp +++ b/test/IXWebSocketPingTest.cpp @@ -413,13 +413,13 @@ TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod REQUIRE(server.getClients().size() == 1); - ix::msleep(1850); + ix::msleep(1900); webSocketClient.stop(); // Here we test ping interval - // -> expected ping messages == 1 as 1850 seconds, 1 ping sent every second + // -> expected ping messages == 1 as 1900 seconds, 1 ping sent every second REQUIRE(serverReceivedPingMessages == 1); // Give us 500ms for the server to notice that clients went away @@ -460,7 +460,7 @@ TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]") webSocketClient.sendMessage("hello world"); ix::msleep(900); webSocketClient.sendMessage("hello world"); - ix::msleep(900); + ix::msleep(1100); webSocketClient.stop(); @@ -469,7 +469,7 @@ TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]") // Here we test ping interval // client has sent data, but ping should have been sent no matter what - // -> expected ping messages == 2 as 900+900+900 = 2700 seconds, 1 ping sent every second + // -> expected ping messages == 2 as 900+900+1100 = 2900 seconds, 1 ping sent every second REQUIRE(serverReceivedPingMessages == 2); // Give us 500ms for the server to notice that clients went away diff --git a/test/IXWebSocketPingTimeoutTest.cpp b/test/IXWebSocketPingTimeoutTest.cpp index 4b744b20..d7eaf8e0 100644 --- a/test/IXWebSocketPingTimeoutTest.cpp +++ b/test/IXWebSocketPingTimeoutTest.cpp @@ -350,7 +350,7 @@ TEST_CASE("Websocket_no_ping_but_timeout", "[setPingTimeout]") REQUIRE(server.getClients().size() == 1); - ix::msleep(2700); + ix::msleep(2900); // Here we test ping timeout, no timeout yet REQUIRE(serverReceivedPingMessages == 0); @@ -359,7 +359,7 @@ TEST_CASE("Websocket_no_ping_but_timeout", "[setPingTimeout]") REQUIRE(webSocketClient.isClosed() == false); REQUIRE(webSocketClient.closedDueToPingTimeout() == false); - ix::msleep(400); + ix::msleep(200); // Here we test ping timeout, timeout REQUIRE(serverReceivedPingMessages == 0); @@ -410,7 +410,7 @@ TEST_CASE("Websocket_ping_timeout", "[setPingTimeout]") REQUIRE(serverReceivedPingMessages == 1); REQUIRE(webSocketClient.getReceivedPongMessages() == 0); - ix::msleep(1000); + ix::msleep(1100); // Here we test ping timeout, timeout REQUIRE(serverReceivedPingMessages == 1); From 3190cd322d80a1ba8b3462899f7c6609d6e5db97 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Mon, 13 May 2019 16:51:58 -0700 Subject: [PATCH 17/53] Feature/windows ci (#76) * close with params * ... * different generator * core size = 1 * disable more tests to get something working on windows * try to enable another test on windows * enable all OS * set proper version of linux * another try * try again with just env variables * Revert "core size = 1" This reverts commit 29af74bba68df093158179e85039dc1580c3b68a. * add windows and mac * Revert "close with params" This reverts commit 6bb00b6788fa453e64f514220e1dedac553cea17. --- .travis.yml | 42 ++++++++++++++++++++++----------- docker/Dockerfile.ubuntu_xenial | 26 ++++++++++---------- test/CMakeLists.txt | 7 +++--- test/run.py | 3 ++- 4 files changed, 46 insertions(+), 32 deletions(-) diff --git a/.travis.yml b/.travis.yml index 634faf78..5c96ab62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,31 @@ -language: cpp -dist: xenial - -compiler: - - gcc - - clang -os: - - linux - - osx +language: bash matrix: - exclude: - # GCC fails on recent Travis OSX images. - - compiler: gcc - os: osx + include: + # macOS + - os: osx + compiler: clang + script: make test -script: python test/run.py + # Linux + - os: linux + dist: xenial + script: python test/run.py + env: + - CC=clang + - CXX=clang++ + + - os: linux + dist: xenial + script: python test/run.py + env: + - CC=clang + - CXX=clang++ + + # Windows + - os: windows + env: + - CMAKE_PATH="/c/Program Files/CMake/bin" + script: + - export PATH=$CMAKE_PATH:$PATH + - python test/run.py diff --git a/docker/Dockerfile.ubuntu_xenial b/docker/Dockerfile.ubuntu_xenial index e24fe498..1e87694d 100644 --- a/docker/Dockerfile.ubuntu_xenial +++ b/docker/Dockerfile.ubuntu_xenial @@ -1,24 +1,22 @@ -# Build time -FROM ubuntu:xenial as build +FROM fedora:30 as build -ENV DEBIAN_FRONTEND noninteractive -RUN apt-get update -RUN apt-get -y install wget +RUN yum install -y gcc-g++ +RUN yum install -y cmake +RUN yum install -y make +RUN yum install -y openssl-devel + +RUN yum install -y wget RUN mkdir -p /tmp/cmake WORKDIR /tmp/cmake RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz -RUN apt-get -y install g++ -RUN apt-get -y install libssl-dev -RUN apt-get -y install libz-dev -RUN apt-get -y install make -RUN apt-get -y install python - -COPY . . - ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin ENV PATH="${CMAKE_BIN_PATH}:${PATH}" -# RUN ["make"] +RUN yum install -y python +RUN yum install -y libtsan + +COPY . . RUN ["make", "test"] +# RUN ["make"] diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 677edd20..34bc285b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -31,20 +31,21 @@ set (SOURCES ../third_party/msgpack11/msgpack11.cpp ../ws/ixcore/utils/IXCoreLogger.cpp - IXDNSLookupTest.cpp IXSocketTest.cpp IXSocketConnectTest.cpp IXWebSocketServerTest.cpp - IXWebSocketPingTest.cpp IXWebSocketTestConnectionDisconnection.cpp IXUrlParserTest.cpp IXWebSocketMessageQTest.cpp + IXWebSocketServerTest.cpp ) # Some unittest don't work on windows yet -if (NOT WIN32) +if (UNIX) list(APPEND SOURCES IXWebSocketPingTimeoutTest.cpp + IXDNSLookupTest.cpp + IXWebSocketPingTest.cpp cmd_websocket_chat.cpp ) endif() diff --git a/test/run.py b/test/run.py index 30b441ce..b5898b53 100755 --- a/test/run.py +++ b/test/run.py @@ -103,7 +103,8 @@ def runCMake(sanitizer, buildDir): if platform.system() == 'Windows': #generator = '"NMake Makefiles"' - generator = '"Visual Studio 16 2019"' + #generator = '"Visual Studio 16 2019"' + generator = '"Visual Studio 15 2017"' else: generator = '"Unix Makefiles"' From a7ed4fe5c32defc4fb3d8169485919878f31e254 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Mon, 13 May 2019 17:01:22 -0700 Subject: [PATCH 18/53] disable ping tests for now as they are not super reliable --- test/CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 34bc285b..c850ccd4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -43,13 +43,15 @@ set (SOURCES # Some unittest don't work on windows yet if (UNIX) list(APPEND SOURCES - IXWebSocketPingTimeoutTest.cpp IXDNSLookupTest.cpp - IXWebSocketPingTest.cpp cmd_websocket_chat.cpp ) endif() +# Disable ping tests for now as they aren't super reliable +# IXWebSocketPingTest.cpp +# IXWebSocketPingTimeoutTest.cpp + add_executable(ixwebsocket_unittest ${SOURCES}) if (NOT WIN32) From 53ceab9f9166ae3a755a72a7269dc87a2a1946fb Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Mon, 13 May 2019 17:06:56 -0700 Subject: [PATCH 19/53] build in parallel + stop building linux + clang --- .travis.yml | 13 +++++++------ test/run.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5c96ab62..4e88007b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,12 +15,13 @@ matrix: - CC=clang - CXX=clang++ - - os: linux - dist: xenial - script: python test/run.py - env: - - CC=clang - - CXX=clang++ + # Clang + Linux disabled for now + # - os: linux + # dist: xenial + # script: python test/run.py + # env: + # - CC=clang + # - CXX=clang++ # Windows - os: windows diff --git a/test/run.py b/test/run.py index b5898b53..27581e30 100755 --- a/test/run.py +++ b/test/run.py @@ -371,7 +371,7 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD #runCommand('{} -C {} {}'.format(makeCmd, buildDir, jobs)) # build with cmake - runCommand('cmake --build ' + buildDir) + runCommand('cmake --build --parallel ' + buildDir) if buildOnly: return From 819e9025b1e154f86af0ad2c3d96d457fc8e7228 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Mon, 13 May 2019 17:17:23 -0700 Subject: [PATCH 20/53] travis cmake version on macOS does not know --parallel option --- test/run.py | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/test/run.py b/test/run.py index 27581e30..7a14eeca 100755 --- a/test/run.py +++ b/test/run.py @@ -1,10 +1,4 @@ #!/usr/bin/env python2.7 -''' -Windows notes: - generator = '-G"NMake Makefiles"' - make = 'nmake' - testBinary ='ixwebsocket_unittest.exe' -''' from __future__ import print_function @@ -358,20 +352,12 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD # gen build files with CMake runCMake(sanitizer, buildDir) - # build with make - #makeCmd = 'cmake --build ' - #jobs = '-j8' - - #if platform.system() == 'Windows': - # makeCmd = 'nmake' - - # nmake does not have a -j option - # jobs = '' - - #runCommand('{} -C {} {}'.format(makeCmd, buildDir, jobs)) - - # build with cmake - runCommand('cmake --build --parallel ' + buildDir) + if platform.system() != 'Darwin': + # build with make + runCommand('make -C {} -j8'.format(buildDir)) + else: + # build with cmake on recent + runCommand('cmake --build --parallel {}'.format(buildDir)) if buildOnly: return From 9217b27d401bc6ba7a05fbb5e9ef2b1c4562e874 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Mon, 13 May 2019 12:20:03 -0700 Subject: [PATCH 21/53] server code / add dedicated thread to close/join terminated connection threads --- ixwebsocket/IXSocketServer.cpp | 71 +++++++++++++++++++++++----------- ixwebsocket/IXSocketServer.h | 11 ++++-- 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/ixwebsocket/IXSocketServer.cpp b/ixwebsocket/IXSocketServer.cpp index 31da31c2..a941371c 100644 --- a/ixwebsocket/IXSocketServer.cpp +++ b/ixwebsocket/IXSocketServer.cpp @@ -30,7 +30,9 @@ namespace ix _host(host), _backlog(backlog), _maxConnections(maxConnections), + _serverFd(-1), _stop(false), + _stopGc(false), _connectionStateFactory(&ConnectionState::createConnectionState) { @@ -124,9 +126,15 @@ namespace ix void SocketServer::start() { - if (_thread.joinable()) return; // we've already been started + if (!_thread.joinable()) + { + _thread = std::thread(&SocketServer::run, this); + } - _thread = std::thread(&SocketServer::run, this); + if (!_gcThread.joinable()) + { + _gcThread = std::thread(&SocketServer::runGC, this); + } } void SocketServer::wait() @@ -142,21 +150,21 @@ namespace ix void SocketServer::stop() { - while (true) + // Stop accepting connections, and close the 'accept' thread + if (_thread.joinable()) { - if (closeTerminatedThreads()) break; - - // wait 10ms and try again later. - // we could have a timeout, but if we exit of here - // we leaked threads, it is quite bad. - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + _stop = true; + _thread.join(); + _stop = false; } - if (!_thread.joinable()) return; // nothing to do - - _stop = true; - _thread.join(); - _stop = false; + // Join all threads and make sure that all connections are terminated + if (_gcThread.joinable()) + { + _stopGc = true; + _gcThread.join(); + _stopGc = false; + } _conditionVariable.notify_one(); Socket::closeSocket(_serverFd); @@ -175,7 +183,7 @@ namespace ix // field becomes true, and we can use that to know that we can join that thread // and remove it from our _connectionsThreads data structure (a list). // - bool SocketServer::closeTerminatedThreads() + void SocketServer::closeTerminatedThreads() { std::lock_guard lock(_connectionsThreadsMutex); auto it = _connectionsThreads.begin(); @@ -195,8 +203,6 @@ namespace ix if (thread.joinable()) thread.join(); it = _connectionsThreads.erase(it); } - - return _connectionsThreads.empty(); } void SocketServer::run() @@ -208,12 +214,6 @@ namespace ix { if (_stop) return; - // Garbage collection to shutdown/join threads for closed connections. - // We could run this in its own thread, so that we dont need to accept - // a new connection to close a thread. - // We could also use a condition variable to be notify when we need to do this - closeTerminatedThreads(); - // Use select to check whether a new connection is in progress fd_set rfds; struct timeval timeout; @@ -290,5 +290,30 @@ namespace ix connectionState))); } } + + size_t SocketServer::getConnectionsThreadsCount() + { + std::lock_guard lock(_connectionsThreadsMutex); + return _connectionsThreads.size(); + } + + void SocketServer::runGC() + { + for (;;) + { + // Garbage collection to shutdown/join threads for closed connections. + closeTerminatedThreads(); + + // We quit this thread if all connections are closed and we received + // a stop request by setting _stopGc to true. + if (_stopGc && getConnectionsThreadsCount() == 0) + { + break; + } + + // Sleep a little bit then keep cleaning up + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } } diff --git a/ixwebsocket/IXSocketServer.h b/ixwebsocket/IXSocketServer.h index d5dfb492..0472e93c 100644 --- a/ixwebsocket/IXSocketServer.h +++ b/ixwebsocket/IXSocketServer.h @@ -74,6 +74,12 @@ namespace ix // background thread to wait for incoming connections std::atomic _stop; std::thread _thread; + void run(); + + // background thread to cleanup (join) terminated threads + std::atomic _stopGc; + std::thread _gcThread; + void runGC(); // the list of (connectionState, threads) for each connections ConnectionThreads _connectionsThreads; @@ -87,13 +93,12 @@ namespace ix // the factory to create ConnectionState objects ConnectionStateFactory _connectionStateFactory; - // Methods - void run(); virtual void handleConnection(int fd, std::shared_ptr connectionState) = 0; virtual size_t getConnectedClientsCount() = 0; // Returns true if all connection threads are joined - bool closeTerminatedThreads(); + void closeTerminatedThreads(); + size_t getConnectionsThreadsCount(); }; } From 7ccd9e1709a6f8a211842a7ce62c605ab4aea430 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Mon, 13 May 2019 17:18:07 -0700 Subject: [PATCH 22/53] fix inverted conditional --- test/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/run.py b/test/run.py index 7a14eeca..9f789cae 100755 --- a/test/run.py +++ b/test/run.py @@ -352,7 +352,7 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD # gen build files with CMake runCMake(sanitizer, buildDir) - if platform.system() != 'Darwin': + if platform.system() == 'Darwin': # build with make runCommand('make -C {} -j8'.format(buildDir)) else: From f8b547c028e69c3492269bcf1f731afc01f163d8 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Mon, 13 May 2019 17:32:57 -0700 Subject: [PATCH 23/53] use spdlog for logging in the unittest --- test/IXTest.h | 6 ++++-- test/IXWebSocketTestConnectionDisconnection.cpp | 14 +++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/test/IXTest.h b/test/IXTest.h index 177b83c0..adf263af 100644 --- a/test/IXTest.h +++ b/test/IXTest.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace ix { @@ -32,8 +33,9 @@ namespace ix { std::lock_guard lock(_mutex); - std::cerr << obj; - std::cerr << std::endl; + std::stringstream ss; + ss << obj; + spdlog::info(ss.str()); return *this; } diff --git a/test/IXWebSocketTestConnectionDisconnection.cpp b/test/IXWebSocketTestConnectionDisconnection.cpp index 003f60d0..974f9de0 100644 --- a/test/IXWebSocketTestConnectionDisconnection.cpp +++ b/test/IXWebSocketTestConnectionDisconnection.cpp @@ -62,33 +62,33 @@ namespace std::stringstream ss; if (messageType == ix::WebSocketMessageType::Open) { - log("cmd_websocket_satori_chat: connected !"); + log("TestConnectionDisconnection: connected !"); } else if (messageType == ix::WebSocketMessageType::Close) { - log("cmd_websocket_satori_chat: disconnected !"); + log("TestConnectionDisconnection: disconnected !"); } else if (messageType == ix::WebSocketMessageType::Error) { - ss << "cmd_websocket_satori_chat: Error! "; + ss << "TestConnectionDisconnection: Error! "; ss << error.reason; log(ss.str()); } else if (messageType == ix::WebSocketMessageType::Message) { - log("cmd_websocket_satori_chat: received message.!"); + log("TestConnectionDisconnection: received message.!"); } else if (messageType == ix::WebSocketMessageType::Ping) { - log("cmd_websocket_satori_chat: received ping message.!"); + log("TestConnectionDisconnection: received ping message.!"); } else if (messageType == ix::WebSocketMessageType::Pong) { - log("cmd_websocket_satori_chat: received pong message.!"); + log("TestConnectionDisconnection: received pong message.!"); } else if (messageType == ix::WebSocketMessageType::Fragment) { - log("cmd_websocket_satori_chat: received fragment.!"); + log("TestConnectionDisconnection: received fragment.!"); } else { From 76172f92e97e6ca1efac6d6cee8ba5029c4abdb8 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Mon, 13 May 2019 17:35:21 -0700 Subject: [PATCH 24/53] build with gcc on Linux --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4e88007b..1f6e4ba6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,8 +12,8 @@ matrix: dist: xenial script: python test/run.py env: - - CC=clang - - CXX=clang++ + - CC=gcc + - CXX=g++ # Clang + Linux disabled for now # - os: linux From 5741b2f6c16f23c15594d9fa8ee2f8b48e6ea3ea Mon Sep 17 00:00:00 2001 From: Kumamon38 Date: Tue, 14 May 2019 06:26:34 +0200 Subject: [PATCH 25/53] add more time to let client close (#73) --- test/IXWebSocketPingTimeoutTest.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/IXWebSocketPingTimeoutTest.cpp b/test/IXWebSocketPingTimeoutTest.cpp index d7eaf8e0..0b449427 100644 --- a/test/IXWebSocketPingTimeoutTest.cpp +++ b/test/IXWebSocketPingTimeoutTest.cpp @@ -359,12 +359,13 @@ TEST_CASE("Websocket_no_ping_but_timeout", "[setPingTimeout]") REQUIRE(webSocketClient.isClosed() == false); REQUIRE(webSocketClient.closedDueToPingTimeout() == false); - ix::msleep(200); + ix::msleep(300); // Here we test ping timeout, timeout REQUIRE(serverReceivedPingMessages == 0); REQUIRE(webSocketClient.getReceivedPongMessages() == 0); - // Ensure client close was not by ping timeout + // Ensure client close was by ping timeout + ix::msleep(300); REQUIRE(webSocketClient.isClosed() == true); REQUIRE(webSocketClient.closedDueToPingTimeout() == true); @@ -415,7 +416,8 @@ TEST_CASE("Websocket_ping_timeout", "[setPingTimeout]") // Here we test ping timeout, timeout REQUIRE(serverReceivedPingMessages == 1); REQUIRE(webSocketClient.getReceivedPongMessages() == 0); - // Ensure client close was not by ping timeout + // Ensure client close was by ping timeout + ix::msleep(300); REQUIRE(webSocketClient.isClosed() == true); REQUIRE(webSocketClient.closedDueToPingTimeout() == true); From 4e5e7ae50ac3ba4e3b75ffb6119ec2a94945bac9 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Mon, 13 May 2019 21:29:36 -0700 Subject: [PATCH 26/53] fix cast warning caught on windows --- ixwebsocket/IXSocket.cpp | 2 +- ixwebsocket/IXSocket.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ixwebsocket/IXSocket.cpp b/ixwebsocket/IXSocket.cpp index e2794229..e855f125 100644 --- a/ixwebsocket/IXSocket.cpp +++ b/ixwebsocket/IXSocket.cpp @@ -129,7 +129,7 @@ namespace ix } // Wake up from poll/select by writing to the pipe which is watched by select - bool Socket::wakeUpFromPoll(uint8_t wakeUpCode) + bool Socket::wakeUpFromPoll(uint64_t wakeUpCode) { return _selectInterrupt->notify(wakeUpCode); } diff --git a/ixwebsocket/IXSocket.h b/ixwebsocket/IXSocket.h index 288ac4d2..e2c0da5e 100644 --- a/ixwebsocket/IXSocket.h +++ b/ixwebsocket/IXSocket.h @@ -57,7 +57,7 @@ namespace ix // Functions to check whether there is activity on the socket PollResultType poll(int timeoutMs = kDefaultPollTimeout); - bool wakeUpFromPoll(uint8_t wakeUpCode); + bool wakeUpFromPoll(uint64_t wakeUpCode); PollResultType isReadyToWrite(int timeoutMs); PollResultType isReadyToRead(int timeoutMs); From 7714bdf7e07f5e03b49e61e7182bd3843dc024cb Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Mon, 13 May 2019 21:35:34 -0700 Subject: [PATCH 27/53] Revert "fix cast warning caught on windows" This reverts commit 4edb7447df0c705e9c393da390de6a9980b3fe09. --- ixwebsocket/IXSocket.cpp | 2 +- ixwebsocket/IXSocket.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ixwebsocket/IXSocket.cpp b/ixwebsocket/IXSocket.cpp index e855f125..e2794229 100644 --- a/ixwebsocket/IXSocket.cpp +++ b/ixwebsocket/IXSocket.cpp @@ -129,7 +129,7 @@ namespace ix } // Wake up from poll/select by writing to the pipe which is watched by select - bool Socket::wakeUpFromPoll(uint64_t wakeUpCode) + bool Socket::wakeUpFromPoll(uint8_t wakeUpCode) { return _selectInterrupt->notify(wakeUpCode); } diff --git a/ixwebsocket/IXSocket.h b/ixwebsocket/IXSocket.h index e2c0da5e..288ac4d2 100644 --- a/ixwebsocket/IXSocket.h +++ b/ixwebsocket/IXSocket.h @@ -57,7 +57,7 @@ namespace ix // Functions to check whether there is activity on the socket PollResultType poll(int timeoutMs = kDefaultPollTimeout); - bool wakeUpFromPoll(uint64_t wakeUpCode); + bool wakeUpFromPoll(uint8_t wakeUpCode); PollResultType isReadyToWrite(int timeoutMs); PollResultType isReadyToRead(int timeoutMs); From 0ba127e4476f2003df4677fbc94ed141d93b610f Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Mon, 13 May 2019 22:16:49 -0700 Subject: [PATCH 28/53] Revert "Revert "fix cast warning caught on windows"" This reverts commit 25eaf730bc93478393dea57b17b32cda0a9b6a60. --- ixwebsocket/IXSocket.cpp | 2 +- ixwebsocket/IXSocket.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ixwebsocket/IXSocket.cpp b/ixwebsocket/IXSocket.cpp index e2794229..e855f125 100644 --- a/ixwebsocket/IXSocket.cpp +++ b/ixwebsocket/IXSocket.cpp @@ -129,7 +129,7 @@ namespace ix } // Wake up from poll/select by writing to the pipe which is watched by select - bool Socket::wakeUpFromPoll(uint8_t wakeUpCode) + bool Socket::wakeUpFromPoll(uint64_t wakeUpCode) { return _selectInterrupt->notify(wakeUpCode); } diff --git a/ixwebsocket/IXSocket.h b/ixwebsocket/IXSocket.h index 288ac4d2..e2c0da5e 100644 --- a/ixwebsocket/IXSocket.h +++ b/ixwebsocket/IXSocket.h @@ -57,7 +57,7 @@ namespace ix // Functions to check whether there is activity on the socket PollResultType poll(int timeoutMs = kDefaultPollTimeout); - bool wakeUpFromPoll(uint8_t wakeUpCode); + bool wakeUpFromPoll(uint64_t wakeUpCode); PollResultType isReadyToWrite(int timeoutMs); PollResultType isReadyToRead(int timeoutMs); From 2a69038c4caaf651a905d068f683972989847b08 Mon Sep 17 00:00:00 2001 From: Dimon4eg Date: Tue, 14 May 2019 21:26:37 +0300 Subject: [PATCH 29/53] add isEnabledAutomaticReconnection (#75) * add isEnabledAutomaticReconnection * test isEnabledAutomaticReconnection * rename --- ixwebsocket/IXWebSocket.cpp | 5 +++++ ixwebsocket/IXWebSocket.h | 1 + test/IXWebSocketTestConnectionDisconnection.cpp | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/ixwebsocket/IXWebSocket.cpp b/ixwebsocket/IXWebSocket.cpp index 0449e694..1146e00d 100644 --- a/ixwebsocket/IXWebSocket.cpp +++ b/ixwebsocket/IXWebSocket.cpp @@ -458,6 +458,11 @@ namespace ix _automaticReconnection = false; } + bool WebSocket::isAutomaticReconnectionEnabled() const + { + return _automaticReconnection; + } + size_t WebSocket::bufferedAmount() const { return _ws.bufferedAmount(); diff --git a/ixwebsocket/IXWebSocket.h b/ixwebsocket/IXWebSocket.h index 283b9e16..59e649d6 100644 --- a/ixwebsocket/IXWebSocket.h +++ b/ixwebsocket/IXWebSocket.h @@ -134,6 +134,7 @@ namespace ix void enableAutomaticReconnection(); void disableAutomaticReconnection(); + bool isAutomaticReconnectionEnabled() const; private: diff --git a/test/IXWebSocketTestConnectionDisconnection.cpp b/test/IXWebSocketTestConnectionDisconnection.cpp index 974f9de0..d06c30c2 100644 --- a/test/IXWebSocketTestConnectionDisconnection.cpp +++ b/test/IXWebSocketTestConnectionDisconnection.cpp @@ -96,6 +96,12 @@ namespace } }); + _webSocket.enableAutomaticReconnection(); + REQUIRE(_webSocket.isAutomaticReconnectionEnabled() == true); + + _webSocket.disableAutomaticReconnection(); + REQUIRE(_webSocket.isAutomaticReconnectionEnabled() == false); + // Start the connection _webSocket.start(); } From 570fa01c0493fc7755ecb9854b90bdd83d36edb6 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Fri, 10 May 2019 20:47:13 -0700 Subject: [PATCH 30/53] close and stop with code and reason + docker = ubuntu xenial --- Dockerfile | 2 +- ixwebsocket/IXWebSocket.cpp | 10 +- ixwebsocket/IXWebSocket.h | 8 +- test/CMakeLists.txt | 1 + test/IXWebSocketCloseTest.cpp | 406 ++++++++++++++++++++++++++++++++++ 5 files changed, 420 insertions(+), 7 deletions(-) create mode 100644 test/IXWebSocketCloseTest.cpp diff --git a/Dockerfile b/Dockerfile index d81c7222..cd238cbd 120000 --- a/Dockerfile +++ b/Dockerfile @@ -1 +1 @@ -docker/Dockerfile.fedora \ No newline at end of file +docker/Dockerfile.ubuntu_xenial \ No newline at end of file diff --git a/ixwebsocket/IXWebSocket.cpp b/ixwebsocket/IXWebSocket.cpp index 1146e00d..c4a537ca 100644 --- a/ixwebsocket/IXWebSocket.cpp +++ b/ixwebsocket/IXWebSocket.cpp @@ -142,9 +142,10 @@ namespace ix _thread = std::thread(&WebSocket::run, this); } - void WebSocket::stop() + void WebSocket::stop(uint16_t code, + const std::string& reason) { - close(); + close(code, reason); if (_thread.joinable()) { @@ -212,9 +213,10 @@ namespace ix return getReadyState() == ReadyState::Closing; } - void WebSocket::close() + void WebSocket::close(uint16_t code, + const std::string& reason) { - _ws.close(); + _ws.close(code, reason); } void WebSocket::checkConnection(bool firstConnectionAttempt) diff --git a/ixwebsocket/IXWebSocket.h b/ixwebsocket/IXWebSocket.h index 59e649d6..00aeb880 100644 --- a/ixwebsocket/IXWebSocket.h +++ b/ixwebsocket/IXWebSocket.h @@ -99,8 +99,10 @@ namespace ix // Run asynchronously, by calling start and stop. void start(); + // stop is synchronous - void stop(); + void stop(uint16_t code = 1000, + const std::string& reason = "Normal closure"); // Run in blocking mode, by connecting first manually, and then calling run. WebSocketInitResult connect(int timeoutSecs); @@ -112,7 +114,9 @@ namespace ix WebSocketSendInfo sendText(const std::string& text, const OnProgressCallback& onProgressCallback = nullptr); WebSocketSendInfo ping(const std::string& text); - void close(); + + void close(uint16_t code = 1000, + const std::string& reason = "Normal closure"); // Set callback to receive websocket messages. // Be aware: your callback will be executed from websocket's internal thread! diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c850ccd4..fc8cded4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -38,6 +38,7 @@ set (SOURCES IXUrlParserTest.cpp IXWebSocketMessageQTest.cpp IXWebSocketServerTest.cpp + IXWebSocketCloseTest.cpp ) # Some unittest don't work on windows yet diff --git a/test/IXWebSocketCloseTest.cpp b/test/IXWebSocketCloseTest.cpp new file mode 100644 index 00000000..219463e1 --- /dev/null +++ b/test/IXWebSocketCloseTest.cpp @@ -0,0 +1,406 @@ +/* + * IXWebSocketCloseTest.cpp + * Author: Alexandre Konieczny + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include +#include +#include +#include +#include + +#include "IXTest.h" + +#include "catch.hpp" + +using namespace ix; + +namespace +{ + class WebSocketClient + { + public: + WebSocketClient(int port); + + void subscribe(const std::string& channel); + void start(); + void stop(); + void stop(uint16_t code, const std::string& reason); + bool isReady() const; + void sendMessage(const std::string& text); + + uint16_t getCloseCode(); + const std::string& getCloseReason(); + bool getCloseRemote(); + + private: + ix::WebSocket _webSocket; + int _port; + + mutable std::mutex _mutexCloseData; + uint16_t _closeCode; + std::string _closeReason; + bool _closeRemote; + }; + + WebSocketClient::WebSocketClient(int port) + : _port(port) + , _closeCode(0) + , _closeReason(std::string("")) + , _closeRemote(false) + { + ; + } + + bool WebSocketClient::isReady() const + { + return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open; + } + + uint16_t WebSocketClient::getCloseCode() + { + std::lock_guard lck(_mutexCloseData); + + return _closeCode; + } + + const std::string& WebSocketClient::getCloseReason() + { + std::lock_guard lck(_mutexCloseData); + + return _closeReason; + } + + bool WebSocketClient::getCloseRemote() + { + std::lock_guard lck(_mutexCloseData); + + return _closeRemote; + } + + void WebSocketClient::stop() + { + _webSocket.stop(); + } + + void WebSocketClient::stop(uint16_t code, const std::string& reason) + { + _webSocket.stop(code, reason); + } + + void WebSocketClient::start() + { + std::string url; + { + std::stringstream ss; + ss << "ws://localhost:" + << _port + << "/"; + + url = ss.str(); + } + + _webSocket.setUrl(url); + + std::stringstream ss; + log(std::string("Connecting to url: ") + url); + + _webSocket.setOnMessageCallback( + [this](ix::WebSocketMessageType messageType, + const std::string& str, + size_t wireSize, + const ix::WebSocketErrorInfo& error, + const ix::WebSocketOpenInfo& openInfo, + const ix::WebSocketCloseInfo& closeInfo) + { + std::stringstream ss; + if (messageType == ix::WebSocket_MessageType_Open) + { + log("client connected"); + + _webSocket.disableAutomaticReconnection(); + } + else if (messageType == ix::WebSocket_MessageType_Close) + { + log("client disconnected"); + + std::lock_guard lck(_mutexCloseData); + + _closeCode = closeInfo.code; + _closeReason = std::string(closeInfo.reason); + _closeRemote = closeInfo.remote; + + _webSocket.disableAutomaticReconnection(); + } + else if (messageType == ix::WebSocket_MessageType_Error) + { + ss << "Error ! " << error.reason; + log(ss.str()); + + _webSocket.disableAutomaticReconnection(); + } + else if (messageType == ix::WebSocket_MessageType_Pong) + { + ss << "Received pong message " << str; + log(ss.str()); + } + else if (messageType == ix::WebSocket_MessageType_Ping) + { + ss << "Received ping message " << str; + log(ss.str()); + } + else if (messageType == ix::WebSocket_MessageType_Message) + { + ss << "Received message " << str; + log(ss.str()); + } + else + { + ss << "Invalid ix::WebSocketMessageType"; + log(ss.str()); + } + }); + + _webSocket.start(); + } + + void WebSocketClient::sendMessage(const std::string& text) + { + _webSocket.send(text); + } + + bool startServer(ix::WebSocketServer& server, + uint16_t& receivedCloseCode, + std::string& receivedCloseReason, + bool& receivedCloseRemote, + std::mutex& mutexWrite) + { + // A dev/null server + server.setOnConnectionCallback( + [&server, &receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](std::shared_ptr webSocket, + std::shared_ptr connectionState) + { + webSocket->setOnMessageCallback( + [webSocket, connectionState, &server, &receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](ix::WebSocketMessageType messageType, + const std::string& str, + size_t wireSize, + const ix::WebSocketErrorInfo& error, + const ix::WebSocketOpenInfo& openInfo, + const ix::WebSocketCloseInfo& closeInfo) + { + if (messageType == ix::WebSocket_MessageType_Open) + { + Logger() << "New server connection"; + Logger() << "id: " << connectionState->getId(); + Logger() << "Uri: " << openInfo.uri; + Logger() << "Headers:"; + for (auto it : openInfo.headers) + { + Logger() << it.first << ": " << it.second; + } + } + else if (messageType == ix::WebSocket_MessageType_Close) + { + log("Server closed connection"); + + //Logger() << closeInfo.code; + //Logger() << closeInfo.reason; + //Logger() << closeInfo.remote; + + std::lock_guard lck(mutexWrite); + + receivedCloseCode = closeInfo.code; + receivedCloseReason = std::string(closeInfo.reason); + receivedCloseRemote = closeInfo.remote; + } + } + ); + } + ); + + auto res = server.listen(); + if (!res.first) + { + log(res.second); + return false; + } + + server.start(); + return true; + } +} + +TEST_CASE("Websocket_client_close_default", "[close]") +{ + SECTION("Make sure that close code and reason was used and sent to server.") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + + uint16_t serverReceivedCloseCode(0); + bool serverReceivedCloseRemote(false); + std::string serverReceivedCloseReason(""); + std::mutex mutexWrite; + + REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(100); + + webSocketClient.stop(); + + ix::msleep(200); + + // ensure client close is the same as values given + REQUIRE(webSocketClient.getCloseCode() == 1000); + REQUIRE(webSocketClient.getCloseReason() == "Normal closure"); + REQUIRE(webSocketClient.getCloseRemote() == false); + + { + std::lock_guard lck(mutexWrite); + + // Here we read the code/reason received by the server, and ensure that remote is true + REQUIRE(serverReceivedCloseCode == 1000); + REQUIRE(serverReceivedCloseReason == "Normal closure"); + REQUIRE(serverReceivedCloseRemote == true); + } + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_client_close_params_given", "[close]") +{ + SECTION("Make sure that close code and reason was used and sent to server.") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + + uint16_t serverReceivedCloseCode(0); + bool serverReceivedCloseRemote(false); + std::string serverReceivedCloseReason(""); + std::mutex mutexWrite; + + REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(100); + + webSocketClient.stop(4000, "My reason"); + + ix::msleep(500); + + // ensure client close is the same as values given + REQUIRE(webSocketClient.getCloseCode() == 4000); + REQUIRE(webSocketClient.getCloseReason() == "My reason"); + REQUIRE(webSocketClient.getCloseRemote() == false); + + { + std::lock_guard lck(mutexWrite); + + // Here we read the code/reason received by the server, and ensure that remote is true + REQUIRE(serverReceivedCloseCode == 4000); + REQUIRE(serverReceivedCloseReason == "My reason"); + REQUIRE(serverReceivedCloseRemote == true); + } + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_server_close", "[close]") +{ + SECTION("Make sure that close code and reason was read from server.") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + + uint16_t serverReceivedCloseCode(0); + bool serverReceivedCloseRemote(false); + std::string serverReceivedCloseReason(""); + std::mutex mutexWrite; + + REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(200); + + server.stop(); + + ix::msleep(500); + + // ensure client close is the same as values given + REQUIRE(webSocketClient.getCloseCode() == 1000); + REQUIRE(webSocketClient.getCloseReason() == "Normal closure"); + REQUIRE(webSocketClient.getCloseRemote() == true); + + { + std::lock_guard lck(mutexWrite); + + // Here we read the code/reason received by the server, and ensure that remote is true + REQUIRE(serverReceivedCloseCode == 1000); + REQUIRE(serverReceivedCloseReason == "Normal closure"); + REQUIRE(serverReceivedCloseRemote == false); + } + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} From 5af3096070f09017f3eff73e972b75b9de843dd8 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 15 May 2019 13:56:42 -0700 Subject: [PATCH 31/53] fix compile errors with C++ enum class --- test/IXWebSocketCloseTest.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/IXWebSocketCloseTest.cpp b/test/IXWebSocketCloseTest.cpp index 219463e1..1764b78a 100644 --- a/test/IXWebSocketCloseTest.cpp +++ b/test/IXWebSocketCloseTest.cpp @@ -55,7 +55,7 @@ namespace bool WebSocketClient::isReady() const { - return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open; + return _webSocket.getReadyState() == ix::ReadyState::Open; } uint16_t WebSocketClient::getCloseCode() @@ -115,13 +115,13 @@ namespace const ix::WebSocketCloseInfo& closeInfo) { std::stringstream ss; - if (messageType == ix::WebSocket_MessageType_Open) + if (messageType == ix::WebSocketMessageType::Open) { log("client connected"); _webSocket.disableAutomaticReconnection(); } - else if (messageType == ix::WebSocket_MessageType_Close) + else if (messageType == ix::WebSocketMessageType::Close) { log("client disconnected"); @@ -133,24 +133,24 @@ namespace _webSocket.disableAutomaticReconnection(); } - else if (messageType == ix::WebSocket_MessageType_Error) + else if (messageType == ix::WebSocketMessageType::Error) { ss << "Error ! " << error.reason; log(ss.str()); _webSocket.disableAutomaticReconnection(); } - else if (messageType == ix::WebSocket_MessageType_Pong) + else if (messageType == ix::WebSocketMessageType::Pong) { ss << "Received pong message " << str; log(ss.str()); } - else if (messageType == ix::WebSocket_MessageType_Ping) + else if (messageType == ix::WebSocketMessageType::Ping) { ss << "Received ping message " << str; log(ss.str()); } - else if (messageType == ix::WebSocket_MessageType_Message) + else if (messageType == ix::WebSocketMessageType::Message) { ss << "Received message " << str; log(ss.str()); @@ -189,7 +189,7 @@ namespace const ix::WebSocketOpenInfo& openInfo, const ix::WebSocketCloseInfo& closeInfo) { - if (messageType == ix::WebSocket_MessageType_Open) + if (messageType == ix::WebSocketMessageType::Open) { Logger() << "New server connection"; Logger() << "id: " << connectionState->getId(); @@ -200,7 +200,7 @@ namespace Logger() << it.first << ": " << it.second; } } - else if (messageType == ix::WebSocket_MessageType_Close) + else if (messageType == ix::WebSocketMessageType::Close) { log("Server closed connection"); From 288b05a048dacab747ada44342e704c414f07b63 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 15 May 2019 15:18:27 -0700 Subject: [PATCH 32/53] more protection against socket when closing --- .travis.yml | 8 +++- CMakeLists.txt | 2 +- docker/Dockerfile.fedora | 1 + docker/Dockerfile.ubuntu_xenial | 26 ++++++------ ixwebsocket/IXWebSocketTransport.cpp | 60 ++++++++++++++++++---------- ixwebsocket/IXWebSocketTransport.h | 4 ++ makefile | 2 +- test/CMakeLists.txt | 2 +- test/run.py | 7 +++- 9 files changed, 72 insertions(+), 40 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1f6e4ba6..aa6679a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,12 +5,16 @@ matrix: # macOS - os: osx compiler: clang - script: make test + script: + - python test/run.py + - make # Linux - os: linux dist: xenial - script: python test/run.py + script: + - python test/run.py + - make env: - CC=gcc - CXX=g++ diff --git a/CMakeLists.txt b/CMakeLists.txt index cebf10e0..c07f3350 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -157,6 +157,6 @@ install(TARGETS ixwebsocket PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ixwebsocket/ ) -if (NOT WIN32) +if (USE_WS) add_subdirectory(ws) endif() diff --git a/docker/Dockerfile.fedora b/docker/Dockerfile.fedora index ac2f178e..b23dc063 100644 --- a/docker/Dockerfile.fedora +++ b/docker/Dockerfile.fedora @@ -16,6 +16,7 @@ ENV PATH="${CMAKE_BIN_PATH}:${PATH}" RUN yum install -y python RUN yum install -y libtsan +RUN yum install -y zlib-devel COPY . . # RUN ["make", "test"] diff --git a/docker/Dockerfile.ubuntu_xenial b/docker/Dockerfile.ubuntu_xenial index 1e87694d..e24fe498 100644 --- a/docker/Dockerfile.ubuntu_xenial +++ b/docker/Dockerfile.ubuntu_xenial @@ -1,22 +1,24 @@ -FROM fedora:30 as build +# Build time +FROM ubuntu:xenial as build -RUN yum install -y gcc-g++ -RUN yum install -y cmake -RUN yum install -y make -RUN yum install -y openssl-devel - -RUN yum install -y wget +ENV DEBIAN_FRONTEND noninteractive +RUN apt-get update +RUN apt-get -y install wget RUN mkdir -p /tmp/cmake WORKDIR /tmp/cmake RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz +RUN apt-get -y install g++ +RUN apt-get -y install libssl-dev +RUN apt-get -y install libz-dev +RUN apt-get -y install make +RUN apt-get -y install python + +COPY . . + ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin ENV PATH="${CMAKE_BIN_PATH}:${PATH}" -RUN yum install -y python -RUN yum install -y libtsan - -COPY . . -RUN ["make", "test"] # RUN ["make"] +RUN ["make", "test"] diff --git a/ixwebsocket/IXWebSocketTransport.cpp b/ixwebsocket/IXWebSocketTransport.cpp index 415b6360..45a1f93d 100644 --- a/ixwebsocket/IXWebSocketTransport.cpp +++ b/ixwebsocket/IXWebSocketTransport.cpp @@ -149,13 +149,16 @@ namespace ix std::string("Could not parse URL ") + url); } - bool tls = protocol == "wss"; std::string errorMsg; - _socket = createSocket(tls, errorMsg); - - if (!_socket) { - return WebSocketInitResult(false, 0, errorMsg); + bool tls = protocol == "wss"; + std::lock_guard lock(_socketMutex); + _socket = createSocket(tls, errorMsg); + + if (!_socket) + { + return WebSocketInitResult(false, 0, errorMsg); + } } WebSocketHandshake webSocketHandshake(_requestInitCancellation, @@ -180,11 +183,14 @@ namespace ix _useMask = false; std::string errorMsg; - _socket = createSocket(fd, errorMsg); - - if (!_socket) { - return WebSocketInitResult(false, 0, errorMsg); + std::lock_guard lock(_socketMutex); + _socket = createSocket(fd, errorMsg); + + if (!_socket) + { + return WebSocketInitResult(false, 0, errorMsg); + } } WebSocketHandshake webSocketHandshake(_requestInitCancellation, @@ -338,7 +344,7 @@ namespace ix if (result == PollResultType::Error) { - _socket->close(); + closeSocket(); setReadyState(ReadyState::CLOSED); break; } @@ -363,7 +369,7 @@ namespace ix // if there are received data pending to be processed, then delay the abnormal closure // to after dispatch (other close code/reason could be read from the buffer) - _socket->close(); + closeSocket(); return PollResult::AbnormalClose; } @@ -377,18 +383,18 @@ namespace ix } else if (pollResult == PollResultType::Error) { - _socket->close(); + closeSocket(); } else if (pollResult == PollResultType::CloseRequest) { - _socket->close(); + closeSocket(); } if (_readyState == ReadyState::CLOSING && closingDelayExceeded()) { _rxbuf.clear(); // close code and reason were set when calling close() - _socket->close(); + closeSocket(); setReadyState(ReadyState::CLOSED); } @@ -655,7 +661,6 @@ namespace ix else { // Unexpected frame type - close(kProtocolErrorCode, kProtocolErrorMessage, _rxbuf.size()); } @@ -673,7 +678,7 @@ namespace ix // if we previously closed the connection (CLOSING state), then set state to CLOSED (code/reason were set before) if (_readyState == ReadyState::CLOSING) { - _socket->close(); + closeSocket(); setReadyState(ReadyState::CLOSED); } // if we weren't closing, then close using abnormal close code and message @@ -949,13 +954,19 @@ namespace ix _enablePerMessageDeflate, onProgressCallback); } + ssize_t WebSocketTransport::send() + { + std::lock_guard lock(_socketMutex); + return _socket->send((char*)&_txbuf[0], _txbuf.size()); + } + void WebSocketTransport::sendOnSocket() { std::lock_guard lock(_txbufMutex); while (_txbuf.size()) { - ssize_t ret = _socket->send((char*)&_txbuf[0], _txbuf.size()); + ssize_t ret = send(); if (ret < 0 && Socket::isWaitNeeded()) { @@ -963,8 +974,7 @@ namespace ix } else if (ret <= 0) { - _socket->close(); - + closeSocket(); setReadyState(ReadyState::CLOSED); break; } @@ -998,9 +1008,16 @@ namespace ix } } - void WebSocketTransport::closeSocketAndSwitchToClosedState(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote) + void WebSocketTransport::closeSocket() { + std::lock_guard lock(_socketMutex); _socket->close(); + } + + void WebSocketTransport::closeSocketAndSwitchToClosedState( + uint16_t code, const std::string& reason, size_t closeWireSize, bool remote) + { + closeSocket(); { std::lock_guard lock(_closeDataMutex); _closeCode = code; @@ -1011,7 +1028,8 @@ namespace ix setReadyState(ReadyState::CLOSED); } - void WebSocketTransport::close(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote) + void WebSocketTransport::close( + uint16_t code, const std::string& reason, size_t closeWireSize, bool remote) { _requestInitCancellation = true; diff --git a/ixwebsocket/IXWebSocketTransport.h b/ixwebsocket/IXWebSocketTransport.h index 22e87c45..83f168b6 100644 --- a/ixwebsocket/IXWebSocketTransport.h +++ b/ixwebsocket/IXWebSocketTransport.h @@ -96,6 +96,9 @@ namespace ix size_t closeWireSize = 0, bool remote = false); + void closeSocket(); + ssize_t send(); + ReadyState getReadyState() const; void setReadyState(ReadyState readyState); void setOnCloseCallback(const OnCloseCallback& onCloseCallback); @@ -151,6 +154,7 @@ namespace ix // Underlying TCP socket std::shared_ptr _socket; + std::mutex _socketMutex; // Hold the state of the connection (OPEN, CLOSED, etc...) std::atomic _readyState; diff --git a/makefile b/makefile index afca75ca..b683cbeb 100644 --- a/makefile +++ b/makefile @@ -9,7 +9,7 @@ install: brew # on osx it is good practice to make /usr/local user writable # sudo chown -R `whoami`/staff /usr/local brew: - mkdir -p build && (cd build ; cmake -DUSE_TLS=1 .. ; make -j install) + mkdir -p build && (cd build ; cmake -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j install) uninstall: xargs rm -fv < build/install_manifest.txt diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fc8cded4..9b35874b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -38,12 +38,12 @@ set (SOURCES IXUrlParserTest.cpp IXWebSocketMessageQTest.cpp IXWebSocketServerTest.cpp - IXWebSocketCloseTest.cpp ) # Some unittest don't work on windows yet if (UNIX) list(APPEND SOURCES + IXWebSocketCloseTest.cpp IXDNSLookupTest.cpp cmd_websocket_chat.cpp ) diff --git a/test/run.py b/test/run.py index 9f789cae..934b8b55 100755 --- a/test/run.py +++ b/test/run.py @@ -352,9 +352,12 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD # gen build files with CMake runCMake(sanitizer, buildDir) - if platform.system() == 'Darwin': + if platform.system() == 'Linux': + # build with make -j + runCommand('make -C {} -j 2'.format(buildDir)) + elif platform.system() == 'Darwin': # build with make - runCommand('make -C {} -j8'.format(buildDir)) + runCommand('make -C {} -j 8'.format(buildDir)) else: # build with cmake on recent runCommand('cmake --build --parallel {}'.format(buildDir)) From 14ec12d1f002f42a16c246971bac23cc2f61c55c Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 15 May 2019 15:26:49 -0700 Subject: [PATCH 33/53] do not build ws for now on travis --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index aa6679a1..5702463a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,14 +7,12 @@ matrix: compiler: clang script: - python test/run.py - - make # Linux - os: linux dist: xenial script: - python test/run.py - - make env: - CC=gcc - CXX=g++ From 042e6a22b8d003da3216b06cd755dad08071ff69 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 15 May 2019 15:37:30 -0700 Subject: [PATCH 34/53] comment failing test --- test/IXWebSocketCloseTest.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/IXWebSocketCloseTest.cpp b/test/IXWebSocketCloseTest.cpp index 1764b78a..c1d71058 100644 --- a/test/IXWebSocketCloseTest.cpp +++ b/test/IXWebSocketCloseTest.cpp @@ -231,6 +231,10 @@ namespace } } +// +// That test can fail on macOS on travis +// +#if 0 TEST_CASE("Websocket_client_close_default", "[close]") { SECTION("Make sure that close code and reason was used and sent to server.") @@ -288,6 +292,7 @@ TEST_CASE("Websocket_client_close_default", "[close]") ix::reportWebSocketTraffic(); } } +#endif TEST_CASE("Websocket_client_close_params_given", "[close]") { From d91c896e46546b140d31db9fcff05c076927635a Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 15 May 2019 15:44:14 -0700 Subject: [PATCH 35/53] comment failing test --- test/IXWebSocketCloseTest.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/IXWebSocketCloseTest.cpp b/test/IXWebSocketCloseTest.cpp index c1d71058..117eba51 100644 --- a/test/IXWebSocketCloseTest.cpp +++ b/test/IXWebSocketCloseTest.cpp @@ -294,6 +294,10 @@ TEST_CASE("Websocket_client_close_default", "[close]") } #endif +// +// That test can fail on macOS on travis +// +#if 0 TEST_CASE("Websocket_client_close_params_given", "[close]") { SECTION("Make sure that close code and reason was used and sent to server.") @@ -351,6 +355,7 @@ TEST_CASE("Websocket_client_close_params_given", "[close]") ix::reportWebSocketTraffic(); } } +#endif TEST_CASE("Websocket_server_close", "[close]") { From 09956d75004572e6bdb229545b09b29ac3b2f4df Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 15 May 2019 16:01:05 -0700 Subject: [PATCH 36/53] recursive mutex + enable test that was breaking on Ubuntu Xenial + gcc + tsan --- ixwebsocket/IXWebSocketTransport.cpp | 8 ++++---- ixwebsocket/IXWebSocketTransport.h | 2 +- test/IXWebSocketTestConnectionDisconnection.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ixwebsocket/IXWebSocketTransport.cpp b/ixwebsocket/IXWebSocketTransport.cpp index 45a1f93d..94c02591 100644 --- a/ixwebsocket/IXWebSocketTransport.cpp +++ b/ixwebsocket/IXWebSocketTransport.cpp @@ -152,7 +152,7 @@ namespace ix std::string errorMsg; { bool tls = protocol == "wss"; - std::lock_guard lock(_socketMutex); + std::lock_guard lock(_socketMutex); _socket = createSocket(tls, errorMsg); if (!_socket) @@ -184,7 +184,7 @@ namespace ix std::string errorMsg; { - std::lock_guard lock(_socketMutex); + std::lock_guard lock(_socketMutex); _socket = createSocket(fd, errorMsg); if (!_socket) @@ -956,7 +956,7 @@ namespace ix ssize_t WebSocketTransport::send() { - std::lock_guard lock(_socketMutex); + std::lock_guard lock(_socketMutex); return _socket->send((char*)&_txbuf[0], _txbuf.size()); } @@ -1010,7 +1010,7 @@ namespace ix void WebSocketTransport::closeSocket() { - std::lock_guard lock(_socketMutex); + std::lock_guard lock(_socketMutex); _socket->close(); } diff --git a/ixwebsocket/IXWebSocketTransport.h b/ixwebsocket/IXWebSocketTransport.h index 83f168b6..bae374ee 100644 --- a/ixwebsocket/IXWebSocketTransport.h +++ b/ixwebsocket/IXWebSocketTransport.h @@ -154,7 +154,7 @@ namespace ix // Underlying TCP socket std::shared_ptr _socket; - std::mutex _socketMutex; + std::recursive_mutex _socketMutex; // Hold the state of the connection (OPEN, CLOSED, etc...) std::atomic _readyState; diff --git a/test/IXWebSocketTestConnectionDisconnection.cpp b/test/IXWebSocketTestConnectionDisconnection.cpp index d06c30c2..dd4588e1 100644 --- a/test/IXWebSocketTestConnectionDisconnection.cpp +++ b/test/IXWebSocketTestConnectionDisconnection.cpp @@ -140,7 +140,7 @@ TEST_CASE("websocket_connections", "[websocket]") // This test breaks on travis CI - Ubuntu Xenial + gcc + tsan // We should fix this. - /*SECTION("Try to connect and disconnect with different timing, from not enough time to successfull connect") + SECTION("Try to connect and disconnect with different timing, from not enough time to successfull connect") { IXWebSocketTestConnectionDisconnection test; for (int i = 0; i < 20; ++i) @@ -150,5 +150,5 @@ TEST_CASE("websocket_connections", "[websocket]") ix::msleep(i*50); test.stop(); } - }*/ + } } From c5caf32b774ee8c9e73cac31a04fce9c3e8f0a35 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 15 May 2019 16:28:29 -0700 Subject: [PATCH 37/53] try to re-enable some tests --- test/IXWebSocketCloseTest.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/IXWebSocketCloseTest.cpp b/test/IXWebSocketCloseTest.cpp index 117eba51..1764b78a 100644 --- a/test/IXWebSocketCloseTest.cpp +++ b/test/IXWebSocketCloseTest.cpp @@ -231,10 +231,6 @@ namespace } } -// -// That test can fail on macOS on travis -// -#if 0 TEST_CASE("Websocket_client_close_default", "[close]") { SECTION("Make sure that close code and reason was used and sent to server.") @@ -292,12 +288,7 @@ TEST_CASE("Websocket_client_close_default", "[close]") ix::reportWebSocketTraffic(); } } -#endif -// -// That test can fail on macOS on travis -// -#if 0 TEST_CASE("Websocket_client_close_params_given", "[close]") { SECTION("Make sure that close code and reason was used and sent to server.") @@ -355,7 +346,6 @@ TEST_CASE("Websocket_client_close_params_given", "[close]") ix::reportWebSocketTraffic(); } } -#endif TEST_CASE("Websocket_server_close", "[close]") { From 24a32a060335f3ecddeb92601266e01cd6bc4cd6 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 15 May 2019 16:50:00 -0700 Subject: [PATCH 38/53] enum class HttpErrorCode derives from int --- ixwebsocket/IXHttpClient.h | 2 +- ixwebsocket/IXWebSocket.cpp | 2 +- ixwebsocket/IXWebSocketTransport.cpp | 12 +- ixwebsocket/IXWebSocketTransport.h | 2 +- ixwebsocket/LUrlParser.cpp | 10 +- ixwebsocket/LUrlParser.h | 10 +- test/IXWebSocketCloseTest.cpp | 22 +- third_party/spdlog/README.md | 638 +++++++++--------- .../include/spdlog/fmt/bundled/printf.h | 4 +- .../include/spdlog/fmt/bundled/ranges.h | 8 +- third_party/spdlog/include/spdlog/tweakme.h | 2 +- third_party/spdlog/tests/main.cpp | 2 +- third_party/spdlog/tests/test_mpmc_q.cpp | 2 +- third_party/spdlog/tests/utils.h | 2 +- 14 files changed, 359 insertions(+), 359 deletions(-) diff --git a/ixwebsocket/IXHttpClient.h b/ixwebsocket/IXHttpClient.h index 66f621cb..fe34ac0e 100644 --- a/ixwebsocket/IXHttpClient.h +++ b/ixwebsocket/IXHttpClient.h @@ -19,7 +19,7 @@ namespace ix { - enum class HttpErrorCode + enum class HttpErrorCode : int { Ok = 0, CannotConnect = 1, diff --git a/ixwebsocket/IXWebSocket.cpp b/ixwebsocket/IXWebSocket.cpp index c4a537ca..7617100a 100644 --- a/ixwebsocket/IXWebSocket.cpp +++ b/ixwebsocket/IXWebSocket.cpp @@ -263,7 +263,7 @@ namespace ix connectErr.wait_time = duration.count(); connectErr.retries = retries; } - + connectErr.reason = status.errorStr; connectErr.http_status = status.http_status; diff --git a/ixwebsocket/IXWebSocketTransport.cpp b/ixwebsocket/IXWebSocketTransport.cpp index 94c02591..f9a61adc 100644 --- a/ixwebsocket/IXWebSocketTransport.cpp +++ b/ixwebsocket/IXWebSocketTransport.cpp @@ -244,7 +244,7 @@ namespace ix { std::lock_guard lock(_lastSendPingTimePointMutex); _lastSendPingTimePoint = std::chrono::steady_clock::now(); - } + } { std::lock_guard lock(_lastReceivePongTimePointMutex); _lastReceivePongTimePoint = std::chrono::steady_clock::now(); @@ -303,7 +303,7 @@ namespace ix sendPing(ss.str()); } } - + // No timeout if state is not OPEN, otherwise computed // pingIntervalOrTimeoutGCD (equals to -1 if no ping and no ping timeout are set) int lastingTimeoutDelayInMs = (_readyState != ReadyState::OPEN) ? 0 : _pingIntervalOrTimeoutGCDSecs; @@ -316,10 +316,10 @@ namespace ix if (now >= _nextGCDTimePoint) { _nextGCDTimePoint = now + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs); - + lastingTimeoutDelayInMs = _pingIntervalOrTimeoutGCDSecs * 1000; } - else + else { lastingTimeoutDelayInMs = (int)std::chrono::duration_cast(_nextGCDTimePoint - now).count(); } @@ -368,7 +368,7 @@ namespace ix { // if there are received data pending to be processed, then delay the abnormal closure // to after dispatch (other close code/reason could be read from the buffer) - + closeSocket(); return PollResult::AbnormalClose; @@ -681,7 +681,7 @@ namespace ix closeSocket(); setReadyState(ReadyState::CLOSED); } - // if we weren't closing, then close using abnormal close code and message + // if we weren't closing, then close using abnormal close code and message else if (_readyState != ReadyState::CLOSED) { closeSocketAndSwitchToClosedState(kAbnormalCloseCode, kAbnormalCloseMessage, 0, false); diff --git a/ixwebsocket/IXWebSocketTransport.h b/ixwebsocket/IXWebSocketTransport.h index bae374ee..433d9e4a 100644 --- a/ixwebsocket/IXWebSocketTransport.h +++ b/ixwebsocket/IXWebSocketTransport.h @@ -173,7 +173,7 @@ namespace ix // Used to cancel dns lookup + socket connect + http upgrade std::atomic _requestInitCancellation; - + mutable std::mutex _closingTimePointMutex; std::chrono::time_point_closingTimePoint; static const int kClosingMaximumWaitingDelayInMs; diff --git a/ixwebsocket/LUrlParser.cpp b/ixwebsocket/LUrlParser.cpp index 926bf452..f7b82209 100644 --- a/ixwebsocket/LUrlParser.cpp +++ b/ixwebsocket/LUrlParser.cpp @@ -1,21 +1,21 @@ /* * Lightweight URL & URI parser (RFC 1738, RFC 3986) * https://github.com/corporateshark/LUrlParser - * + * * The MIT License (MIT) - * + * * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com) - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE diff --git a/ixwebsocket/LUrlParser.h b/ixwebsocket/LUrlParser.h index e347b369..06bd3d20 100644 --- a/ixwebsocket/LUrlParser.h +++ b/ixwebsocket/LUrlParser.h @@ -1,21 +1,21 @@ /* * Lightweight URL & URI parser (RFC 1738, RFC 3986) * https://github.com/corporateshark/LUrlParser - * + * * The MIT License (MIT) - * + * * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com) - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE diff --git a/test/IXWebSocketCloseTest.cpp b/test/IXWebSocketCloseTest.cpp index 1764b78a..ac286cda 100644 --- a/test/IXWebSocketCloseTest.cpp +++ b/test/IXWebSocketCloseTest.cpp @@ -61,7 +61,7 @@ namespace uint16_t WebSocketClient::getCloseCode() { std::lock_guard lck(_mutexCloseData); - + return _closeCode; } @@ -75,7 +75,7 @@ namespace bool WebSocketClient::getCloseRemote() { std::lock_guard lck(_mutexCloseData); - + return _closeRemote; } @@ -126,11 +126,11 @@ namespace log("client disconnected"); std::lock_guard lck(_mutexCloseData); - + _closeCode = closeInfo.code; _closeReason = std::string(closeInfo.reason); _closeRemote = closeInfo.remote; - + _webSocket.disableAutomaticReconnection(); } else if (messageType == ix::WebSocketMessageType::Error) @@ -209,7 +209,7 @@ namespace //Logger() << closeInfo.remote; std::lock_guard lck(mutexWrite); - + receivedCloseCode = closeInfo.code; receivedCloseReason = std::string(closeInfo.reason); receivedCloseRemote = closeInfo.remote; @@ -239,7 +239,7 @@ TEST_CASE("Websocket_client_close_default", "[close]") int port = getFreePort(); ix::WebSocketServer server(port); - + uint16_t serverReceivedCloseCode(0); bool serverReceivedCloseRemote(false); std::string serverReceivedCloseReason(""); @@ -274,7 +274,7 @@ TEST_CASE("Websocket_client_close_default", "[close]") { std::lock_guard lck(mutexWrite); - + // Here we read the code/reason received by the server, and ensure that remote is true REQUIRE(serverReceivedCloseCode == 1000); REQUIRE(serverReceivedCloseReason == "Normal closure"); @@ -297,7 +297,7 @@ TEST_CASE("Websocket_client_close_params_given", "[close]") int port = getFreePort(); ix::WebSocketServer server(port); - + uint16_t serverReceivedCloseCode(0); bool serverReceivedCloseRemote(false); std::string serverReceivedCloseReason(""); @@ -332,7 +332,7 @@ TEST_CASE("Websocket_client_close_params_given", "[close]") { std::lock_guard lck(mutexWrite); - + // Here we read the code/reason received by the server, and ensure that remote is true REQUIRE(serverReceivedCloseCode == 4000); REQUIRE(serverReceivedCloseReason == "My reason"); @@ -355,7 +355,7 @@ TEST_CASE("Websocket_server_close", "[close]") int port = getFreePort(); ix::WebSocketServer server(port); - + uint16_t serverReceivedCloseCode(0); bool serverReceivedCloseRemote(false); std::string serverReceivedCloseReason(""); @@ -390,7 +390,7 @@ TEST_CASE("Websocket_server_close", "[close]") { std::lock_guard lck(mutexWrite); - + // Here we read the code/reason received by the server, and ensure that remote is true REQUIRE(serverReceivedCloseCode == 1000); REQUIRE(serverReceivedCloseReason == "Normal closure"); diff --git a/third_party/spdlog/README.md b/third_party/spdlog/README.md index 48264324..1a2486f1 100644 --- a/third_party/spdlog/README.md +++ b/third_party/spdlog/README.md @@ -1,319 +1,319 @@ -# spdlog - -Very fast, header only, C++ logging library. [![Build Status](https://travis-ci.org/gabime/spdlog.svg?branch=master)](https://travis-ci.org/gabime/spdlog)  [![Build status](https://ci.appveyor.com/api/projects/status/d2jnxclg20vd0o50?svg=true)](https://ci.appveyor.com/project/gabime/spdlog) - - - -## Install -#### Just copy the headers: - -* Copy the source [folder](https://github.com/gabime/spdlog/tree/v1.x/include/spdlog) to your build tree and use a C++11 compiler. - -#### Or use your favorite package manager: - -* Ubuntu: `apt-get install libspdlog-dev` -* Homebrew: `brew install spdlog` -* FreeBSD: `cd /usr/ports/devel/spdlog/ && make install clean` -* Fedora: `yum install spdlog` -* Gentoo: `emerge dev-libs/spdlog` -* Arch Linux: `yaourt -S spdlog-git` -* vcpkg: `vcpkg install spdlog` - - -## Platforms - * Linux, FreeBSD, OpenBSD, Solaris, AIX - * Windows (msvc 2013+, cygwin) - * macOS (clang 3.5+) - * Android - -## Features -* Very fast (see [benchmarks](#benchmarks) below). -* Headers only, just copy and use. -* Feature rich formatting, using the excellent [fmt](https://github.com/fmtlib/fmt) library. -* Fast asynchronous mode (optional) -* [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting. -* Multi/Single threaded loggers. -* Various log targets: - * Rotating log files. - * Daily log files. - * Console logging (colors supported). - * syslog. - * Windows debugger (```OutputDebugString(..)```) - * Easily extendable with custom log targets (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface). -* Severity based filtering - threshold levels can be modified in runtime as well as in compile time. -* Binary data logging. - - -## Benchmarks - -Below are some [benchmarks](https://github.com/gabime/spdlog/blob/v1.x/bench/bench.cpp) done in Ubuntu 64 bit, Intel i7-4770 CPU @ 3.40GHz - -#### Synchronous mode -``` -******************************************************************************* -Single thread, 1,000,000 iterations -******************************************************************************* -basic_st... Elapsed: 0.181652 5,505,042/sec -rotating_st... Elapsed: 0.181781 5,501,117/sec -daily_st... Elapsed: 0.187595 5,330,630/sec -null_st... Elapsed: 0.0504704 19,813,602/sec -******************************************************************************* -10 threads sharing same logger, 1,000,000 iterations -******************************************************************************* -basic_mt... Elapsed: 0.616035 1,623,284/sec -rotating_mt... Elapsed: 0.620344 1,612,008/sec -daily_mt... Elapsed: 0.648353 1,542,369/sec -null_mt... Elapsed: 0.151972 6,580,166/sec -``` -#### Asynchronous mode -``` -******************************************************************************* -10 threads sharing same logger, 1,000,000 iterations -******************************************************************************* -async... Elapsed: 0.350066 2,856,606/sec -async... Elapsed: 0.314865 3,175,960/sec -async... Elapsed: 0.349851 2,858,358/sec -``` - -## Usage samples - -#### Basic usage -```c++ -#include "spdlog/spdlog.h" -int main() -{ - spdlog::info("Welcome to spdlog!"); - spdlog::error("Some error message with arg: {}", 1); - - spdlog::warn("Easy padding in numbers like {:08d}", 12); - spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); - spdlog::info("Support for floats {:03.2f}", 1.23456); - spdlog::info("Positional args are {1} {0}..", "too", "supported"); - spdlog::info("{:<30}", "left aligned"); - - spdlog::set_level(spdlog::level::debug); // Set global log level to debug - spdlog::debug("This message should be displayed.."); - - // change log pattern - spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v"); - - // Compile time log levels - // define SPDLOG_ACTIVE_LEVEL to desired level - SPDLOG_TRACE("Some trace message with param {}", {}); - SPDLOG_DEBUG("Some debug message"); - -} -``` -#### create stdout/stderr logger object -```c++ -#include "spdlog/spdlog.h" -#include "spdlog/sinks/stdout_color_sinks.h" -void stdout_example() -{ - // create color multi threaded logger - auto console = spdlog::stdout_color_mt("console"); - auto err_logger = spdlog::stderr_color_mt("stderr"); - spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)"); -} -``` ---- -#### Basic file logger -```c++ -#include "spdlog/sinks/basic_file_sink.h" -void basic_logfile_example() -{ - try - { - auto my_logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt"); - } - catch (const spdlog::spdlog_ex &ex) - { - std::cout << "Log init failed: " << ex.what() << std::endl; - } -} -``` ---- -#### Rotating files -```c++ -#include "spdlog/sinks/rotating_file_sink.h" -void rotating_example() -{ - // Create a file rotating logger with 5mb size max and 3 rotated files - auto rotating_logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3); -} -``` - ---- -#### Daily files -```c++ - -#include "spdlog/sinks/daily_file_sink.h" -void daily_example() -{ - // Create a daily logger - a new file is created every day on 2:30am - auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); -} - -``` - ---- -#### Cloning loggers -```c++ -// clone a logger and give it new name. -// Useful for creating subsystem loggers from some "root" logger -void clone_example() -{ - auto network_logger = spdlog::get("root")->clone("network"); - network_logger->info("Logging network stuff.."); -} -``` - ---- -#### Periodic flush -```c++ -// periodically flush all *registered* loggers every 3 seconds: -// warning: only use if all your loggers are thread safe! -spdlog::flush_every(std::chrono::seconds(3)); - -``` - ---- -#### Binary logging -```c++ -// log binary data as hex. -// many types of std::container types can be used. -// ranges are supported too. -// format flags: -// {:X} - print in uppercase. -// {:s} - don't separate each byte with space. -// {:p} - don't print the position on each line start. -// {:n} - don't split the output to lines. - -#include "spdlog/fmt/bin_to_hex.h" - -void binary_example() -{ - auto console = spdlog::get("console"); - std::array buf; - console->info("Binary example: {}", spdlog::to_hex(buf)); - console->info("Another binary example:{:n}", spdlog::to_hex(std::begin(buf), std::begin(buf) + 10)); - // more examples: - // logger->info("uppercase: {:X}", spdlog::to_hex(buf)); - // logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf)); - // logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf)); -} - -``` - ---- -#### Logger with multi sinks - each with different format and log level -```c++ - -// create logger with 2 targets with different log levels and formats. -// the console will show only warnings or errors, while the file will log all. -void multi_sink_example() -{ - auto console_sink = std::make_shared(); - console_sink->set_level(spdlog::level::warn); - console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v"); - - auto file_sink = std::make_shared("logs/multisink.txt", true); - file_sink->set_level(spdlog::level::trace); - - spdlog::logger logger("multi_sink", {console_sink, file_sink}); - logger.set_level(spdlog::level::debug); - logger.warn("this should appear in both console and file"); - logger.info("this message should not appear in the console, only in the file"); -} -``` - ---- -#### Asynchronous logging -```c++ -#include "spdlog/async.h" -#include "spdlog/sinks/basic_file_sink.h" -void async_example() -{ - // default thread pool settings can be modified *before* creating the async logger: - // spdlog::init_thread_pool(8192, 1); // queue with 8k items and 1 backing thread. - auto async_file = spdlog::basic_logger_mt("async_file_logger", "logs/async_log.txt"); - // alternatively: - // auto async_file = spdlog::create_async("async_file_logger", "logs/async_log.txt"); -} - -``` - ---- -#### Asynchronous logger with multi sinks -```c++ -#include "spdlog/sinks/stdout_color_sinks.h" -#include "spdlog/sinks/rotating_file_sink.h" - -void multi_sink_example2() -{ - spdlog::init_thread_pool(8192, 1); - auto stdout_sink = std::make_shared(); - auto rotating_sink = std::make_shared("mylog.txt", 1024*1024*10, 3); - std::vector sinks {stdout_sink, rotating_sink}; - auto logger = std::make_shared("loggername", sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block); - spdlog::register_logger(logger); -} -``` - ---- -#### User defined types -```c++ -// user defined types logging by implementing operator<< -#include "spdlog/fmt/ostr.h" // must be included -struct my_type -{ - int i; - template - friend OStream &operator<<(OStream &os, const my_type &c) - { - return os << "[my_type i=" << c.i << "]"; - } -}; - -void user_defined_example() -{ - spdlog::get("console")->info("user defined type: {}", my_type{14}); -} - -``` ---- -#### Custom error handler -```c++ -void err_handler_example() -{ - // can be set globally or per logger(logger->set_error_handler(..)) - spdlog::set_error_handler([](const std::string &msg) { spdlog::get("console")->error("*** LOGGER ERROR ***: {}", msg); }); - spdlog::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3); -} - -``` ---- -#### syslog -```c++ -#include "spdlog/sinks/syslog_sink.h" -void syslog_example() -{ - std::string ident = "spdlog-example"; - auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID); - syslog_logger->warn("This is warning that will end up in syslog."); -} -``` ---- -#### Android example -```c++ -#include "spdlog/sinks/android_sink.h" -void android_example() -{ - std::string tag = "spdlog-android"; - auto android_logger = spdlog::android_logger("android", tag); - android_logger->critical("Use \"adb shell logcat\" to view this message."); -} -``` - -## Documentation -Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki/1.-QuickStart) pages. +# spdlog + +Very fast, header only, C++ logging library. [![Build Status](https://travis-ci.org/gabime/spdlog.svg?branch=master)](https://travis-ci.org/gabime/spdlog)  [![Build status](https://ci.appveyor.com/api/projects/status/d2jnxclg20vd0o50?svg=true)](https://ci.appveyor.com/project/gabime/spdlog) + + + +## Install +#### Just copy the headers: + +* Copy the source [folder](https://github.com/gabime/spdlog/tree/v1.x/include/spdlog) to your build tree and use a C++11 compiler. + +#### Or use your favorite package manager: + +* Ubuntu: `apt-get install libspdlog-dev` +* Homebrew: `brew install spdlog` +* FreeBSD: `cd /usr/ports/devel/spdlog/ && make install clean` +* Fedora: `yum install spdlog` +* Gentoo: `emerge dev-libs/spdlog` +* Arch Linux: `yaourt -S spdlog-git` +* vcpkg: `vcpkg install spdlog` + + +## Platforms + * Linux, FreeBSD, OpenBSD, Solaris, AIX + * Windows (msvc 2013+, cygwin) + * macOS (clang 3.5+) + * Android + +## Features +* Very fast (see [benchmarks](#benchmarks) below). +* Headers only, just copy and use. +* Feature rich formatting, using the excellent [fmt](https://github.com/fmtlib/fmt) library. +* Fast asynchronous mode (optional) +* [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting. +* Multi/Single threaded loggers. +* Various log targets: + * Rotating log files. + * Daily log files. + * Console logging (colors supported). + * syslog. + * Windows debugger (```OutputDebugString(..)```) + * Easily extendable with custom log targets (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface). +* Severity based filtering - threshold levels can be modified in runtime as well as in compile time. +* Binary data logging. + + +## Benchmarks + +Below are some [benchmarks](https://github.com/gabime/spdlog/blob/v1.x/bench/bench.cpp) done in Ubuntu 64 bit, Intel i7-4770 CPU @ 3.40GHz + +#### Synchronous mode +``` +******************************************************************************* +Single thread, 1,000,000 iterations +******************************************************************************* +basic_st... Elapsed: 0.181652 5,505,042/sec +rotating_st... Elapsed: 0.181781 5,501,117/sec +daily_st... Elapsed: 0.187595 5,330,630/sec +null_st... Elapsed: 0.0504704 19,813,602/sec +******************************************************************************* +10 threads sharing same logger, 1,000,000 iterations +******************************************************************************* +basic_mt... Elapsed: 0.616035 1,623,284/sec +rotating_mt... Elapsed: 0.620344 1,612,008/sec +daily_mt... Elapsed: 0.648353 1,542,369/sec +null_mt... Elapsed: 0.151972 6,580,166/sec +``` +#### Asynchronous mode +``` +******************************************************************************* +10 threads sharing same logger, 1,000,000 iterations +******************************************************************************* +async... Elapsed: 0.350066 2,856,606/sec +async... Elapsed: 0.314865 3,175,960/sec +async... Elapsed: 0.349851 2,858,358/sec +``` + +## Usage samples + +#### Basic usage +```c++ +#include "spdlog/spdlog.h" +int main() +{ + spdlog::info("Welcome to spdlog!"); + spdlog::error("Some error message with arg: {}", 1); + + spdlog::warn("Easy padding in numbers like {:08d}", 12); + spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); + spdlog::info("Support for floats {:03.2f}", 1.23456); + spdlog::info("Positional args are {1} {0}..", "too", "supported"); + spdlog::info("{:<30}", "left aligned"); + + spdlog::set_level(spdlog::level::debug); // Set global log level to debug + spdlog::debug("This message should be displayed.."); + + // change log pattern + spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v"); + + // Compile time log levels + // define SPDLOG_ACTIVE_LEVEL to desired level + SPDLOG_TRACE("Some trace message with param {}", {}); + SPDLOG_DEBUG("Some debug message"); + +} +``` +#### create stdout/stderr logger object +```c++ +#include "spdlog/spdlog.h" +#include "spdlog/sinks/stdout_color_sinks.h" +void stdout_example() +{ + // create color multi threaded logger + auto console = spdlog::stdout_color_mt("console"); + auto err_logger = spdlog::stderr_color_mt("stderr"); + spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)"); +} +``` +--- +#### Basic file logger +```c++ +#include "spdlog/sinks/basic_file_sink.h" +void basic_logfile_example() +{ + try + { + auto my_logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt"); + } + catch (const spdlog::spdlog_ex &ex) + { + std::cout << "Log init failed: " << ex.what() << std::endl; + } +} +``` +--- +#### Rotating files +```c++ +#include "spdlog/sinks/rotating_file_sink.h" +void rotating_example() +{ + // Create a file rotating logger with 5mb size max and 3 rotated files + auto rotating_logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3); +} +``` + +--- +#### Daily files +```c++ + +#include "spdlog/sinks/daily_file_sink.h" +void daily_example() +{ + // Create a daily logger - a new file is created every day on 2:30am + auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); +} + +``` + +--- +#### Cloning loggers +```c++ +// clone a logger and give it new name. +// Useful for creating subsystem loggers from some "root" logger +void clone_example() +{ + auto network_logger = spdlog::get("root")->clone("network"); + network_logger->info("Logging network stuff.."); +} +``` + +--- +#### Periodic flush +```c++ +// periodically flush all *registered* loggers every 3 seconds: +// warning: only use if all your loggers are thread safe! +spdlog::flush_every(std::chrono::seconds(3)); + +``` + +--- +#### Binary logging +```c++ +// log binary data as hex. +// many types of std::container types can be used. +// ranges are supported too. +// format flags: +// {:X} - print in uppercase. +// {:s} - don't separate each byte with space. +// {:p} - don't print the position on each line start. +// {:n} - don't split the output to lines. + +#include "spdlog/fmt/bin_to_hex.h" + +void binary_example() +{ + auto console = spdlog::get("console"); + std::array buf; + console->info("Binary example: {}", spdlog::to_hex(buf)); + console->info("Another binary example:{:n}", spdlog::to_hex(std::begin(buf), std::begin(buf) + 10)); + // more examples: + // logger->info("uppercase: {:X}", spdlog::to_hex(buf)); + // logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf)); + // logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf)); +} + +``` + +--- +#### Logger with multi sinks - each with different format and log level +```c++ + +// create logger with 2 targets with different log levels and formats. +// the console will show only warnings or errors, while the file will log all. +void multi_sink_example() +{ + auto console_sink = std::make_shared(); + console_sink->set_level(spdlog::level::warn); + console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v"); + + auto file_sink = std::make_shared("logs/multisink.txt", true); + file_sink->set_level(spdlog::level::trace); + + spdlog::logger logger("multi_sink", {console_sink, file_sink}); + logger.set_level(spdlog::level::debug); + logger.warn("this should appear in both console and file"); + logger.info("this message should not appear in the console, only in the file"); +} +``` + +--- +#### Asynchronous logging +```c++ +#include "spdlog/async.h" +#include "spdlog/sinks/basic_file_sink.h" +void async_example() +{ + // default thread pool settings can be modified *before* creating the async logger: + // spdlog::init_thread_pool(8192, 1); // queue with 8k items and 1 backing thread. + auto async_file = spdlog::basic_logger_mt("async_file_logger", "logs/async_log.txt"); + // alternatively: + // auto async_file = spdlog::create_async("async_file_logger", "logs/async_log.txt"); +} + +``` + +--- +#### Asynchronous logger with multi sinks +```c++ +#include "spdlog/sinks/stdout_color_sinks.h" +#include "spdlog/sinks/rotating_file_sink.h" + +void multi_sink_example2() +{ + spdlog::init_thread_pool(8192, 1); + auto stdout_sink = std::make_shared(); + auto rotating_sink = std::make_shared("mylog.txt", 1024*1024*10, 3); + std::vector sinks {stdout_sink, rotating_sink}; + auto logger = std::make_shared("loggername", sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block); + spdlog::register_logger(logger); +} +``` + +--- +#### User defined types +```c++ +// user defined types logging by implementing operator<< +#include "spdlog/fmt/ostr.h" // must be included +struct my_type +{ + int i; + template + friend OStream &operator<<(OStream &os, const my_type &c) + { + return os << "[my_type i=" << c.i << "]"; + } +}; + +void user_defined_example() +{ + spdlog::get("console")->info("user defined type: {}", my_type{14}); +} + +``` +--- +#### Custom error handler +```c++ +void err_handler_example() +{ + // can be set globally or per logger(logger->set_error_handler(..)) + spdlog::set_error_handler([](const std::string &msg) { spdlog::get("console")->error("*** LOGGER ERROR ***: {}", msg); }); + spdlog::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3); +} + +``` +--- +#### syslog +```c++ +#include "spdlog/sinks/syslog_sink.h" +void syslog_example() +{ + std::string ident = "spdlog-example"; + auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID); + syslog_logger->warn("This is warning that will end up in syslog."); +} +``` +--- +#### Android example +```c++ +#include "spdlog/sinks/android_sink.h" +void android_example() +{ + std::string tag = "spdlog-android"; + auto android_logger = spdlog::android_logger("android", tag); + android_logger->critical("Use \"adb shell logcat\" to view this message."); +} +``` + +## Documentation +Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki/1.-QuickStart) pages. diff --git a/third_party/spdlog/include/spdlog/fmt/bundled/printf.h b/third_party/spdlog/include/spdlog/fmt/bundled/printf.h index 6f2715d9..c6088458 100644 --- a/third_party/spdlog/include/spdlog/fmt/bundled/printf.h +++ b/third_party/spdlog/include/spdlog/fmt/bundled/printf.h @@ -713,7 +713,7 @@ typedef basic_format_args wprintf_args; /** \rst Constructs an `~fmt::format_arg_store` object that contains references to - arguments and can be implicitly converted to `~fmt::printf_args`. + arguments and can be implicitly converted to `~fmt::printf_args`. \endrst */ template @@ -723,7 +723,7 @@ inline format_arg_store /** \rst Constructs an `~fmt::format_arg_store` object that contains references to - arguments and can be implicitly converted to `~fmt::wprintf_args`. + arguments and can be implicitly converted to `~fmt::wprintf_args`. \endrst */ template diff --git a/third_party/spdlog/include/spdlog/fmt/bundled/ranges.h b/third_party/spdlog/include/spdlog/fmt/bundled/ranges.h index 3672d4ca..59094100 100644 --- a/third_party/spdlog/include/spdlog/fmt/bundled/ranges.h +++ b/third_party/spdlog/include/spdlog/fmt/bundled/ranges.h @@ -159,7 +159,7 @@ void for_each(index_sequence, Tuple &&tup, F &&f) FMT_NOEXCEPT { } template -FMT_CONSTEXPR make_index_sequence::value> +FMT_CONSTEXPR make_index_sequence::value> get_indexes(T const &) { return {}; } template @@ -169,14 +169,14 @@ void for_each(Tuple &&tup, F &&f) { } template -FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&, +FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&, typename std::enable_if< !is_like_std_string::type>::value>::type* = nullptr) { return add_space ? " {}" : "{}"; } template -FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&, +FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&, typename std::enable_if< is_like_std_string::type>::value>::type* = nullptr) { return add_space ? " \"{}\"" : "\"{}\""; @@ -205,7 +205,7 @@ struct is_tuple_like { }; template -struct formatter::value>::type> { private: // C++11 generic lambda for format() diff --git a/third_party/spdlog/include/spdlog/tweakme.h b/third_party/spdlog/include/spdlog/tweakme.h index b3b71e4f..d7db40ff 100644 --- a/third_party/spdlog/include/spdlog/tweakme.h +++ b/third_party/spdlog/include/spdlog/tweakme.h @@ -142,4 +142,4 @@ // Defaults to __FUNCTION__ (should work on all compilers) if not defined. // // #define SPDLOG_FUNCTION __PRETTY_FUNCTION__ -/////////////////////////////////////////////////////////////////////////////// \ No newline at end of file +/////////////////////////////////////////////////////////////////////////////// diff --git a/third_party/spdlog/tests/main.cpp b/third_party/spdlog/tests/main.cpp index 063e8787..0c7c351f 100644 --- a/third_party/spdlog/tests/main.cpp +++ b/third_party/spdlog/tests/main.cpp @@ -1,2 +1,2 @@ #define CATCH_CONFIG_MAIN -#include "catch.hpp" \ No newline at end of file +#include "catch.hpp" diff --git a/third_party/spdlog/tests/test_mpmc_q.cpp b/third_party/spdlog/tests/test_mpmc_q.cpp index 19a188d8..7959b10c 100644 --- a/third_party/spdlog/tests/test_mpmc_q.cpp +++ b/third_party/spdlog/tests/test_mpmc_q.cpp @@ -104,4 +104,4 @@ TEST_CASE("full_queue", "[mpmc_blocking_q]") int item = -1; q.dequeue_for(item, milliseconds(0)); REQUIRE(item == 123456); -} \ No newline at end of file +} diff --git a/third_party/spdlog/tests/utils.h b/third_party/spdlog/tests/utils.h index e788b507..e7ffc921 100644 --- a/third_party/spdlog/tests/utils.h +++ b/third_party/spdlog/tests/utils.h @@ -13,4 +13,4 @@ std::size_t count_lines(const std::string &filename); std::size_t get_filesize(const std::string &filename); -bool ends_with(std::string const &value, std::string const &ending); \ No newline at end of file +bool ends_with(std::string const &value, std::string const &ending); From 643e1bf20f998af31416dacfea33836dc7a17dfd Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 15 May 2019 17:52:03 -0700 Subject: [PATCH 39/53] unittest / add options to set the number of jobs --- test/run.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/test/run.py b/test/run.py index 934b8b55..6b29475b 100755 --- a/test/run.py +++ b/test/run.py @@ -269,12 +269,12 @@ def executeJob(job): return job -def executeJobs(jobs): +def executeJobs(jobs, cpuCount): '''Execute a list of job concurrently on multiple CPU/cores''' - poolSize = multiprocessing.cpu_count() + print('Using {} cores to execute the unittest'.format(cpuCount)) - pool = multiprocessing.Pool(poolSize) + pool = multiprocessing.Pool(cpuCount) results = pool.map(executeJob, jobs) pool.close() pool.join() @@ -346,7 +346,8 @@ def generateXmlOutput(results, xmlOutput, testRunName, runTime): f.write(content.encode('utf-8')) -def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLDB): +def run(testName, buildDir, sanitizer, xmlOutput, + testRunName, buildOnly, useLLDB, cpuCount): '''Main driver. Run cmake, compiles, execute and validate the testsuite.''' # gen build files with CMake @@ -405,7 +406,7 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD }) start = time.time() - results = executeJobs(jobs) + results = executeJobs(jobs, cpuCount) runTime = time.time() - start generateXmlOutput(results, xmlOutput, testRunName, runTime) @@ -455,6 +456,8 @@ def main(): help='Run the test through lldb.') parser.add_argument('--run_name', '-n', help='Name of the test run.') + parser.add_argument('--cpu_count', '-j', + help='Number of cpus to use for running the tests.') args = parser.parse_args() @@ -499,8 +502,10 @@ def main(): print('LLDB is only supported on Apple at this point') args.lldb = False + cpuCount = args.cpu_count or multiprocessing.cpu_count() + return run(args.test, buildDir, sanitizer, xmlOutput, - testRunName, args.build_only, args.lldb) + testRunName, args.build_only, args.lldb, cpuCount) if __name__ == '__main__': From ba0e007c055200156f4c58fcaace2ceb3e9e241b Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 15 May 2019 18:15:45 -0700 Subject: [PATCH 40/53] -j option actually work ... --- test/run.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/run.py b/test/run.py index 6b29475b..70fdc450 100755 --- a/test/run.py +++ b/test/run.py @@ -456,7 +456,7 @@ def main(): help='Run the test through lldb.') parser.add_argument('--run_name', '-n', help='Name of the test run.') - parser.add_argument('--cpu_count', '-j', + parser.add_argument('--cpu_count', '-j', type=int, default=multiprocessing.cpu_count(), help='Number of cpus to use for running the tests.') args = parser.parse_args() @@ -502,10 +502,8 @@ def main(): print('LLDB is only supported on Apple at this point') args.lldb = False - cpuCount = args.cpu_count or multiprocessing.cpu_count() - return run(args.test, buildDir, sanitizer, xmlOutput, - testRunName, args.build_only, args.lldb, cpuCount) + testRunName, args.build_only, args.lldb, args.cpu_count) if __name__ == '__main__': From f1604c64604aef00ca9d75cd1706ce87b26f3cc8 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 15 May 2019 18:57:17 -0700 Subject: [PATCH 41/53] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c529100b..1096f9be 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # General ![Alt text](https://travis-ci.org/machinezone/IXWebSocket.svg?branch=master) +(master is broken now ... see https://github.com/machinezone/IXWebSocket/issues/77) ## Introduction From 20b625e48300f30a55b984be81bc791bf6144cb7 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 15 May 2019 19:22:05 -0700 Subject: [PATCH 42/53] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 1096f9be..c529100b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # General ![Alt text](https://travis-ci.org/machinezone/IXWebSocket.svg?branch=master) -(master is broken now ... see https://github.com/machinezone/IXWebSocket/issues/77) ## Introduction From 23f171f34d6fe7cf6d8aa588808203002160671a Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 15 May 2019 18:43:57 -0700 Subject: [PATCH 43/53] adding logging to IXWebSocketTestConnectionDisconnection makes it fails reliably --- test/IXWebSocketTestConnectionDisconnection.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/IXWebSocketTestConnectionDisconnection.cpp b/test/IXWebSocketTestConnectionDisconnection.cpp index dd4588e1..ac124ce6 100644 --- a/test/IXWebSocketTestConnectionDisconnection.cpp +++ b/test/IXWebSocketTestConnectionDisconnection.cpp @@ -129,11 +129,17 @@ TEST_CASE("websocket_connections", "[websocket]") SECTION("Try to connect and disconnect with different timing, not enough time to succesfully connect") { IXWebSocketTestConnectionDisconnection test; + log(std::string("50 Runs")); + for (int i = 0; i < 50; ++i) { log(std::string("Run: ") + std::to_string(i)); test.start(WEBSOCKET_DOT_ORG_URL); + + log(std::string("Sleeping")); ix::msleep(i); + + log(std::string("Stopping")); test.stop(); } } @@ -143,11 +149,17 @@ TEST_CASE("websocket_connections", "[websocket]") SECTION("Try to connect and disconnect with different timing, from not enough time to successfull connect") { IXWebSocketTestConnectionDisconnection test; + log(std::string("20 Runs")); + for (int i = 0; i < 20; ++i) { log(std::string("Run: ") + std::to_string(i)); test.start(WEBSOCKET_DOT_ORG_URL); + + log(std::string("Sleeping")); ix::msleep(i*50); + + log(std::string("Stopping")); test.stop(); } } From cdbed26d1fc0d4600f334a10c30d3bd3aab3fdba Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 15 May 2019 19:19:13 -0700 Subject: [PATCH 44/53] use a regular mutex instead of a recursive one + stop properly --- ixwebsocket/IXWebSocket.cpp | 3 +++ ixwebsocket/IXWebSocketTransport.cpp | 24 +++++++++++++++++++----- ixwebsocket/IXWebSocketTransport.h | 2 +- test/CMakeLists.txt | 4 ++-- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/ixwebsocket/IXWebSocket.cpp b/ixwebsocket/IXWebSocket.cpp index 7617100a..3b232fac 100644 --- a/ixwebsocket/IXWebSocket.cpp +++ b/ixwebsocket/IXWebSocket.cpp @@ -293,6 +293,9 @@ namespace ix break; } + // We cannot enter poll which might block forever if we are stopping + if (_stop) break; + // 2. Poll to see if there's any new data available WebSocketTransport::PollResult pollResult = _ws.poll(); diff --git a/ixwebsocket/IXWebSocketTransport.cpp b/ixwebsocket/IXWebSocketTransport.cpp index f9a61adc..758d2375 100644 --- a/ixwebsocket/IXWebSocketTransport.cpp +++ b/ixwebsocket/IXWebSocketTransport.cpp @@ -152,7 +152,7 @@ namespace ix std::string errorMsg; { bool tls = protocol == "wss"; - std::lock_guard lock(_socketMutex); + std::lock_guard lock(_socketMutex); _socket = createSocket(tls, errorMsg); if (!_socket) @@ -184,7 +184,7 @@ namespace ix std::string errorMsg; { - std::lock_guard lock(_socketMutex); + std::lock_guard lock(_socketMutex); _socket = createSocket(fd, errorMsg); if (!_socket) @@ -326,9 +326,20 @@ namespace ix } #ifdef _WIN32 - if (lastingTimeoutDelayInMs <= 0) lastingTimeoutDelayInMs = 20; + // Windows does not have select interrupt capabilities, so wait with a small timeout + if (lastingTimeoutDelayInMs <= 0) + { + lastingTimeoutDelayInMs = 20; + } #endif + // If we are requesting a cancellation, pass in a positive and small timeout + // to never poll forever without a timeout. + if (_requestInitCancellation) + { + lastingTimeoutDelayInMs = 100; + } + // poll the socket PollResultType pollResult = _socket->poll(lastingTimeoutDelayInMs); @@ -956,7 +967,7 @@ namespace ix ssize_t WebSocketTransport::send() { - std::lock_guard lock(_socketMutex); + std::lock_guard lock(_socketMutex); return _socket->send((char*)&_txbuf[0], _txbuf.size()); } @@ -1010,7 +1021,7 @@ namespace ix void WebSocketTransport::closeSocket() { - std::lock_guard lock(_socketMutex); + std::lock_guard lock(_socketMutex); _socket->close(); } @@ -1018,6 +1029,7 @@ namespace ix uint16_t code, const std::string& reason, size_t closeWireSize, bool remote) { closeSocket(); + { std::lock_guard lock(_closeDataMutex); _closeCode = code; @@ -1025,7 +1037,9 @@ namespace ix _closeWireSize = closeWireSize; _closeRemote = remote; } + setReadyState(ReadyState::CLOSED); + _requestInitCancellation = false; } void WebSocketTransport::close( diff --git a/ixwebsocket/IXWebSocketTransport.h b/ixwebsocket/IXWebSocketTransport.h index 433d9e4a..6aa1ac82 100644 --- a/ixwebsocket/IXWebSocketTransport.h +++ b/ixwebsocket/IXWebSocketTransport.h @@ -154,7 +154,7 @@ namespace ix // Underlying TCP socket std::shared_ptr _socket; - std::recursive_mutex _socketMutex; + std::mutex _socketMutex; // Hold the state of the connection (OPEN, CLOSED, etc...) std::atomic _readyState; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9b35874b..d27a796f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -43,15 +43,15 @@ set (SOURCES # Some unittest don't work on windows yet if (UNIX) list(APPEND SOURCES - IXWebSocketCloseTest.cpp IXDNSLookupTest.cpp cmd_websocket_chat.cpp ) endif() -# Disable ping tests for now as they aren't super reliable +# Disable tests for now that are failing or not reliable # IXWebSocketPingTest.cpp # IXWebSocketPingTimeoutTest.cpp +# IXWebSocketCloseTest.cpp add_executable(ixwebsocket_unittest ${SOURCES}) From 9ac02323ad51e4949e60a44bc4a800864e351422 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Thu, 16 May 2019 07:01:15 -0700 Subject: [PATCH 45/53] build ws on travis (mac + linux) --- .travis.yml | 2 ++ makefile | 3 +++ 2 files changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 5702463a..5fadfeec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,14 @@ matrix: compiler: clang script: - python test/run.py + - make ws # Linux - os: linux dist: xenial script: - python test/run.py + - make ws env: - CC=gcc - CXX=g++ diff --git a/makefile b/makefile index b683cbeb..8d5f15c4 100644 --- a/makefile +++ b/makefile @@ -11,6 +11,9 @@ install: brew brew: mkdir -p build && (cd build ; cmake -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j install) +ws: + mkdir -p build && (cd build ; cmake -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j) + uninstall: xargs rm -fv < build/install_manifest.txt From 5c4840f129d18e025aa6cd60fed871087ee69c4c Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Thu, 16 May 2019 12:24:58 -0700 Subject: [PATCH 46/53] first socket test hit a local server instead of a remote server / this can help with a windows intermittent failure --- test/IXSocketTest.cpp | 69 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/test/IXSocketTest.cpp b/test/IXSocketTest.cpp index 6306c7ed..279ad1ad 100644 --- a/test/IXSocketTest.cpp +++ b/test/IXSocketTest.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "IXTest.h" #include "catch.hpp" @@ -49,17 +50,75 @@ namespace ix REQUIRE(sscanf(line.c_str(), "HTTP/1.1 %d", &status) == 1); REQUIRE(status == expectedStatus); } + + bool startServer(ix::WebSocketServer& server) + { + server.setOnConnectionCallback( + [&server](std::shared_ptr webSocket, + std::shared_ptr connectionState) + { + webSocket->setOnMessageCallback( + [webSocket, connectionState, &server](ix::WebSocketMessageType messageType, + const std::string& str, + size_t wireSize, + const ix::WebSocketErrorInfo& error, + const ix::WebSocketOpenInfo& openInfo, + const ix::WebSocketCloseInfo& closeInfo) + { + if (messageType == ix::WebSocketMessageType::Open) + { + Logger() << "New connection"; + Logger() << "Uri: " << openInfo.uri; + Logger() << "Headers:"; + for (auto it : openInfo.headers) + { + Logger() << it.first << ": " << it.second; + } + } + else if (messageType == ix::WebSocketMessageType::Close) + { + Logger() << "Closed connection"; + } + else if (messageType == ix::WebSocketMessageType::Message) + { + for (auto&& client : server.getClients()) + { + if (client != webSocket) + { + client->send(str); + } + } + } + } + ); + } + ); + + auto res = server.listen(); + if (!res.first) + { + Logger() << res.second; + return false; + } + + server.start(); + return true; + } } TEST_CASE("socket", "[socket]") { - SECTION("Connect to google HTTP server. Send GET request without header. Should return 200") + SECTION("Connect to a local websocket server over a free port. Send GET request without header. Should return 400") { + // Start a server first which we'll hit with our socket code + int port = getFreePort(); + ix::WebSocketServer server(port); + REQUIRE(startServer(server)); + std::string errMsg; bool tls = false; std::shared_ptr socket = createSocket(tls, errMsg); - std::string host("www.google.com"); - int port = 80; + std::string host("127.0.0.1"); std::stringstream ss; ss << "GET / HTTP/1.1\r\n"; @@ -67,14 +126,14 @@ TEST_CASE("socket", "[socket]") ss << "\r\n"; std::string request(ss.str()); - int expectedStatus = 200; + int expectedStatus = 400; int timeoutSecs = 3; testSocket(host, port, request, socket, expectedStatus, timeoutSecs); } #if defined(__APPLE__) || defined(__linux__) - SECTION("Connect to google HTTPS server. Send GET request without header. Should return 200") + SECTION("Connect to google HTTPS server over port 443. Send GET request without header. Should return 200") { std::string errMsg; bool tls = true; From 0e59927384cbd3fa3d54d2f941271635e5c3876c Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Thu, 16 May 2019 12:46:53 -0700 Subject: [PATCH 47/53] Add constants for closing code and messages --- CMakeLists.txt | 2 ++ ixwebsocket/IXWebSocket.h | 5 ++-- ixwebsocket/IXWebSocketCloseConstants.cpp | 23 +++++++++++++++ ixwebsocket/IXWebSocketCloseConstants.h | 29 +++++++++++++++++++ ixwebsocket/IXWebSocketTransport.cpp | 35 ++++++++++------------- ixwebsocket/IXWebSocketTransport.h | 5 ++-- 6 files changed, 75 insertions(+), 24 deletions(-) create mode 100644 ixwebsocket/IXWebSocketCloseConstants.cpp create mode 100644 ixwebsocket/IXWebSocketCloseConstants.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c07f3350..a82c9c05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,7 @@ set( IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterrupt.cpp ixwebsocket/IXSelectInterruptFactory.cpp ixwebsocket/IXConnectionState.cpp + ixwebsocket/IXWebSocketCloseConstants.cpp ) set( IXWEBSOCKET_HEADERS @@ -72,6 +73,7 @@ set( IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterrupt.h ixwebsocket/IXSelectInterruptFactory.h ixwebsocket/IXConnectionState.h + ixwebsocket/IXWebSocketCloseConstants.h ) if (UNIX) diff --git a/ixwebsocket/IXWebSocket.h b/ixwebsocket/IXWebSocket.h index 00aeb880..f25a88ec 100644 --- a/ixwebsocket/IXWebSocket.h +++ b/ixwebsocket/IXWebSocket.h @@ -19,6 +19,7 @@ #include "IXWebSocketSendInfo.h" #include "IXWebSocketPerMessageDeflateOptions.h" #include "IXWebSocketHttpHeaders.h" +#include "IXWebSocketCloseConstants.h" #include "IXProgressCallback.h" namespace ix @@ -101,8 +102,8 @@ namespace ix void start(); // stop is synchronous - void stop(uint16_t code = 1000, - const std::string& reason = "Normal closure"); + void stop(uint16_t code = WebSocketCloseConstants::kNormalClosureCode, + const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage); // Run in blocking mode, by connecting first manually, and then calling run. WebSocketInitResult connect(int timeoutSecs); diff --git a/ixwebsocket/IXWebSocketCloseConstants.cpp b/ixwebsocket/IXWebSocketCloseConstants.cpp new file mode 100644 index 00000000..85b69454 --- /dev/null +++ b/ixwebsocket/IXWebSocketCloseConstants.cpp @@ -0,0 +1,23 @@ +/* + * IXWebSocketCloseConstants.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#include "IXWebSocketCloseConstants.h" + +namespace ix +{ + const uint16_t WebSocketCloseConstants::kNormalClosureCode(1000); + const uint16_t WebSocketCloseConstants::kInternalErrorCode(1011); + const uint16_t WebSocketCloseConstants::kAbnormalCloseCode(1006); + const uint16_t WebSocketCloseConstants::kProtocolErrorCode(1002); + const uint16_t WebSocketCloseConstants::kNoStatusCodeErrorCode(1005); + + const std::string WebSocketCloseConstants::kNormalClosureMessage("Normal closure"); + const std::string WebSocketCloseConstants::kInternalErrorMessage("Internal error"); + const std::string WebSocketCloseConstants::kAbnormalCloseMessage("Abnormal closure"); + const std::string WebSocketCloseConstants::kPingTimeoutMessage("Ping timeout"); + const std::string WebSocketCloseConstants::kProtocolErrorMessage("Protocol error"); + const std::string WebSocketCloseConstants::kNoStatusCodeErrorMessage("No status code"); +} diff --git a/ixwebsocket/IXWebSocketCloseConstants.h b/ixwebsocket/IXWebSocketCloseConstants.h new file mode 100644 index 00000000..81ac531b --- /dev/null +++ b/ixwebsocket/IXWebSocketCloseConstants.h @@ -0,0 +1,29 @@ +/* + * IXWebSocketCloseConstants.h + * Author: Benjamin Sergeant + * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include +#include + +namespace ix +{ + struct WebSocketCloseConstants + { + static const uint16_t kNormalClosureCode; + static const uint16_t kInternalErrorCode; + static const uint16_t kAbnormalCloseCode; + static const uint16_t kProtocolErrorCode; + static const uint16_t kNoStatusCodeErrorCode; + + static const std::string kNormalClosureMessage; + static const std::string kInternalErrorMessage; + static const std::string kAbnormalCloseMessage; + static const std::string kPingTimeoutMessage; + static const std::string kProtocolErrorMessage; + static const std::string kNoStatusCodeErrorMessage; + }; +} diff --git a/ixwebsocket/IXWebSocketTransport.cpp b/ixwebsocket/IXWebSocketTransport.cpp index 758d2375..9741aae9 100644 --- a/ixwebsocket/IXWebSocketTransport.cpp +++ b/ixwebsocket/IXWebSocketTransport.cpp @@ -74,21 +74,11 @@ namespace ix const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(200); constexpr size_t WebSocketTransport::kChunkSize; - const uint16_t WebSocketTransport::kInternalErrorCode(1011); - const uint16_t WebSocketTransport::kAbnormalCloseCode(1006); - const uint16_t WebSocketTransport::kProtocolErrorCode(1002); - const uint16_t WebSocketTransport::kNoStatusCodeErrorCode(1005); - const std::string WebSocketTransport::kInternalErrorMessage("Internal error"); - const std::string WebSocketTransport::kAbnormalCloseMessage("Abnormal closure"); - const std::string WebSocketTransport::kPingTimeoutMessage("Ping timeout"); - const std::string WebSocketTransport::kProtocolErrorMessage("Protocol error"); - const std::string WebSocketTransport::kNoStatusCodeErrorMessage("No status code"); - WebSocketTransport::WebSocketTransport() : _useMask(true), _readyState(ReadyState::CLOSED), - _closeCode(kInternalErrorCode), - _closeReason(kInternalErrorMessage), + _closeCode(WebSocketCloseConstants::kInternalErrorCode), + _closeReason(WebSocketCloseConstants::kInternalErrorMessage), _closeWireSize(0), _closeRemote(false), _enablePerMessageDeflate(false), @@ -221,8 +211,8 @@ namespace ix { std::lock_guard lock(_closeDataMutex); _onCloseCallback(_closeCode, _closeReason, _closeWireSize, _closeRemote); - _closeCode = kInternalErrorCode; - _closeReason = kInternalErrorMessage; + _closeCode = WebSocketCloseConstants::kInternalErrorCode; + _closeReason = WebSocketCloseConstants::kInternalErrorMessage; _closeWireSize = 0; _closeRemote = false; } @@ -292,7 +282,8 @@ namespace ix // ping response (PONG) exceeds the maximum delay, then close the connection if (pingTimeoutExceeded()) { - close(kInternalErrorCode, kPingTimeoutMessage); + close(WebSocketCloseConstants::kInternalErrorCode, + WebSocketCloseConstants::kPingTimeoutMessage); } // If ping is enabled and no ping has been sent for a duration // exceeding our ping interval, send a ping to the server. @@ -637,8 +628,8 @@ namespace ix else { // no close code received - code = kNoStatusCodeErrorCode; - reason = kNoStatusCodeErrorMessage; + code = WebSocketCloseConstants::kNoStatusCodeErrorCode; + reason = WebSocketCloseConstants::kNoStatusCodeErrorMessage; } // We receive a CLOSE frame from remote and are NOT the ones who triggered the close @@ -672,7 +663,9 @@ namespace ix else { // Unexpected frame type - close(kProtocolErrorCode, kProtocolErrorMessage, _rxbuf.size()); + close(WebSocketCloseConstants::kProtocolErrorCode, + WebSocketCloseConstants::kProtocolErrorMessage, + _rxbuf.size()); } // Erase the message that has been processed from the input/read buffer @@ -695,7 +688,9 @@ namespace ix // if we weren't closing, then close using abnormal close code and message else if (_readyState != ReadyState::CLOSED) { - closeSocketAndSwitchToClosedState(kAbnormalCloseCode, kAbnormalCloseMessage, 0, false); + closeSocketAndSwitchToClosedState(WebSocketCloseConstants::kAbnormalCloseCode, + WebSocketCloseConstants::kAbnormalCloseMessage, + 0, false); } } } @@ -1001,7 +996,7 @@ namespace ix bool compress = false; // if a status is set/was read - if (code != kNoStatusCodeErrorCode) + if (code != WebSocketCloseConstants::kNoStatusCodeErrorCode) { // See list of close events here: // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent diff --git a/ixwebsocket/IXWebSocketTransport.h b/ixwebsocket/IXWebSocketTransport.h index 6aa1ac82..19dfafcf 100644 --- a/ixwebsocket/IXWebSocketTransport.h +++ b/ixwebsocket/IXWebSocketTransport.h @@ -25,6 +25,7 @@ #include "IXCancellationRequest.h" #include "IXWebSocketHandshake.h" #include "IXProgressCallback.h" +#include "IXWebSocketCloseConstants.h" namespace ix { @@ -91,8 +92,8 @@ namespace ix const OnProgressCallback& onProgressCallback); WebSocketSendInfo sendPing(const std::string& message); - void close(uint16_t code = 1000, - const std::string& reason = "Normal closure", + void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode, + const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage, size_t closeWireSize = 0, bool remote = false); From 3323a51ab5709d82a582d58ffd7a0280a77232bf Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Thu, 16 May 2019 13:56:25 -0700 Subject: [PATCH 48/53] try to run ws test on linux + macOS on travis --- .travis.yml | 4 ++-- makefile | 3 ++- ws/test_ws.sh | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5fadfeec..335a350d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,14 +7,14 @@ matrix: compiler: clang script: - python test/run.py - - make ws + - make ws_test # Linux - os: linux dist: xenial script: - python test/run.py - - make ws + - make ws_test env: - CC=gcc - CXX=g++ diff --git a/makefile b/makefile index 8d5f15c4..310f13ab 100644 --- a/makefile +++ b/makefile @@ -52,7 +52,7 @@ test: python2.7 test/run.py ws_test: all - (cd ws ; bash test_ws.sh) + (cd ws ; env DEBUG=1 PATH=../ws/build:$$PATH bash test_ws.sh) # For the fork that is configured with appveyor rebase_upstream: @@ -67,3 +67,4 @@ install_cmake_for_linux: .PHONY: test .PHONY: build +.PHONY: ws diff --git a/ws/test_ws.sh b/ws/test_ws.sh index 587fd332..f2e94aa1 100644 --- a/ws/test_ws.sh +++ b/ws/test_ws.sh @@ -61,4 +61,4 @@ sleep 2 kill `cat /tmp/ws_test/pidfile.transfer` kill `cat /tmp/ws_test/pidfile.receive` kill `cat /tmp/ws_test/pidfile.send` - +exit 0 From f746070944912630088e06f575e9a8bd9c22e8cb Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Thu, 16 May 2019 14:02:24 -0700 Subject: [PATCH 49/53] travis makefile fix --- makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makefile b/makefile index 310f13ab..caa57211 100644 --- a/makefile +++ b/makefile @@ -51,7 +51,7 @@ test_server: test: python2.7 test/run.py -ws_test: all +ws_test: ws (cd ws ; env DEBUG=1 PATH=../ws/build:$$PATH bash test_ws.sh) # For the fork that is configured with appveyor From b11678e63601865422ac180957f89a75ccea1bc1 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Thu, 16 May 2019 14:25:31 -0700 Subject: [PATCH 50/53] refactor connect unittest so that it hits a local server instead of a remote server --- .travis.yml | 4 +-- test/IXSocketConnectTest.cpp | 12 ++++++-- test/IXSocketTest.cpp | 57 +----------------------------------- test/IXTest.cpp | 54 ++++++++++++++++++++++++++++++++++ test/IXTest.h | 3 ++ 5 files changed, 70 insertions(+), 60 deletions(-) diff --git a/.travis.yml b/.travis.yml index 335a350d..5fadfeec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,14 +7,14 @@ matrix: compiler: clang script: - python test/run.py - - make ws_test + - make ws # Linux - os: linux dist: xenial script: - python test/run.py - - make ws_test + - make ws env: - CC=gcc - CXX=g++ diff --git a/test/IXSocketConnectTest.cpp b/test/IXSocketConnectTest.cpp index 1f98173d..aeeb63bd 100644 --- a/test/IXSocketConnectTest.cpp +++ b/test/IXSocketConnectTest.cpp @@ -17,8 +17,12 @@ TEST_CASE("socket_connect", "[net]") { SECTION("Test connecting to a known hostname") { + int port = getFreePort(); + ix::WebSocketServer server(port); + REQUIRE(startWebSocketEchoServer(server)); + std::string errMsg; - int fd = SocketConnect::connect("www.google.com", 80, errMsg, [] { return false; }); + int fd = SocketConnect::connect("127.0.0.1", port, errMsg, [] { return false; }); std::cerr << "Error message: " << errMsg << std::endl; REQUIRE(fd != -1); } @@ -34,9 +38,13 @@ TEST_CASE("socket_connect", "[net]") SECTION("Test connecting to a good hostname, with cancellation") { + int port = getFreePort(); + ix::WebSocketServer server(port); + REQUIRE(startWebSocketEchoServer(server)); + std::string errMsg; // The callback returning true means we are requesting cancellation - int fd = SocketConnect::connect("www.google.com", 80, errMsg, [] { return true; }); + int fd = SocketConnect::connect("127.0.0.1", port, errMsg, [] { return true; }); std::cerr << "Error message: " << errMsg << std::endl; REQUIRE(fd == -1); } diff --git a/test/IXSocketTest.cpp b/test/IXSocketTest.cpp index 279ad1ad..7c689c09 100644 --- a/test/IXSocketTest.cpp +++ b/test/IXSocketTest.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include "IXTest.h" #include "catch.hpp" @@ -50,60 +49,6 @@ namespace ix REQUIRE(sscanf(line.c_str(), "HTTP/1.1 %d", &status) == 1); REQUIRE(status == expectedStatus); } - - bool startServer(ix::WebSocketServer& server) - { - server.setOnConnectionCallback( - [&server](std::shared_ptr webSocket, - std::shared_ptr connectionState) - { - webSocket->setOnMessageCallback( - [webSocket, connectionState, &server](ix::WebSocketMessageType messageType, - const std::string& str, - size_t wireSize, - const ix::WebSocketErrorInfo& error, - const ix::WebSocketOpenInfo& openInfo, - const ix::WebSocketCloseInfo& closeInfo) - { - if (messageType == ix::WebSocketMessageType::Open) - { - Logger() << "New connection"; - Logger() << "Uri: " << openInfo.uri; - Logger() << "Headers:"; - for (auto it : openInfo.headers) - { - Logger() << it.first << ": " << it.second; - } - } - else if (messageType == ix::WebSocketMessageType::Close) - { - Logger() << "Closed connection"; - } - else if (messageType == ix::WebSocketMessageType::Message) - { - for (auto&& client : server.getClients()) - { - if (client != webSocket) - { - client->send(str); - } - } - } - } - ); - } - ); - - auto res = server.listen(); - if (!res.first) - { - Logger() << res.second; - return false; - } - - server.start(); - return true; - } } TEST_CASE("socket", "[socket]") @@ -113,7 +58,7 @@ TEST_CASE("socket", "[socket]") // Start a server first which we'll hit with our socket code int port = getFreePort(); ix::WebSocketServer server(port); - REQUIRE(startServer(server)); + REQUIRE(startWebSocketEchoServer(server)); std::string errMsg; bool tls = false; diff --git a/test/IXTest.cpp b/test/IXTest.cpp index 886df20f..d9e2affb 100644 --- a/test/IXTest.cpp +++ b/test/IXTest.cpp @@ -170,4 +170,58 @@ namespace ix std::cout << prefix << ": " << s << " => " << ss.str() << std::endl; } + + bool startWebSocketEchoServer(ix::WebSocketServer& server) + { + server.setOnConnectionCallback( + [&server](std::shared_ptr webSocket, + std::shared_ptr connectionState) + { + webSocket->setOnMessageCallback( + [webSocket, connectionState, &server](ix::WebSocketMessageType messageType, + const std::string& str, + size_t wireSize, + const ix::WebSocketErrorInfo& error, + const ix::WebSocketOpenInfo& openInfo, + const ix::WebSocketCloseInfo& closeInfo) + { + if (messageType == ix::WebSocketMessageType::Open) + { + Logger() << "New connection"; + Logger() << "Uri: " << openInfo.uri; + Logger() << "Headers:"; + for (auto it : openInfo.headers) + { + Logger() << it.first << ": " << it.second; + } + } + else if (messageType == ix::WebSocketMessageType::Close) + { + Logger() << "Closed connection"; + } + else if (messageType == ix::WebSocketMessageType::Message) + { + for (auto&& client : server.getClients()) + { + if (client != webSocket) + { + client->send(str); + } + } + } + } + ); + } + ); + + auto res = server.listen(); + if (!res.first) + { + Logger() << res.second; + return false; + } + + server.start(); + return true; + } } diff --git a/test/IXTest.h b/test/IXTest.h index adf263af..c65b265a 100644 --- a/test/IXTest.h +++ b/test/IXTest.h @@ -12,6 +12,7 @@ #include #include #include +#include namespace ix { @@ -46,4 +47,6 @@ namespace ix void log(const std::string& msg); int getFreePort(); + + bool startWebSocketEchoServer(ix::WebSocketServer& server); } From b7db5f77fb6dcc663dbf84d57f485befd4a00887 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Thu, 16 May 2019 15:05:20 -0700 Subject: [PATCH 51/53] remove dead code --- ixwebsocket/IXWebSocketTransport.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ixwebsocket/IXWebSocketTransport.h b/ixwebsocket/IXWebSocketTransport.h index 19dfafcf..96d8cae0 100644 --- a/ixwebsocket/IXWebSocketTransport.h +++ b/ixwebsocket/IXWebSocketTransport.h @@ -179,17 +179,6 @@ namespace ix std::chrono::time_point_closingTimePoint; static const int kClosingMaximumWaitingDelayInMs; - // Constants for dealing with closing conneections - static const uint16_t kInternalErrorCode; - static const uint16_t kAbnormalCloseCode; - static const uint16_t kProtocolErrorCode; - static const uint16_t kNoStatusCodeErrorCode; - static const std::string kInternalErrorMessage; - static const std::string kAbnormalCloseMessage; - static const std::string kPingTimeoutMessage; - static const std::string kProtocolErrorMessage; - static const std::string kNoStatusCodeErrorMessage; - // enable auto response to ping std::atomic _enablePong; static const bool kDefaultEnablePong; From a696264b4893c4bd0d454425766a25d9b70d372c Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Thu, 16 May 2019 15:46:32 -0700 Subject: [PATCH 52/53] disable socket mutex usage in WebSocketTransport --- ixwebsocket/IXWebSocketTransport.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ixwebsocket/IXWebSocketTransport.cpp b/ixwebsocket/IXWebSocketTransport.cpp index 9741aae9..b872a750 100644 --- a/ixwebsocket/IXWebSocketTransport.cpp +++ b/ixwebsocket/IXWebSocketTransport.cpp @@ -142,7 +142,7 @@ namespace ix std::string errorMsg; { bool tls = protocol == "wss"; - std::lock_guard lock(_socketMutex); + // std::lock_guard lock(_socketMutex); _socket = createSocket(tls, errorMsg); if (!_socket) @@ -174,7 +174,7 @@ namespace ix std::string errorMsg; { - std::lock_guard lock(_socketMutex); + // std::lock_guard lock(_socketMutex); _socket = createSocket(fd, errorMsg); if (!_socket) @@ -962,7 +962,7 @@ namespace ix ssize_t WebSocketTransport::send() { - std::lock_guard lock(_socketMutex); + // std::lock_guard lock(_socketMutex); return _socket->send((char*)&_txbuf[0], _txbuf.size()); } @@ -1016,7 +1016,7 @@ namespace ix void WebSocketTransport::closeSocket() { - std::lock_guard lock(_socketMutex); + // std::lock_guard lock(_socketMutex); _socket->close(); } From 773cbb490759211c5d1c30530e5fec303e141aba Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Thu, 16 May 2019 15:58:20 -0700 Subject: [PATCH 53/53] bring back socket mutex which is needed, some CI failures are happening without it --- ixwebsocket/IXWebSocketTransport.cpp | 32 +++++++++++++--------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/ixwebsocket/IXWebSocketTransport.cpp b/ixwebsocket/IXWebSocketTransport.cpp index b872a750..12199bfe 100644 --- a/ixwebsocket/IXWebSocketTransport.cpp +++ b/ixwebsocket/IXWebSocketTransport.cpp @@ -130,6 +130,8 @@ namespace ix WebSocketInitResult WebSocketTransport::connectToUrl(const std::string& url, int timeoutSecs) { + std::lock_guard lock(_socketMutex); + std::string protocol, host, path, query; int port; @@ -140,15 +142,12 @@ namespace ix } std::string errorMsg; - { - bool tls = protocol == "wss"; - // std::lock_guard lock(_socketMutex); - _socket = createSocket(tls, errorMsg); + bool tls = protocol == "wss"; + _socket = createSocket(tls, errorMsg); - if (!_socket) - { - return WebSocketInitResult(false, 0, errorMsg); - } + if (!_socket) + { + return WebSocketInitResult(false, 0, errorMsg); } WebSocketHandshake webSocketHandshake(_requestInitCancellation, @@ -169,18 +168,17 @@ namespace ix // Server WebSocketInitResult WebSocketTransport::connectToSocket(int fd, int timeoutSecs) { + std::lock_guard lock(_socketMutex); + // Server should not mask the data it sends to the client _useMask = false; std::string errorMsg; - { - // std::lock_guard lock(_socketMutex); - _socket = createSocket(fd, errorMsg); + _socket = createSocket(fd, errorMsg); - if (!_socket) - { - return WebSocketInitResult(false, 0, errorMsg); - } + if (!_socket) + { + return WebSocketInitResult(false, 0, errorMsg); } WebSocketHandshake webSocketHandshake(_requestInitCancellation, @@ -962,7 +960,7 @@ namespace ix ssize_t WebSocketTransport::send() { - // std::lock_guard lock(_socketMutex); + std::lock_guard lock(_socketMutex); return _socket->send((char*)&_txbuf[0], _txbuf.size()); } @@ -1016,7 +1014,7 @@ namespace ix void WebSocketTransport::closeSocket() { - // std::lock_guard lock(_socketMutex); + std::lock_guard lock(_socketMutex); _socket->close(); }