From fea949767a39b18b15499c0b9161718d9071463a Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Tue, 9 Nov 2021 08:51:18 +0100 Subject: TEST(client): Add plugin to test deadlock prevention This plugin tests the built-in deadlock prevention of the Mumble plugin API. --- plugins/CMakeLists.txt | 1 + plugins/deadLockPlugin/CMakeLists.txt | 6 ++ plugins/deadLockPlugin/deadLockPlugin.cpp | 173 ++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 plugins/deadLockPlugin/CMakeLists.txt create mode 100644 plugins/deadLockPlugin/deadLockPlugin.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 95739f0ed..b20667d82 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -21,6 +21,7 @@ if(${CMAKE_BUILD_TYPE} MATCHES Debug) message("Including TestPlugin in debug mode") list(APPEND AVAILABLE_PLUGINS "testPlugin" + "deadLockPlugin" ) endif() diff --git a/plugins/deadLockPlugin/CMakeLists.txt b/plugins/deadLockPlugin/CMakeLists.txt new file mode 100644 index 000000000..8e28af5f0 --- /dev/null +++ b/plugins/deadLockPlugin/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright 2021 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 . + +add_library(deadLockPlugin SHARED "deadLockPlugin.cpp") diff --git a/plugins/deadLockPlugin/deadLockPlugin.cpp b/plugins/deadLockPlugin/deadLockPlugin.cpp new file mode 100644 index 000000000..35ef820a9 --- /dev/null +++ b/plugins/deadLockPlugin/deadLockPlugin.cpp @@ -0,0 +1,173 @@ +// Copyright 2021 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 . + +// Include the definitions of the plugin functions +// Not that this will also include ../PluginComponents.h +#include "../MumbleAPI_v_1_0_x.h" +#include "../MumblePlugin_v_1_0_x.h" + +#include +#include +#include +#include +#include + +MumbleAPI_v_1_0_x 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"); + } +} -- cgit v1.2.3