Do some more session cleanup
[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     gst_rtsp_session_touch (result);
220   }
221   g_mutex_unlock (pool->lock);
222
223   return result;
224 }
225
226 static gchar *
227 create_session_id (GstRTSPSessionPool *pool)
228 {
229   gchar id[16];
230   gint i;
231
232   for (i = 0; i < 16; i++) {
233     id[i] = g_random_int_range ('a', 'z');
234   }
235
236   return g_strndup (id, 16);
237 }
238
239 /**
240  * gst_rtsp_session_pool_create:
241  * @pool: a #GstRTSPSessionPool
242  *
243  * Create a new #GstRTSPSession object in @pool.
244  *
245  * Returns: a new #GstRTSPSession.
246  */
247 GstRTSPSession *
248 gst_rtsp_session_pool_create (GstRTSPSessionPool *pool)
249 {
250   GstRTSPSession *result = NULL;
251   GstRTSPSessionPoolClass *klass;
252   gchar *id = NULL;
253   guint retry;
254
255   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
256
257   klass = GST_RTSP_SESSION_POOL_GET_CLASS (pool);
258
259   retry = 0;
260   do {
261     /* start by creating a new random session id, we assume that this is random
262      * enough to not cause a collision, which we will check later  */
263     if (klass->create_session_id)
264       id = klass->create_session_id (pool);
265     else
266       goto no_function;
267
268     if (id == NULL)
269       goto no_session;
270
271     g_mutex_lock (pool->lock);
272     /* check session limit */
273     if (pool->max_sessions > 0) {
274       if (g_hash_table_size (pool->sessions) >= pool->max_sessions)
275         goto too_many_sessions;
276     }
277     /* check if the sessionid existed */
278     result = g_hash_table_lookup (pool->sessions, id);
279     if (result) {
280       /* found, retry with a different session id */
281       result = NULL;
282       retry++;
283       if (retry > 100)
284         goto collision;
285     }
286     else {
287       /* not found, create session and insert it in the pool */
288       result = gst_rtsp_session_new (id); 
289       /* take additional ref for the pool */
290       g_object_ref (result);
291       g_hash_table_insert (pool->sessions, result->sessionid, result);
292     }
293     g_mutex_unlock (pool->lock);
294
295     g_free (id);
296   } while (result == NULL);
297
298   return result;
299
300   /* ERRORS */
301 no_function:
302   {
303     g_warning ("no create_session_id vmethod in GstRTSPSessionPool %p", pool);
304     return NULL;
305   }
306 no_session:
307   {
308     g_warning ("can't create session id with GstRTSPSessionPool %p", pool);
309     return NULL;
310   }
311 collision:
312   {
313     g_warning ("can't find unique sessionid for GstRTSPSessionPool %p", pool);
314     g_mutex_unlock (pool->lock);
315     g_free (id);
316     return NULL;
317   }
318 too_many_sessions:
319   {
320     g_warning ("session pool reached max sessions of %d", pool->max_sessions);
321     g_mutex_unlock (pool->lock);
322     g_free (id);
323     return NULL;
324   }
325 }
326
327 /**
328  * gst_rtsp_session_pool_remove:
329  * @pool: a #GstRTSPSessionPool
330  * @sess: a #GstRTSPSession
331  *
332  * Remove @sess from @pool, releasing the ref that the pool has on @sess.
333  *
334  * Returns: %TRUE if the session was found and removed.
335  */
336 gboolean
337 gst_rtsp_session_pool_remove (GstRTSPSessionPool *pool, GstRTSPSession *sess)
338 {
339   gboolean found;
340
341   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), FALSE);
342   g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), FALSE);
343
344   g_mutex_lock (pool->lock);
345   found = g_hash_table_remove (pool->sessions, sess->sessionid);
346   g_mutex_unlock (pool->lock);
347
348   return found;
349 }
350
351 static gboolean
352 cleanup_func (gchar *sessionid, GstRTSPSession *sess, GTimeVal *now)
353 {
354   return gst_rtsp_session_is_expired (sess, now);
355 }
356
357 /**
358  * gst_rtsp_session_pool_cleanup:
359  * @pool: a #GstRTSPSessionPool
360  *
361  * Inspect all the sessions in @pool and remove the sessions that are inactive
362  * for more than their timeout.
363  *
364  * Returns: the amount of sessions that got removed.
365  */
366 guint
367 gst_rtsp_session_pool_cleanup (GstRTSPSessionPool *pool)
368 {
369   guint result;
370   GTimeVal now;
371
372   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
373
374   g_get_current_time (&now);
375
376   g_mutex_lock (pool->lock);
377   result = g_hash_table_foreach_remove (pool->sessions, (GHRFunc) cleanup_func, &now);
378   g_mutex_unlock (pool->lock);
379
380   return result;
381 }
382
383 typedef struct
384 {
385   GSource source;
386   GstRTSPSessionPool *pool;
387   gint timeout;
388 } GstPoolSource;
389
390 static void
391 collect_timeout (gchar *sessionid, GstRTSPSession *sess, GstPoolSource *psrc)
392 {
393   gint timeout;
394   GTimeVal now;
395
396   g_source_get_current_time ((GSource*)psrc, &now);
397
398   timeout = gst_rtsp_session_next_timeout (sess, &now);
399   g_message ("%p: next timeout: %d", sess, timeout);
400   if (psrc->timeout == -1 || timeout < psrc->timeout)
401     psrc->timeout = timeout;
402 }
403
404 static gboolean
405 gst_pool_source_prepare (GSource * source, gint * timeout)
406 {
407   GstPoolSource *psrc;
408   gboolean result;
409
410   psrc = (GstPoolSource *) source;
411   psrc->timeout = -1;
412
413   g_mutex_lock (psrc->pool->lock);
414   g_hash_table_foreach (psrc->pool->sessions, (GHFunc) collect_timeout, psrc);
415   g_mutex_unlock (psrc->pool->lock);
416
417   if (timeout)
418     *timeout = psrc->timeout;
419
420   result = psrc->timeout == 0;
421
422   g_message ("prepare %d, %d", psrc->timeout, result);
423
424   return result;
425 }
426
427 static gboolean
428 gst_pool_source_check (GSource * source)
429 {
430   g_message ("check");
431
432   return gst_pool_source_prepare (source, NULL);
433 }
434
435 static gboolean
436 gst_pool_source_dispatch (GSource * source, GSourceFunc callback,
437     gpointer user_data)
438 {
439   gboolean res;
440   GstPoolSource *psrc = (GstPoolSource *) source;
441   GstRTSPSessionPoolFunc func = (GstRTSPSessionPoolFunc) callback;
442
443   g_message ("dispatch");
444
445   if (func)
446     res = func (psrc->pool, user_data);
447   else
448     res = FALSE;
449
450   return res;
451 }
452
453 static void
454 gst_pool_source_finalize (GSource * source)
455 {
456   GstPoolSource *psrc = (GstPoolSource *) source;
457
458   g_message ("finalize %p", psrc);
459
460   g_object_unref (psrc->pool);
461   psrc->pool = NULL;
462 }
463
464 static GSourceFuncs gst_pool_source_funcs = {
465   gst_pool_source_prepare,
466   gst_pool_source_check,
467   gst_pool_source_dispatch,
468   gst_pool_source_finalize
469 };
470
471 /**
472  * gst_rtsp_session_pool_create_watch:
473  * @pool: a #GstRTSPSessionPool
474  *
475  * A GSource that will be dispatched when the session should be cleaned up.
476  */
477 GSource *
478 gst_rtsp_session_pool_create_watch (GstRTSPSessionPool *pool)
479 {
480   GstPoolSource *source;
481
482   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
483
484   source = (GstPoolSource *) g_source_new (&gst_pool_source_funcs,
485       sizeof (GstPoolSource));
486   source->pool = g_object_ref (pool);
487
488   return (GSource *) source;
489 }
490