Pass GTimeVal around for performance reasons
[platform/upstream/gstreamer.git] / gst / rtsp-server / rtsp-session-pool.c
1 /* GStreamer
2  * Copyright (C) 2008 Wim Taymans <wim.taymans at gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "rtsp-session-pool.h"
21
22 #undef DEBUG
23
24 #define DEFAULT_MAX_SESSIONS 0
25
26 enum
27 {
28   PROP_0,
29   PROP_MAX_SESSIONS,
30   PROP_LAST
31 };
32
33 static void gst_rtsp_session_pool_get_property (GObject *object, guint propid,
34     GValue *value, GParamSpec *pspec);
35 static void gst_rtsp_session_pool_set_property (GObject *object, guint propid,
36     const GValue *value, GParamSpec *pspec);
37 static void gst_rtsp_session_pool_finalize (GObject * object);
38
39 static gchar * create_session_id (GstRTSPSessionPool *pool);
40
41 G_DEFINE_TYPE (GstRTSPSessionPool, gst_rtsp_session_pool, G_TYPE_OBJECT);
42
43 static void
44 gst_rtsp_session_pool_class_init (GstRTSPSessionPoolClass * klass)
45 {
46   GObjectClass *gobject_class;
47
48   gobject_class = G_OBJECT_CLASS (klass);
49
50   gobject_class->get_property = gst_rtsp_session_pool_get_property;
51   gobject_class->set_property = gst_rtsp_session_pool_set_property;
52   gobject_class->finalize = gst_rtsp_session_pool_finalize;
53
54   g_object_class_install_property (gobject_class, PROP_MAX_SESSIONS,
55       g_param_spec_uint ("max-sessions", "Max Sessions",
56           "the maximum amount of sessions (0 = unlimited)",
57           0, G_MAXUINT, DEFAULT_MAX_SESSIONS,
58           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
59
60   klass->create_session_id = create_session_id;
61
62 }
63
64 static void
65 gst_rtsp_session_pool_init (GstRTSPSessionPool * pool)
66 {
67   pool->lock = g_mutex_new ();
68   pool->sessions = g_hash_table_new_full (g_str_hash, g_str_equal,
69                   NULL, g_object_unref);
70   pool->max_sessions = DEFAULT_MAX_SESSIONS;
71 }
72
73 static void
74 gst_rtsp_session_pool_finalize (GObject * object)
75 {
76   GstRTSPSessionPool * pool = GST_RTSP_SESSION_POOL (object);
77
78   g_mutex_free (pool->lock);
79   g_hash_table_unref (pool->sessions);
80   
81   G_OBJECT_CLASS (gst_rtsp_session_pool_parent_class)->finalize (object);
82 }
83
84 static void
85 gst_rtsp_session_pool_get_property (GObject *object, guint propid,
86     GValue *value, GParamSpec *pspec)
87 {
88   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
89
90   switch (propid) {
91     case PROP_MAX_SESSIONS:
92       g_value_set_uint (value, gst_rtsp_session_pool_get_max_sessions (pool));
93       break;
94     default:
95       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
96       break;
97   }
98 }
99
100 static void
101 gst_rtsp_session_pool_set_property (GObject *object, guint propid,
102     const GValue *value, GParamSpec *pspec)
103 {
104   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
105
106   switch (propid) {
107     case PROP_MAX_SESSIONS:
108       gst_rtsp_session_pool_set_max_sessions (pool, g_value_get_uint (value));
109       break;
110     default:
111       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
112       break;
113   }
114 }
115
116 /**
117  * gst_rtsp_session_pool_new:
118  *
119  * Create a new #GstRTSPSessionPool instance.
120  *
121  * Returns: A new #GstRTSPSessionPool. g_object_unref() after usage.
122  */
123 GstRTSPSessionPool *
124 gst_rtsp_session_pool_new (void)
125 {
126   GstRTSPSessionPool *result;
127
128   result = g_object_new (GST_TYPE_RTSP_SESSION_POOL, NULL);
129
130   return result;
131 }
132
133 /**
134  * gst_rtsp_session_pool_set_max_sessions:
135  * @pool: a #GstRTSPSessionPool
136  * @max: the maximum number of sessions
137  *
138  * Configure the maximum allowed number of sessions in @pool to @max.
139  * A value of 0 means an unlimited amount of sessions.
140  */
141 void
142 gst_rtsp_session_pool_set_max_sessions (GstRTSPSessionPool *pool, guint max)
143 {
144   g_return_if_fail (GST_IS_RTSP_SESSION_POOL (pool));
145
146   g_mutex_lock (pool->lock);
147   pool->max_sessions = max;
148   g_mutex_unlock (pool->lock);
149 }
150
151 /**
152  * gst_rtsp_session_pool_get_max_sessions:
153  * @pool: a #GstRTSPSessionPool
154  *
155  * Get the maximum allowed number of sessions in @pool. 0 means an unlimited
156  * amount of sessions.
157  *
158  * Returns: the maximum allowed number of sessions.
159  */
160 guint
161 gst_rtsp_session_pool_get_max_sessions (GstRTSPSessionPool *pool)
162 {
163   guint result;
164
165   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
166
167   g_mutex_lock (pool->lock);
168   result = pool->max_sessions;
169   g_mutex_unlock (pool->lock);
170
171   return result;
172 }
173
174 /**
175  * gst_rtsp_session_pool_get_n_sessions:
176  * @pool: a #GstRTSPSessionPool
177  *
178  * Get the amount of active sessions in @pool.
179  *
180  * Returns: the amount of active sessions in @pool.
181  */
182 guint
183 gst_rtsp_session_pool_get_n_sessions (GstRTSPSessionPool *pool)
184 {
185   guint result;
186
187   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
188
189   g_mutex_lock (pool->lock);
190   result = g_hash_table_size (pool->sessions);
191   g_mutex_unlock (pool->lock);
192
193   return result;
194 }
195
196 /**
197  * gst_rtsp_session_pool_find:
198  * @pool: the pool to search
199  * @sessionid: the session id
200  *
201  * Find the session with @sessionid in @pool. The access time of the session
202  * will be updated with gst_rtsp_session_touch().
203  *
204  * Returns: the #GstRTSPSession with @sessionid or %NULL when the session did
205  * not exist. g_object_unref() after usage.
206  */
207 GstRTSPSession *
208 gst_rtsp_session_pool_find (GstRTSPSessionPool *pool, const gchar *sessionid)
209 {
210   GstRTSPSession *result;
211
212   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
213   g_return_val_if_fail (sessionid != NULL, NULL);
214
215   g_mutex_lock (pool->lock);
216   result = g_hash_table_lookup (pool->sessions, sessionid);
217   if (result) 
218     g_object_ref (result);
219   g_mutex_unlock (pool->lock);
220
221   if (result) {
222     gst_rtsp_session_touch (result);
223   }
224
225   return result;
226 }
227
228 static gchar *
229 create_session_id (GstRTSPSessionPool *pool)
230 {
231   gchar id[16];
232   gint i;
233
234   for (i = 0; i < 16; i++) {
235     id[i] = g_random_int_range ('a', 'z');
236   }
237
238   return g_strndup (id, 16);
239 }
240
241 /**
242  * gst_rtsp_session_pool_create:
243  * @pool: a #GstRTSPSessionPool
244  *
245  * Create a new #GstRTSPSession object in @pool.
246  *
247  * Returns: a new #GstRTSPSession.
248  */
249 GstRTSPSession *
250 gst_rtsp_session_pool_create (GstRTSPSessionPool *pool)
251 {
252   GstRTSPSession *result = NULL;
253   GstRTSPSessionPoolClass *klass;
254   gchar *id = NULL;
255   guint retry;
256
257   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
258
259   klass = GST_RTSP_SESSION_POOL_GET_CLASS (pool);
260
261   retry = 0;
262   do {
263     /* start by creating a new random session id, we assume that this is random
264      * enough to not cause a collision, which we will check later  */
265     if (klass->create_session_id)
266       id = klass->create_session_id (pool);
267     else
268       goto no_function;
269
270     if (id == NULL)
271       goto no_session;
272
273     g_mutex_lock (pool->lock);
274     /* check session limit */
275     if (pool->max_sessions > 0) {
276       if (g_hash_table_size (pool->sessions) >= pool->max_sessions)
277         goto too_many_sessions;
278     }
279     /* check if the sessionid existed */
280     result = g_hash_table_lookup (pool->sessions, id);
281     if (result) {
282       /* found, retry with a different session id */
283       result = NULL;
284       retry++;
285       if (retry > 100)
286         goto collision;
287     }
288     else {
289       /* not found, create session and insert it in the pool */
290       result = gst_rtsp_session_new (id); 
291       /* take additional ref for the pool */
292       g_object_ref (result);
293       g_hash_table_insert (pool->sessions, result->sessionid, result);
294     }
295     g_mutex_unlock (pool->lock);
296
297     g_free (id);
298   } while (result == NULL);
299
300   return result;
301
302   /* ERRORS */
303 no_function:
304   {
305     g_warning ("no create_session_id vmethod in GstRTSPSessionPool %p", pool);
306     return NULL;
307   }
308 no_session:
309   {
310     g_warning ("can't create session id with GstRTSPSessionPool %p", pool);
311     return NULL;
312   }
313 collision:
314   {
315     g_warning ("can't find unique sessionid for GstRTSPSessionPool %p", pool);
316     g_mutex_unlock (pool->lock);
317     g_free (id);
318     return NULL;
319   }
320 too_many_sessions:
321   {
322     g_warning ("session pool reached max sessions of %d", pool->max_sessions);
323     g_mutex_unlock (pool->lock);
324     g_free (id);
325     return NULL;
326   }
327 }
328
329 /**
330  * gst_rtsp_session_pool_remove:
331  * @pool: a #GstRTSPSessionPool
332  * @sess: a #GstRTSPSession
333  *
334  * Remove @sess from @pool, releasing the ref that the pool has on @sess.
335  *
336  * Returns: %TRUE if the session was found and removed.
337  */
338 gboolean
339 gst_rtsp_session_pool_remove (GstRTSPSessionPool *pool, GstRTSPSession *sess)
340 {
341   gboolean found;
342
343   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), FALSE);
344   g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), FALSE);
345
346   g_mutex_lock (pool->lock);
347   found = g_hash_table_remove (pool->sessions, sess->sessionid);
348   g_mutex_unlock (pool->lock);
349
350   return found;
351 }
352
353 static gboolean
354 cleanup_func (gchar *sessionid, GstRTSPSession *sess, GTimeVal *now)
355 {
356   return gst_rtsp_session_is_expired (sess, now);
357 }
358
359 /**
360  * gst_rtsp_session_pool_cleanup:
361  * @pool: a #GstRTSPSessionPool
362  *
363  * Inspect all the sessions in @pool and remove the sessions that are inactive
364  * for more than their timeout.
365  *
366  * Returns: the amount of sessions that got removed.
367  */
368 guint
369 gst_rtsp_session_pool_cleanup (GstRTSPSessionPool *pool)
370 {
371   guint result;
372   GTimeVal now;
373
374   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
375
376   g_get_current_time (&now);
377
378   g_mutex_lock (pool->lock);
379   result = g_hash_table_foreach_remove (pool->sessions, (GHRFunc) cleanup_func, &now);
380   g_mutex_unlock (pool->lock);
381
382   return result;
383 }
384
385 typedef struct
386 {
387   GSource source;
388   GstRTSPSessionPool *pool;
389   gint timeout;
390 } GstPoolSource;
391
392 static void
393 collect_timeout (gchar *sessionid, GstRTSPSession *sess, GstPoolSource *psrc)
394 {
395   gint timeout;
396   GTimeVal now;
397
398   g_source_get_current_time ((GSource*)psrc, &now);
399
400   timeout = gst_rtsp_session_next_timeout (sess, &now);
401   g_message ("%p: next timeout: %d", sess, timeout);
402   if (psrc->timeout == -1 || timeout < psrc->timeout)
403     psrc->timeout = timeout;
404 }
405
406 static gboolean
407 gst_pool_source_prepare (GSource * source, gint * timeout)
408 {
409   GstPoolSource *psrc;
410   gboolean result;
411
412   psrc = (GstPoolSource *) source;
413   psrc->timeout = -1;
414
415   g_mutex_lock (psrc->pool->lock);
416   g_hash_table_foreach (psrc->pool->sessions, (GHFunc) collect_timeout, psrc);
417   g_mutex_unlock (psrc->pool->lock);
418
419   if (timeout)
420     *timeout = psrc->timeout;
421
422   result = psrc->timeout == 0;
423
424   g_message ("prepare %d, %d", psrc->timeout, result);
425
426   return result;
427 }
428
429 static gboolean
430 gst_pool_source_check (GSource * source)
431 {
432   g_message ("check");
433
434   return gst_pool_source_prepare (source, NULL);
435 }
436
437 static gboolean
438 gst_pool_source_dispatch (GSource * source, GSourceFunc callback,
439     gpointer user_data)
440 {
441   gboolean res;
442   GstPoolSource *psrc = (GstPoolSource *) source;
443   GstRTSPSessionPoolFunc func = (GstRTSPSessionPoolFunc) callback;
444
445   g_message ("dispatch");
446
447   if (func)
448     res = func (psrc->pool, user_data);
449   else
450     res = FALSE;
451
452   return res;
453 }
454
455 static void
456 gst_pool_source_finalize (GSource * source)
457 {
458   GstPoolSource *psrc = (GstPoolSource *) source;
459
460   g_message ("finalize %p", psrc);
461
462   g_object_unref (psrc->pool);
463   psrc->pool = NULL;
464 }
465
466 static GSourceFuncs gst_pool_source_funcs = {
467   gst_pool_source_prepare,
468   gst_pool_source_check,
469   gst_pool_source_dispatch,
470   gst_pool_source_finalize
471 };
472
473 /**
474  * gst_rtsp_session_pool_create_watch:
475  * @pool: a #GstRTSPSessionPool
476  *
477  * A GSource that will be dispatched when the session should be cleaned up.
478  */
479 GSource *
480 gst_rtsp_session_pool_create_watch (GstRTSPSessionPool *pool)
481 {
482   GstPoolSource *source;
483
484   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
485
486   source = (GstPoolSource *) g_source_new (&gst_pool_source_funcs,
487       sizeof (GstPoolSource));
488   source->pool = g_object_ref (pool);
489
490   return (GSource *) source;
491 }
492