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>
		
			
				
	
	
		
			250 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			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
 |