-
-
Notifications
You must be signed in to change notification settings - Fork 540
Commit
checkSockets()
function to be usable with v…
…arious polling APIs The patch introduces the following abstractions to make it easier to switch between various polling APIs in the TCP_DIRECT netcode: 1. `IDescriptorSet` interface to abstract away the details of a particular polling API (`select()` and `poll()` are supported). 2. `PollEventType` enumeration to be used in concrete subclasses of `IDescriptorSet` to describe the type of events we are interested in, when polling a given descriptor set (generally speaking, both `select` and `poll` can listen for multiple types of events simultaneously, but in our particular case we listen for only one of them at a time). 3. `SelectDescriptorSet<PollEventType>` and `PollDescriptorSet<PollEventType>` descriptor set types which actually implement the support for `select` and `poll` APIs. 4. Helper function `tcp::pollImpl(descriptorSet, timeout)` for automatically retrying a polling operation upon encountering `EINTR` or `EAGAIN` signals. 5. `checkSocketsReadable()` function now makes direct use of `SelectDescriptorSet`(Windows, note on support below) and `PollDescriptorSet`(Linux/macOS/*BSD). NOTE: We don't use `poll()`(`WSAPoll()`, to be accurate) on Windows for the time being, since it's affected by a bug in Windows versions prior to Windows 10 version 2004 (and there wasn't any prior analysis on how many potential players will be affected): `WSAPoll()` function can time out on socket connection errors instead of returning an error early. For more information on the bug, see: https://stackoverflow.com/questions/21653003/is-this-wsapoll-bug-for-non-blocking-sockets-fixed and also https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll#remarks Signed-off-by: Pavel Solodovnikov <pavel.al.solodovnikov@gmail.com>
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
|
||
/* | ||
This file is part of Warzone 2100. | ||
Copyright (C) 2025 Warzone 2100 Project | ||
Warzone 2100 is free software; you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation; either version 2 of the License, or | ||
(at your option) any later version. | ||
Warzone 2100 is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
You should have received a copy of the GNU General Public License | ||
along with Warzone 2100; if not, write to the Free Software | ||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include "lib/framework/wzglobal.h" | ||
|
||
#ifdef WZ_OS_WIN | ||
# include <winsock2.h> | ||
#elif defined(WZ_OS_UNIX) | ||
using SOCKET = int; | ||
#endif | ||
|
||
#include <chrono> | ||
|
||
namespace tcp | ||
{ | ||
|
||
enum class PollEventType | ||
{ | ||
READABLE, | ||
WRITABLE | ||
}; | ||
|
||
/// <summary> | ||
/// Abstract class for describing polling descriptor sets used by various polling APIs (e.g. `select()` and `poll()`). | ||
/// </summary> | ||
class IDescriptorSet | ||
{ | ||
public: | ||
|
||
virtual ~IDescriptorSet() = default; | ||
|
||
virtual void add(SOCKET fd) = 0; | ||
virtual void clear() = 0; | ||
|
||
/// <summary> | ||
/// Polling algorithm implementation for this descriptor set kind. | ||
/// Should represent the same semantics as `select()` or `poll()` APIs, i.e. after calling `pollImpl()` one | ||
/// should check individual descriptors via `isSet()` to see, which of them were marked as ready. | ||
/// </summary> | ||
/// <param name="timeout">Timeout value in milliseconds</param> | ||
/// <returns> | ||
/// Error code from internal polling API, per `select()` or `poll()` API documentation: | ||
/// * -1 (SOCKET_ERROR) on error | ||
/// * 0 on timeout (none of the polling descriptors were ready by the end of an internal polling function call) | ||
/// * <num_ready_fds> - positive number of ready descriptors | ||
/// </returns> | ||
virtual int pollImpl(std::chrono::milliseconds timeout) = 0; | ||
|
||
virtual bool isSet(SOCKET fd) const = 0; | ||
}; | ||
|
||
} // namespace tcp |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
|
||
/* | ||
This file is part of Warzone 2100. | ||
Copyright (C) 2025 Warzone 2100 Project | ||
Warzone 2100 is free software; you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation; either version 2 of the License, or | ||
(at your option) any later version. | ||
Warzone 2100 is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
You should have received a copy of the GNU General Public License | ||
along with Warzone 2100; if not, write to the Free Software | ||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include "descriptor_set.h" | ||
|
||
#include "lib/framework/frame.h" // for MAX_PLAYERS | ||
|
||
#ifdef WZ_OS_WIN | ||
# include <winsock2.h> | ||
#elif defined(WZ_OS_UNIX) | ||
# include <poll.h> // for pollfd, poll | ||
#endif | ||
|
||
#include <array> | ||
#include <stdexcept> | ||
|
||
namespace tcp | ||
{ | ||
|
||
/// <summary> | ||
/// Descriptor set interface specialization using the `poll()` API for actual polling. | ||
/// </summary> | ||
/// <typeparam name="EventType">Type of updates (readable/writable sockets) to poll for.</typeparam> | ||
template <PollEventType EventType> | ||
class PollDescriptorSet : public IDescriptorSet | ||
{ | ||
public: | ||
|
||
explicit PollDescriptorSet() = default; | ||
|
||
virtual void add(SOCKET fd) override | ||
{ | ||
constexpr short evt = EventType == PollEventType::READABLE ? POLLIN : POLLOUT; | ||
|
||
assert(size_ < MAX_PLAYERS); | ||
if (size_ >= MAX_PLAYERS) | ||
{ | ||
throw std::runtime_error("Too many poll descriptors (>= MAX_PLAYERS)"); | ||
} | ||
fds_[size_++] = { fd, evt, 0 }; | ||
} | ||
|
||
virtual void clear() override | ||
{ | ||
fds_.assign({}); | ||
Check failure on line 65 in lib/netplay/tcp/poll_descriptor_set.h
|
||
size_ = 0; | ||
} | ||
|
||
virtual int pollImpl(std::chrono::milliseconds timeout) override | ||
{ | ||
#ifdef WZ_OS_WIN | ||
return WSAPoll(fds_.data(), size_, timeout.count()); | ||
#else | ||
return poll(fds_.data(), size_, timeout.count()); | ||
#endif | ||
} | ||
|
||
virtual bool isSet(SOCKET fd) const override | ||
{ | ||
constexpr short evt = EventType == PollEventType::READABLE ? POLLIN : POLLOUT; | ||
|
||
const auto it = std::find_if(fds_.begin(), fds_.end(), [fd](const pollfd& pfd) { return pfd.fd == fd; }); | ||
return it != fds_.end() && (it->revents & evt); | ||
} | ||
|
||
private: | ||
|
||
std::array<pollfd, MAX_PLAYERS> fds_; | ||
size_t size_ = 0; | ||
}; | ||
|
||
} // namespace tcp |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
|
||
/* | ||
This file is part of Warzone 2100. | ||
Copyright (C) 2025 Warzone 2100 Project | ||
Warzone 2100 is free software; you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation; either version 2 of the License, or | ||
(at your option) any later version. | ||
Warzone 2100 is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
You should have received a copy of the GNU General Public License | ||
along with Warzone 2100; if not, write to the Free Software | ||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
*/ | ||
|
||
#include "polling_algo_impl.h" | ||
#include "descriptor_set.h" | ||
|
||
#ifndef WZ_OS_WIN | ||
static const int SOCKET_ERROR = -1; | ||
#else | ||
# include <winsock2.h> // for SOCKET_ERROR | ||
#endif | ||
|
||
namespace tcp | ||
{ | ||
|
||
int pollImpl(IDescriptorSet& descriptorSet, std::chrono::milliseconds timeout) | ||
{ | ||
int ret; | ||
do | ||
{ | ||
ret = descriptorSet.pollImpl(timeout); | ||
if (ret == SOCKET_ERROR) | ||
{ | ||
return SOCKET_ERROR; | ||
} | ||
} while (errno == EINTR || errno == EAGAIN); | ||
Check failure on line 44 in lib/netplay/tcp/polling_algo_impl.cpp
|
||
return ret; | ||
} | ||
|
||
} // namespace tcp |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
|
||
/* | ||
This file is part of Warzone 2100. | ||
Copyright (C) 2025 Warzone 2100 Project | ||
Warzone 2100 is free software; you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation; either version 2 of the License, or | ||
(at your option) any later version. | ||
Warzone 2100 is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
You should have received a copy of the GNU General Public License | ||
along with Warzone 2100; if not, write to the Free Software | ||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include "descriptor_set.h" | ||
|
||
#include <chrono> | ||
|
||
namespace tcp | ||
{ | ||
|
||
class IDescriptorImpl; | ||
|
||
/// <summary> | ||
/// Helper function to call into the internal polling API via given descriptor set. | ||
/// Automatically retries upon encountering `EAGAIN` and `EINTR` signals. | ||
/// </summary> | ||
/// <param name="descriptorSet"></param> | ||
/// <param name="timeout"></param> | ||
/// <returns></returns> | ||
int pollImpl(IDescriptorSet& descriptorSet, std::chrono::milliseconds timeout); | ||
|
||
} // namespace tcp |