-
Notifications
You must be signed in to change notification settings - Fork 81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Memory leak when recreating Player on loop in Python #335
Comments
A better compromise for me due to a memory leak that I otherwise experience: altdesktop/playerctl#335
I'm facing the same issue, which is especially noticeable when a media player reports the cover image in the MPRIS To reproduce:
This is a minimal reproducible example in C++ using the C playerctl library, version 2.4.1: #include "playerctl.h"
extern "C" {
#include "playerctl-common.h"
}
#define defer(name__, block__) \
std::shared_ptr<void> name__(nullptr, std::bind([&] { \
try { block__; } catch (...) { assert(false); } \
}))
static void observe(std::function<bool()> run)
{
GError* error = nullptr;
PlayerctlPlayerManager* manager = playerctl_player_manager_new(&error);
if (error != nullptr) throw std::runtime_error(error->message);
defer(free_manager, g_object_unref(manager));
while (run()) {
// Get all available players
GList *available_players = nullptr;
g_object_get(manager, "player-names", &available_players, nullptr);
if (available_players == nullptr) throw std::runtime_error("no players");
available_players = g_list_copy(available_players);
defer(free_available_players, g_list_free(available_players));
// Iterate each player
GList *l = nullptr;
for (l = available_players; l != nullptr; l = l->next) {
// Player
PlayerctlPlayerName *player_name = reinterpret_cast<PlayerctlPlayerName *>(l->data);
PlayerctlPlayer *player = playerctl_player_new_from_name(player_name, &error);
if (error != nullptr) throw std::runtime_error(error->message);
if (player == nullptr) throw std::runtime_error("player is null");
defer(free_player, g_object_unref(player));
std::string player_id = player_name->instance;
// Metadata
GVariant *raw_metadata = nullptr;
g_object_get(player, "metadata", &raw_metadata, nullptr);
if (raw_metadata == nullptr) throw std::runtime_error("no metadata");
defer(free_raw_metadata, g_variant_unref(raw_metadata));
}
std::this_thread::sleep_for(std::chrono::milliseconds{ 10 });
}
} While writing this I've been closely following how playerctl-cli.c is written and read notes about when to free in the GLib documentation, to make sure I'm doing everything right, yet it still has memory leaks. Running this with Elisa opened and a song with a 2 MB cover that is reported via MPRIS with a Running this with Valgrind gives the following output (truncated to the relevant parts):
Commenting everything below // // Metadata
// GVariant *raw_metadata = nullptr;
// g_object_get(player, "metadata", &raw_metadata, nullptr);
// if (raw_metadata == nullptr) throw std::runtime_error("no metadata");
// defer(free_raw_metadata, g_variant_unref(raw_metadata)); glib version: EDIT: Compiled glib from the master branch to exclude it being the issue, still same memory leak: https://github.com/GNOME/glib/tree/c50836535a10e142b27cd8b7d7c97c8d114d3598 |
Modifying the loop to run the metadata retrieval only once and linking with glib compiled from source (https://github.com/GNOME/glib/tree/c50836535a10e142b27cd8b7d7c97c8d114d3598) yields this valgrind output:
This happens to be exactly the size of the cover image (encoded as a data URL ~3MB). Decrementing the reference count of the metadata GVariant appears to fix the leak in my case: GVariant *raw_metadata = nullptr;
g_object_get(player, "metadata", &raw_metadata, nullptr);
if (raw_metadata == nullptr) throw std::runtime_error("no metadata");
defer(unref_raw_metadata, {
g_variant_unref(raw_metadata);
g_variant_unref(raw_metadata);
}); It seems there's a I've looked at the OP's code again and Bug reports with similar stack traces, might be useful: |
I have a Python script that I have running once a second, which uses Playerctl to fetch data about currently running players.
The only way I found to do so is to create a new
Player
for each player name fromlist_players()
, becausePlayerManager
doesn't get information about newly appearing or closing players unless I recreate it in loop too. But this occupies more and more memory each time, even if invoke Python's garbage collector after each loop.Same issue seems to occur even if I preserve
My script and the problem could be boiled down to this:
Running this slowly drains more and more system memory. I may be missing a better way about this though, but I'm unsure.
The text was updated successfully, but these errors were encountered: