Doug Nazar f11d258545 cpu-throttling-clock: fix race between async callback and unscheduling
It's possible that the callback is already scheduled to run on another
thread when we unschedule it during dispose and we would then access
a freed object.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/9151>
2025-07-08 09:05:29 +00:00

250 lines
6.6 KiB
C

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_GETRUSAGE
#include "gst-cpu-throttling-clock.h"
#include <unistd.h>
#include <sys/resource.h>
#include "gst-cpu-throttling-clock.h"
/**
* SECTION: gst-cpu-throttling-clock
* @title: GstCpuThrottlingClock
* @short_description: TODO
*
* TODO
*/
/* *INDENT-OFF* */
GST_DEBUG_CATEGORY_STATIC (gst_cpu_throttling_clock_debug);
#define GST_CAT_DEFAULT gst_cpu_throttling_clock_debug
struct _GstCpuThrottlingClockPrivate
{
guint wanted_cpu_usage;
GstClock *sclock;
GstClockTime current_wait_time;
GstPoll *timer;
struct rusage last_usage;
GstClockID evaluate_wait_time;
GstClockTime time_between_evals;
};
#define parent_class gst_cpu_throttling_clock_parent_class
G_DEFINE_TYPE_WITH_CODE (GstCpuThrottlingClock, gst_cpu_throttling_clock, GST_TYPE_CLOCK, G_ADD_PRIVATE(GstCpuThrottlingClock))
enum
{
PROP_FIRST,
PROP_CPU_USAGE,
PROP_LAST
};
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
/* *INDENT-ON* */
static void
gst_cpu_throttling_clock_get_property (GObject * object,
guint property_id, GValue * value, GParamSpec * pspec)
{
GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (object);
switch (property_id) {
case PROP_CPU_USAGE:
g_value_set_uint (value, self->priv->wanted_cpu_usage);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gst_cpu_throttling_clock_set_property (GObject * object,
guint property_id, const GValue * value, GParamSpec * pspec)
{
GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (object);
switch (property_id) {
case PROP_CPU_USAGE:
self->priv->wanted_cpu_usage = g_value_get_uint (value);
if (self->priv->wanted_cpu_usage == 0)
self->priv->wanted_cpu_usage = 100;
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
typedef struct
{
GWeakRef cpu_throttling_clock;
} WaitData;
static void
_delete_wait_data (WaitData * wait_data)
{
g_weak_ref_clear (&wait_data->cpu_throttling_clock);
g_free (wait_data);
}
static gboolean
gst_transcoder_adjust_wait_time (GstClock * sync_clock, GstClockTime time,
GstClockID id, WaitData * wait_data)
{
struct rusage ru;
float delta_usage, usage, coef;
GstCpuThrottlingClockPrivate *priv;
GstCpuThrottlingClock *self =
g_weak_ref_get (&wait_data->cpu_throttling_clock);
if (!self)
return FALSE;
priv = self->priv;
getrusage (RUSAGE_SELF, &ru);
delta_usage = GST_TIMEVAL_TO_TIME (ru.ru_utime) -
GST_TIMEVAL_TO_TIME (self->priv->last_usage.ru_utime);
usage =
((float) delta_usage / self->priv->time_between_evals * 100) /
g_get_num_processors ();
self->priv->last_usage = ru;
coef = GST_MSECOND / 10;
if (usage < (gfloat) priv->wanted_cpu_usage) {
coef = -coef;
}
priv->current_wait_time = CLAMP (0,
(GstClockTime) priv->current_wait_time + coef, GST_SECOND);
GST_DEBUG_OBJECT (self,
"Avg is %f (wanted %d) => %" GST_TIME_FORMAT, usage,
self->priv->wanted_cpu_usage, GST_TIME_ARGS (priv->current_wait_time));
g_object_unref (self);
return TRUE;
}
static GstClockReturn
_wait (GstClock * clock, GstClockEntry * entry, GstClockTimeDiff * jitter)
{
GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (clock);
if (!self->priv->evaluate_wait_time) {
if (!(self->priv->sclock)) {
GST_ERROR_OBJECT (clock, "Could not find any system clock"
" to start the wait time evaluation task");
} else {
WaitData *wait_data = g_new (WaitData, 1);
g_weak_ref_init (&wait_data->cpu_throttling_clock, self);
self->priv->evaluate_wait_time =
gst_clock_new_periodic_id (self->priv->sclock,
gst_clock_get_time (self->priv->sclock),
self->priv->time_between_evals);
gst_clock_id_wait_async (self->priv->evaluate_wait_time,
(GstClockCallback) gst_transcoder_adjust_wait_time,
(gpointer) wait_data, (GDestroyNotify) _delete_wait_data);
}
}
if (G_UNLIKELY (GST_CLOCK_ENTRY_STATUS (entry) == GST_CLOCK_UNSCHEDULED))
return GST_CLOCK_UNSCHEDULED;
if (gst_poll_wait (self->priv->timer, self->priv->current_wait_time)) {
GST_INFO_OBJECT (self, "Something happened on the poll");
}
return GST_CLOCK_ENTRY_STATUS (entry);
}
static GstClockTime
_get_internal_time (GstClock * clock)
{
GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (clock);
return gst_clock_get_internal_time (self->priv->sclock);
}
static void
gst_cpu_throttling_clock_dispose (GObject * object)
{
GstCpuThrottlingClock *self = GST_CPU_THROTTLING_CLOCK (object);
if (self->priv->evaluate_wait_time) {
gst_clock_id_unschedule (self->priv->evaluate_wait_time);
gst_clock_id_unref (self->priv->evaluate_wait_time);
self->priv->evaluate_wait_time = 0;
}
if (self->priv->timer) {
gst_poll_free (self->priv->timer);
self->priv->timer = NULL;
}
g_clear_object (&self->priv->sclock);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_cpu_throttling_clock_class_init (GstCpuThrottlingClockClass * klass)
{
GObjectClass *oclass = G_OBJECT_CLASS (klass);
GstClockClass *clock_klass = GST_CLOCK_CLASS (klass);
GST_DEBUG_CATEGORY_INIT (gst_cpu_throttling_clock_debug, "cpuclock", 0,
"UriTranscodebin element");
oclass->get_property = gst_cpu_throttling_clock_get_property;
oclass->set_property = gst_cpu_throttling_clock_set_property;
oclass->dispose = gst_cpu_throttling_clock_dispose;
/**
* GstCpuThrottlingClock:cpu-usage:
*
* Since: UNRELEASED
*/
param_specs[PROP_CPU_USAGE] = g_param_spec_uint ("cpu-usage", "cpu-usage",
"The percentage of CPU to try to use with the processus running the "
"pipeline driven by the clock", 0, 100,
100, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (oclass, PROP_LAST, param_specs);
clock_klass->wait = GST_DEBUG_FUNCPTR (_wait);
clock_klass->get_internal_time = _get_internal_time;
}
static void
gst_cpu_throttling_clock_init (GstCpuThrottlingClock * self)
{
self->priv = gst_cpu_throttling_clock_get_instance_private (self);
self->priv->current_wait_time = GST_MSECOND;
self->priv->wanted_cpu_usage = 100;
self->priv->timer = gst_poll_new_timer ();
self->priv->time_between_evals = GST_SECOND / 4;
self->priv->sclock = GST_CLOCK (gst_system_clock_obtain ());
getrusage (RUSAGE_SELF, &self->priv->last_usage);
}
GstCpuThrottlingClock *
gst_cpu_throttling_clock_new (guint cpu_usage)
{
return g_object_new (GST_TYPE_CPU_THROTTLING_CLOCK, "cpu-usage",
cpu_usage, NULL);
}
#endif