Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

guard against double-connect attempts on socket #150

Merged
merged 7 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project are documented below.

The format is based on [keep a changelog](http://keepachangelog.com/) and this project uses [semantic versioning](http://semver.org/).

### [2.8.3] - [2023-10-31]
### Fixed
- `NRtClient` will now guard against multiple connection attempts without disconnecting from the socket.

### [2.8.2] - [2023-10-31]
### Fixed
- Fixed a race condition that could happen during socket connection which would cause `std::future_error` to be thrown.
Expand Down
23 changes: 12 additions & 11 deletions core/core-rt/NRtClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ void NRtClient::setListener(NRtClientListenerInterface * listener)

void NRtClient::connect(NSessionPtr session, bool createStatus, NRtClientProtocol protocol)
{
if (_transport->isConnected() || _transport->isConnecting())
{
return;
}

std::string url;
NRtTransportType transportType;

Expand Down Expand Up @@ -109,15 +114,11 @@ void NRtClient::connect(NSessionPtr session, bool createStatus, NRtClientProtoco

std::future<void> NRtClient::connectAsync(NSessionPtr session, bool createStatus, NRtClientProtocol protocol)
{
try
if (_transport->isConnected() || _transport->isConnecting())
{
// if old promise not ready, just complete it for the user.
_connectPromise->set_value();
}
catch(const std::future_error&)
{
// if we get an exception here, it means the connect promise has completed already from a previous connect.
// this is very expected. there is no way to check from the promise itself if it has completed already.
std::promise<void> emptyPromise = std::promise<void>();
emptyPromise.set_value();
return emptyPromise.get_future();
}

// stomp the old promise
Expand Down Expand Up @@ -150,9 +151,9 @@ std::future<void> NRtClient::disconnectAsync()
{
// currently, disconnect callback is invoked immediately by client here, so we just return a completed future.
disconnect();
std::promise<void> promise = std::promise<void>();
promise.set_value();
return promise.get_future();
std::promise<void> emptyPromise = std::promise<void>();
emptyPromise.set_value();
return emptyPromise.get_future();
}

void NRtClient::disconnect(const NRtClientDisconnectInfo& info)
Expand Down
5 changes: 5 additions & 0 deletions impl/wsWslay/NWebsocketWslay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,4 +341,9 @@ namespace Nakama {
return;
}
}

bool NWebsocketWslay::isConnecting()
{
return _state == State::Connecting || _state == State::Handshake_Receiving || _state == State::Handshake_Sending;
}
}
3 changes: 3 additions & 0 deletions impl/wsWslay/NWebsocketWslay.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ class NWebsocketWslay : public NRtTransportInterface
void disconnect() override;
bool send(const NBytes& data) override;

protected:
bool isConnecting() override;

private:
static ssize_t recv_callback(wslay_event_context_ptr ctx, uint8_t* data, size_t len, int flags, void* user_data);
static ssize_t send_callback(wslay_event_context_ptr ctx, const uint8_t* data, size_t len, int flags, void* user_data);
Expand Down
5 changes: 5 additions & 0 deletions interface/include/nakama-cpp/realtime/NRtTransportInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ NAKAMA_NAMESPACE_BEGIN
*/
virtual bool isConnected() const { return _connected; }

/**
* @return True if connecting to server.
*/
virtual bool isConnecting() = 0;

/**
* Close the connection with the server.
*
Expand Down
2 changes: 1 addition & 1 deletion test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ If you are building for Mac/iOS, you'll need to set your NAKAMA_TEST_DEVELOPMENT

In-tree example:
```
cd example
cd test
cmake --list-presets
cmake --preset <configure-preset>
cmake --build build/<build-preset> --target install run
Expand Down
54 changes: 54 additions & 0 deletions test/src/realtime/test_lifecycle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

#include <condition_variable>
#include <thread>
#include "NTest.h"
#include "globals.h"
Expand Down Expand Up @@ -116,5 +117,58 @@ namespace Nakama {
test.stopTest(connected);

}

void test_rt_double_connect_async()
{
bool threadedTick = true;

NTest test(__func__, true);
test.runTest();

NSessionPtr session = test.client->authenticateCustomAsync(TestGuid::newGuid(), std::string(), true).get();

bool connected = false;

test.listener.setConnectCallback([&connected](){
connected = true;
});

// should not throw any errors.
test.rtClient->connectAsync(session, true).get();
test.rtClient->connectAsync(session, true).get();

test.stopTest(connected);
}

void test_rt_double_connect()
{
bool threadedTick = true;

NTest test(__func__, true);
test.runTest();

NSessionPtr session = test.client->authenticateCustomAsync(TestGuid::newGuid(), std::string(), true).get();

bool connected = false;
std::mutex mtx;
std::condition_variable cv;

test.listener.setConnectCallback([&](){
std::unique_lock<std::mutex> lock(mtx);
connected = true;
cv.notify_one();
});

// should not throw any errors.
test.rtClient->connect(session, true);
test.rtClient->connect(session, true);

{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&](){ return connected; }); // Wait until `connected` becomes true.
}

test.stopTest(connected);
}
}
}
5 changes: 5 additions & 0 deletions test/src/realtime/test_realtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ void test_rt_quickdestroy();
void test_rt_rapiddisconnect();
void test_rt_reconnect();
void test_rt_connect_callback();
void test_rt_double_connect();
void test_rt_double_connect_async();

void run_realtime_tests()
{
Expand All @@ -53,6 +55,9 @@ void test_realtime()
// These tests are not protocol specific
test_rt_rapiddisconnect();
test_rt_connect_callback();
test_rt_double_connect();
test_rt_double_connect_async();

/// change to 10 iterations to trigger https://github.com/microsoft/libHttpClient/issues/698 bug
for (int i = 0; i < 1; i++) {
test_rt_reconnect();
Expand Down
2 changes: 1 addition & 1 deletion version.cmake
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Easy to update by CI scripts file containing our version as well as
# dependent git repos to achieve reproducible builds

set(LIBNAKAMA_VERSION 2.8.2)
set(LIBNAKAMA_VERSION 2.8.3)
Loading