mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Let more than one thing receive the log stream at a time
The log manager had a single external-callback slot, but the WebSocket debugger registers one per *connection* - LogBroadcaster is a local in the per-connection handler. So with two clients attached (the bundled JS debugger in a browser and Tools/wsdbg, say) the second one to connect silently took the log stream away from the first, and then whichever disconnected first cleared the slot and stopped delivery to the other as well. A one-shot wsdbg command is enough to do it: connect, take the stream, exit, and the long-lived listener that was watching the log goes quiet with nothing to say why. Make it a list with add/remove by handle. The dispatch loop holds the lock across the callbacks so a listener can't be freed while one is running - which is what lets LogBroadcaster delete its listener straight after removing it. Enabling and disabling LogOutput::ExternalCallback belongs to the list now, and disabling only happens when the last callback goes away. libretro registers one of these too, and never removes it; it just moves to the new call. It can't actually collide with the debugger - the libretro build doesn't compile Core/Debugger/WebSocket at all - but there's no reason for it to keep using an API that only has room for one caller. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
b0af7a8dfe
commit
9799085c3a
5 files changed
+62
-15
No files matched your search
@@ -364,12 +364,44 @@ void LogManager::LogLine(LogLevel level, Log type, const char *file, int line, c
|
||||
#endif
|
||||
|
||||
if (outputs_ & LogOutput::ExternalCallback) {
|
||||
if (externalCallback_) {
|
||||
externalCallback_(message, externalUserData_);
|
||||
// Held across the dispatch on purpose: RemoveExternalLogCallback() takes the same lock, so a
|
||||
// listener can't be torn down (and its userdata freed) while we're in the middle of calling it.
|
||||
std::lock_guard<std::mutex> guard(externalLock_);
|
||||
for (const ExternalCallbackEntry &entry : externalCallbacks_) {
|
||||
entry.callback(message, entry.userdata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int LogManager::AddExternalLogCallback(LogCallback callback, void *userdata) {
|
||||
if (!callback) {
|
||||
return -1;
|
||||
}
|
||||
std::lock_guard<std::mutex> guard(externalLock_);
|
||||
const int handle = nextExternalHandle_++;
|
||||
externalCallbacks_.push_back(ExternalCallbackEntry{ handle, callback, userdata });
|
||||
EnableOutput(LogOutput::ExternalCallback);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void LogManager::RemoveExternalLogCallback(int handle) {
|
||||
if (handle < 0) {
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> guard(externalLock_);
|
||||
for (size_t i = 0; i < externalCallbacks_.size(); i++) {
|
||||
if (externalCallbacks_[i].handle == handle) {
|
||||
externalCallbacks_.erase(externalCallbacks_.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Only when the last one goes away - otherwise removing one connection's callback would stop
|
||||
// delivery to the ones still attached, which is the bug this list exists to avoid.
|
||||
if (externalCallbacks_.empty()) {
|
||||
DisableOutput(LogOutput::ExternalCallback);
|
||||
}
|
||||
}
|
||||
|
||||
void RingbufferLog::Log(const LogMessage &message) {
|
||||
std::lock_guard<std::mutex> lock(ringLock_);
|
||||
messages_[curMessage_] = message;
|
||||
|
||||
+18
-7
@@ -158,10 +158,15 @@ public:
|
||||
void Init(bool *enabledSetting, bool headless = false);
|
||||
void Shutdown();
|
||||
|
||||
void SetExternalLogCallback(LogCallback callback, void *userdata) {
|
||||
externalCallback_ = callback;
|
||||
externalUserData_ = userdata;
|
||||
}
|
||||
// A list rather than a single slot, because the WebSocket debugger registers one callback per
|
||||
// *connection* (LogBroadcaster is a local in the per-connection handler) and more than one
|
||||
// client can be attached at once - the bundled JS debugger in a browser alongside Tools/wsdbg,
|
||||
// say. With a single slot the second connection silently took the log stream away from the
|
||||
// first, and then the first one to disconnect cleared the slot and stopped delivery to the
|
||||
// other as well.
|
||||
// Returns a handle to hand back to RemoveExternalLogCallback(), or -1 if it wasn't added.
|
||||
int AddExternalLogCallback(LogCallback callback, void *userdata);
|
||||
void RemoveExternalLogCallback(int handle);
|
||||
|
||||
void SetFileLogPath(const Path &filename);
|
||||
const Path &GetLogFilePath() const { return logFilename_; }
|
||||
@@ -215,9 +220,15 @@ private:
|
||||
// Ring buffer
|
||||
RingbufferLog ringLog_;
|
||||
|
||||
// Callback
|
||||
LogCallback externalCallback_ = nullptr;
|
||||
void *externalUserData_ = nullptr;
|
||||
// Callbacks
|
||||
struct ExternalCallbackEntry {
|
||||
int handle;
|
||||
LogCallback callback;
|
||||
void *userdata;
|
||||
};
|
||||
std::mutex externalLock_;
|
||||
std::vector<ExternalCallbackEntry> externalCallbacks_;
|
||||
int nextExternalHandle_ = 1;
|
||||
};
|
||||
|
||||
extern LogManager g_logManager;
|
||||
@@ -100,13 +100,14 @@ static void BroadcastCallback(const LogMessage &message, void *userdata) {
|
||||
|
||||
LogBroadcaster::LogBroadcaster() {
|
||||
listener_ = new DebuggerLogListener();
|
||||
g_logManager.SetExternalLogCallback(&BroadcastCallback, (void *)listener_);
|
||||
g_logManager.EnableOutput(LogOutput::ExternalCallback);
|
||||
// One of these exists per open connection, so it registers alongside any other client's
|
||||
// rather than replacing it - see AddExternalLogCallback().
|
||||
callbackHandle_ = g_logManager.AddExternalLogCallback(&BroadcastCallback, (void *)listener_);
|
||||
}
|
||||
|
||||
LogBroadcaster::~LogBroadcaster() {
|
||||
g_logManager.DisableOutput(LogOutput::ExternalCallback);
|
||||
g_logManager.SetExternalLogCallback(nullptr, nullptr);
|
||||
// Returns only once no log call is inside our callback, so the listener is safe to delete.
|
||||
g_logManager.RemoveExternalLogCallback(callbackHandle_);
|
||||
delete listener_;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,4 +32,5 @@ public:
|
||||
|
||||
private:
|
||||
DebuggerLogListener *listener_;
|
||||
int callbackHandle_ = -1;
|
||||
};
|
||||
@@ -1174,8 +1174,10 @@ void retro_init(void)
|
||||
// auto-detection, which only fires if a debugger was already attached before Init() ran) so the
|
||||
// log always shows up in the debugger's Output window when debugging the core in-process with
|
||||
// RetroArch, regardless of where RetroArch itself routes the ExternalCallback log messages.
|
||||
g_logManager.EnableOutput(LogOutput::ExternalCallback | LogOutput::DebugString);
|
||||
g_logManager.SetExternalLogCallback(&RetroLogCallback, (void *)log_cb);
|
||||
g_logManager.EnableOutput(LogOutput::DebugString);
|
||||
// AddExternalLogCallback() enables LogOutput::ExternalCallback itself. Never removed - this
|
||||
// callback lives as long as the core does.
|
||||
g_logManager.AddExternalLogCallback(&RetroLogCallback, (void *)log_cb);
|
||||
}
|
||||
|
||||
VsyncSwapIntervalReset();
|
||||
|
||||
Reference in new issue
Block a user