mumble-voip_mumble/plugins/deadLockPlugin/deadLockPlugin.cpp

173 lines
6.0 KiB
C++

// Copyright 2021-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
// Include the definitions of the plugin functions
// Not that this will also include ../PluginComponents.h
#include "../MumblePlugin.h"
#include <condition_variable>
#include <cstring>
#include <iostream>
#include <mutex>
#include <thread>
MumbleAPI mumAPI;
mumble_connection_t activeConnection;
mumble_plugin_id_t ownID;
std::mutex dummyMutex;
std::condition_variable waiter;
bool dummyRan = false;
bool deadlockPreventionWorks = false;
//////////////////////////////////////////////////////////////
//////////////////// OBLIGATORY FUNCTIONS ////////////////////
//////////////////////////////////////////////////////////////
mumble_error_t mumble_init(uint32_t id) {
ownID = id;
mumAPI.log(ownID, "Intitialized");
// MUMBLE_STATUS_OK is a macro set to the appropriate status flag (ErrorCode)
// If you need to return any other status have a look at the ErrorCode enum
// inside PluginComponents.h and use one of its values
return MUMBLE_STATUS_OK;
}
void mumble_shutdown() {
mumAPI.log(ownID, "Shutdown");
}
MumbleStringWrapper mumble_getName() {
static const char *name = "DeadLockPlugin";
MumbleStringWrapper wrapper;
wrapper.data = name;
wrapper.size = strlen(name);
// It's a static String and therefore doesn't need releasing
wrapper.needsReleasing = false;
return wrapper;
}
mumble_version_t mumble_getAPIVersion() {
// MUMBLE_PLUGIN_API_VERSION will always contain the API version of the used header file (the one used to build
// this plugin against). Thus you should always return that here in order to no have to worry about it.
return MUMBLE_PLUGIN_API_VERSION;
}
void mumble_registerAPIFunctions(void *api) {
// In this function the plugin is presented with a struct of function pointers that can be used
// to interact with Mumble. Thus you should store it somewhere safe for later usage.
// The pointer has to be cast to the respective API struct. You always have to cast to the same API version
// as this plugin itself is using. Thus if this plugin is compiled using the API version 1.0.x (where x is an
// arbitrary version) the pointer has to be cast to MumbleAPI_v_1_0_x (where x is a literal "x"). Furthermore the
// struct HAS TO BE COPIED!!! Storing the pointer is not an option as it will become invalid quickly!
// **If** you are using the same API version that is specified in the included header file (as you should), you
// can simply use the MUMBLE_API_CAST to cast the pointer to the correct type and automatically dereferencing it.
mumAPI = MUMBLE_API_CAST(api);
}
void mumble_releaseResource(const void *pointer) {
std::cerr << "[ERROR]: Unexpected call to mumble_releaseResources" << std::endl;
std::terminate();
// Mark as unused
(void) pointer;
}
//////////////////////////////////////////////////////////////
///////////////////// OPTIONAL FUNCTIONS /////////////////////
//////////////////////////////////////////////////////////////
// The implementation of below functions is optional. If you don't need them, don't include them in your
// plugin
mumble_version_t mumble_getVersion() {
// Mumble uses semantic versioning (see https://semver.org/)
// { major, minor, patch }
return { 1, 0, 0 };
}
MumbleStringWrapper mumble_getAuthor() {
static const char *author = "MumbleDevelopers";
MumbleStringWrapper wrapper;
wrapper.data = author;
wrapper.size = strlen(author);
// It's a static String and therefore doesn't need releasing
wrapper.needsReleasing = false;
return wrapper;
}
MumbleStringWrapper mumble_getDescription() {
static const char *description =
"This plugin intentionally produces a deadlock (if Mumble doesn't prevent it properly). "
"It shouldn't be included in the release build of Mumble.";
MumbleStringWrapper wrapper;
wrapper.data = description;
wrapper.size = strlen(description);
// It's a static String and therefore doesn't need releasing
wrapper.needsReleasing = false;
return wrapper;
}
uint32_t mumble_getFeatures() {
// Tells Mumble whether this plugin delivers some known common functionality. See the PluginFeature enum in
// PluginComponents.h for what is available.
// If you want your plugin to deliver positional data, you'll want to return FEATURE_POSITIONAL
return MUMBLE_FEATURE_NONE;
}
uint32_t mumble_deactivateFeatures(uint32_t features) {
return features;
}
void dummyFunction() {
std::lock_guard< std::mutex > guard(dummyMutex);
// The client has finished synchronizing with the server. Thus we can now obtain a list of all users on this server
mumble_connection_t connection;
mumble_error_t error = mumAPI.getActiveServerConnection(ownID, &connection);
if (error == MUMBLE_EC_API_REQUEST_TIMEOUT) {
deadlockPreventionWorks = true;
} else {
std::cerr << "[ERROR]: Deadlocked API request returned " << error << " (\"" << mumble_errorMessage(error)
<< "\")" << std::endl;
deadlockPreventionWorks = false;
}
dummyRan = true;
waiter.notify_all();
}
void mumble_onServerSynchronized(mumble_connection_t) {
mumAPI.log(ownID, "Starting deadlock attempt...");
// We enter this function, which is called from Mumble's main thread
// We spawn a separate thread and make sure that we block this function until the separate thread has finished. In
// that separate thread, we are calling an API function, which Mumble needs to execute in the main thread (for
// synchronization purposes). However, since we are blocking the main thread here, we produce a deadlock. Only if
// Mumble's deadlock prevention works as expected, is a freeze prevented.
std::unique_lock< std::mutex > lock(dummyMutex);
std::thread myThread(dummyFunction);
waiter.wait(lock, []() { return dummyRan; });
myThread.join();
if (deadlockPreventionWorks) {
mumAPI.log(ownID, "Deadlock prevented successfully");
} else {
mumAPI.log(ownID, "Deadlock prevented but not in the expected way");
}
}