Fix #9388: thread unsafe use of NetworkAdminConsole/IConsolePrint

(cherry picked from commit d7a0e80fc18b689455eaca09883fccbcc722d7e3)

# Conflicts:
#	src/debug.cpp
#	src/debug.h
#	src/table/settings/gui_settings.ini

See: https://github.com/OpenTTD/OpenTTD/pull/9456
This commit is contained in:
Rubidium
2021-07-23 21:36:17 +01:00
committed by Jonathan G Rennison
parent 63d8f3bdd7
commit 1c8f4bfaef
8 changed files with 95 additions and 17 deletions

View File

@@ -15,7 +15,9 @@
#include "fileio_func.h"
#include "settings_type.h"
#include "date_func.h"
#include "thread.h"
#include <array>
#include <mutex>
#if defined(_WIN32)
#include "os/windows/win32.h"
@@ -32,6 +34,16 @@ SOCKET _debug_socket = INVALID_SOCKET;
#include "safeguards.h"
/** Element in the queue of debug messages that have to be passed to either NetworkAdminConsole or IConsolePrint.*/
struct QueuedDebugItem {
std::string level; ///< The used debug level.
std::string message; ///< The actual formatted message.
};
std::atomic<bool> _debug_remote_console; ///< Whether we need to send data to either NetworkAdminConsole or IConsolePrint.
std::mutex _debug_remote_console_mutex; ///< Mutex to guard the queue of debug messages for either NetworkAdminConsole or IConsolePrint.
std::vector<QueuedDebugItem> _debug_remote_console_queue; ///< Queue for debug messages to be passed to NetworkAdminConsole or IConsolePrint.
std::vector<QueuedDebugItem> _debug_remote_console_queue_spare; ///< Spare queue to swap with _debug_remote_console_queue.
int _debug_driver_level;
int _debug_grf_level;
int _debug_map_level;
@@ -130,6 +142,11 @@ static void debug_print(const char *dbg, const char *buf)
char buf2[1024 + 32];
seprintf(buf2, lastof(buf2), "%sdbg: [%s] %s\n", GetLogPrefix(), dbg, buf);
/* Prevent sending a message concurrently, as that might cause interleaved messages. */
static std::mutex _debug_socket_mutex;
std::lock_guard<std::mutex> lock(_debug_socket_mutex);
/* Sending out an error when this fails would be nice, however... the error
* would have to be send over this failing socket which won't work. */
send(_debug_socket, buf2, (int)strlen(buf2), 0);
@@ -192,8 +209,16 @@ static void debug_print(const char *dbg, const char *buf)
fputs(buffer, stderr);
#endif
NetworkAdminConsole(dbg, buf);
IConsoleDebug(dbg, buf);
if (_debug_remote_console.load()) {
/* Only add to the queue when there is at least one consumer of the data. */
if (IsNonGameThread()) {
std::lock_guard<std::mutex> lock(_debug_remote_console_mutex);
_debug_remote_console_queue.push_back({ dbg, buf });
} else {
NetworkAdminConsole(dbg, buf);
if (_settings_client.gui.developer >= 2) IConsolePrintF(CC_DEBUG, "dbg: [%s] %s", dbg, buf);
}
}
}
/**
@@ -403,3 +428,50 @@ void LogRemoteDesyncMsg(Date date, DateFract date_fract, uint8 tick_skip_counter
entry.src_id = src_id;
_remote_desync_msg_log.LogMsg(std::move(entry));
}
/**
* Send the queued Debug messages to either NetworkAdminConsole or IConsolePrint from the
* GameLoop thread to prevent concurrent accesses to both the NetworkAdmin's packet queue
* as well as IConsolePrint's buffers.
*
* This is to be called from the GameLoop thread.
*/
void DebugSendRemoteMessages()
{
if (!_debug_remote_console.load()) return;
{
std::lock_guard<std::mutex> lock(_debug_remote_console_mutex);
std::swap(_debug_remote_console_queue, _debug_remote_console_queue_spare);
}
for (auto &item : _debug_remote_console_queue_spare) {
NetworkAdminConsole(item.level.c_str(), item.message.c_str());
if (_settings_client.gui.developer >= 2) IConsolePrintF(CC_DEBUG, "dbg: [%s] %s", item.level.c_str(), item.message.c_str());
}
_debug_remote_console_queue_spare.clear();
}
/**
* Reconsider whether we need to send debug messages to either NetworkAdminConsole
* or IConsolePrint. The former is when they have enabled console handling whereas
* the latter depends on the gui.developer setting's value.
*
* This is to be called from the GameLoop thread.
*/
void DebugReconsiderSendRemoteMessages()
{
bool enable = _settings_client.gui.developer >= 2;
if (!enable) {
for (ServerNetworkAdminSocketHandler *as : ServerNetworkAdminSocketHandler::IterateActive()) {
if (as->update_frequency[ADMIN_UPDATE_CONSOLE] & ADMIN_FREQUENCY_AUTOMATIC) {
enable = true;
break;
}
}
}
_debug_remote_console.store(enable);
}