Add beginnings of session timeouts and limits
authorWim Taymans <wim.taymans@collabora.co.uk>
Wed, 4 Feb 2009 18:52:50 +0000 (19:52 +0100)
committerWim Taymans <wim.taymans@collabora.co.uk>
Wed, 4 Feb 2009 18:52:50 +0000 (19:52 +0100)
Add the timeout value to the Session header for unusual timeout values.

Allow us to configure a limit to the amount of active sessions in a pool. Set a
limit on the amount of retry we do after a sessionid collision.

Add properties to the sessionid and the timeout of a session. Keep track of
creation time and last access time for sessions.

gst/rtsp-server/rtsp-client.c
gst/rtsp-server/rtsp-session-pool.c
gst/rtsp-server/rtsp-session-pool.h
gst/rtsp-server/rtsp-session.c
gst/rtsp-server/rtsp-session.h

index 0f18d21..8405d22 100644 (file)
@@ -561,8 +561,16 @@ handle_setup_request (GstRTSPClient *client, GstRTSPUrl *uri, GstRTSPMessage *re
   gst_rtsp_message_init_response (&response, code, gst_rtsp_status_as_text (code), request);
 
   /* add the new session header for new session ids */
-  if (need_session)
-    gst_rtsp_message_add_header (&response, GST_RTSP_HDR_SESSION, session->sessionid);
+  if (need_session) {
+    gchar *str;
+
+    if (session->timeout != 60)
+      str = g_strdup_printf ("%s; timeout=%d", session->sessionid, session->timeout);
+    else
+      str = g_strdup (session->sessionid);
+
+    gst_rtsp_message_take_header (&response, GST_RTSP_HDR_SESSION, str);
+  }
 
   gst_rtsp_message_add_header (&response, GST_RTSP_HDR_TRANSPORT, trans_str);
   g_free (trans_str);
index 4260498..8ce18ef 100644 (file)
 
 #undef DEBUG
 
+#define DEFAULT_MAX_SESSIONS 0
+
+enum
+{
+  PROP_0,
+  PROP_MAX_SESSIONS,
+  PROP_LAST
+};
+
+static void gst_rtsp_session_pool_get_property (GObject *object, guint propid,
+    GValue *value, GParamSpec *pspec);
+static void gst_rtsp_session_pool_set_property (GObject *object, guint propid,
+    const GValue *value, GParamSpec *pspec);
 static void gst_rtsp_session_pool_finalize (GObject * object);
 
 static gchar * create_session_id (GstRTSPSessionPool *pool);
@@ -34,8 +47,16 @@ gst_rtsp_session_pool_class_init (GstRTSPSessionPoolClass * klass)
 
   gobject_class = G_OBJECT_CLASS (klass);
 
+  gobject_class->get_property = gst_rtsp_session_pool_get_property;
+  gobject_class->set_property = gst_rtsp_session_pool_set_property;
   gobject_class->finalize = gst_rtsp_session_pool_finalize;
 
+  g_object_class_install_property (gobject_class, PROP_MAX_SESSIONS,
+      g_param_spec_uint ("max-sessions", "Max Sessions",
+          "the maximum amount of sessions (0 = unlimited)",
+          0, G_MAXUINT, DEFAULT_MAX_SESSIONS,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   klass->create_session_id = create_session_id;
 }
 
@@ -45,6 +66,7 @@ gst_rtsp_session_pool_init (GstRTSPSessionPool * pool)
   pool->lock = g_mutex_new ();
   pool->sessions = g_hash_table_new_full (g_str_hash, g_str_equal,
                  NULL, g_object_unref);
+  pool->max_sessions = DEFAULT_MAX_SESSIONS;
 }
 
 static void
@@ -58,6 +80,42 @@ gst_rtsp_session_pool_finalize (GObject * object)
   G_OBJECT_CLASS (gst_rtsp_session_pool_parent_class)->finalize (object);
 }
 
+static void
+gst_rtsp_session_pool_get_property (GObject *object, guint propid,
+    GValue *value, GParamSpec *pspec)
+{
+  GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
+
+  switch (propid) {
+    case PROP_MAX_SESSIONS:
+      g_mutex_lock (pool->lock);
+      g_value_set_uint (value, pool->max_sessions);
+      g_mutex_unlock (pool->lock);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+      break;
+  }
+}
+
+static void
+gst_rtsp_session_pool_set_property (GObject *object, guint propid,
+    const GValue *value, GParamSpec *pspec)
+{
+  GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
+
+  switch (propid) {
+    case PROP_MAX_SESSIONS:
+      g_mutex_lock (pool->lock);
+      pool->max_sessions = g_value_get_uint (value);
+      g_mutex_unlock (pool->lock);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+      break;
+  }
+}
+
 /**
  * gst_rtsp_session_pool_new:
  *
@@ -76,11 +134,75 @@ gst_rtsp_session_pool_new (void)
 }
 
 /**
+ * gst_rtsp_session_pool_set_max_sessions:
+ * @pool: a #GstRTSPSessionPool
+ * @max: the maximum number of sessions
+ *
+ * Configure the maximum allowed number of sessions in @pool to @max.
+ * A value of 0 means an unlimited amount of sessions.
+ */
+void
+gst_rtsp_session_pool_set_max_sessions (GstRTSPSessionPool *pool, guint max)
+{
+  g_return_if_fail (GST_IS_RTSP_SESSION_POOL (pool));
+
+  g_mutex_lock (pool->lock);
+  pool->max_sessions = max;
+  g_mutex_unlock (pool->lock);
+}
+
+/**
+ * gst_rtsp_session_pool_get_max_sessions:
+ * @pool: a #GstRTSPSessionPool
+ *
+ * Get the maximum allowed number of sessions in @pool. 0 means an unlimited
+ * amount of sessions.
+ *
+ * Returns: the maximum allowed number of sessions.
+ */
+guint
+gst_rtsp_session_pool_get_max_sessions (GstRTSPSessionPool *pool)
+{
+  guint result;
+
+  g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
+
+  g_mutex_lock (pool->lock);
+  result = pool->max_sessions;
+  g_mutex_unlock (pool->lock);
+
+  return result;
+}
+
+/**
+ * gst_rtsp_session_pool_get_n_sessions:
+ * @pool: a #GstRTSPSessionPool
+ *
+ * Get the amount of active sessions in @pool.
+ *
+ * Returns: the amount of active sessions in @pool.
+ */
+guint
+gst_rtsp_session_pool_get_n_sessions (GstRTSPSessionPool *pool)
+{
+  guint result;
+
+  g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
+
+  g_mutex_lock (pool->lock);
+  result = g_hash_table_size (pool->sessions);
+  g_mutex_unlock (pool->lock);
+
+  return result;
+}
+
+/**
  * gst_rtsp_session_pool_find:
  * @pool: the pool to search
  * @sessionid: the session id
  *
- * Find the session with @sessionid in @pool.
+ * Find the session with @sessionid in @pool. The access time of the session
+ * will be updated with gst_rtsp_session_touch().
  *
  * Returns: the #GstRTSPSession with @sessionid or %NULL when the session did
  * not exist. g_object_unref() after usage.
@@ -95,10 +217,13 @@ gst_rtsp_session_pool_find (GstRTSPSessionPool *pool, const gchar *sessionid)
 
   g_mutex_lock (pool->lock);
   result = g_hash_table_lookup (pool->sessions, sessionid);
-  if (result)
+  if (result) 
     g_object_ref (result);
   g_mutex_unlock (pool->lock);
 
+  if (result)
+    gst_rtsp_session_touch (result);
+
   return result;
 }
 
@@ -129,11 +254,13 @@ gst_rtsp_session_pool_create (GstRTSPSessionPool *pool)
   GstRTSPSession *result = NULL;
   GstRTSPSessionPoolClass *klass;
   gchar *id = NULL;
+  guint retry;
 
   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
 
   klass = GST_RTSP_SESSION_POOL_GET_CLASS (pool);
 
+  retry = 0;
   do {
     /* start by creating a new random session id, we assume that this is random
      * enough to not cause a collision, which we will check later  */
@@ -146,11 +273,19 @@ gst_rtsp_session_pool_create (GstRTSPSessionPool *pool)
       goto no_session;
 
     g_mutex_lock (pool->lock);
+    /* check session limit */
+    if (pool->max_sessions > 0) {
+      if (g_hash_table_size (pool->sessions) >= pool->max_sessions)
+       goto too_many_sessions;
+    }
     /* check if the sessionid existed */
     result = g_hash_table_lookup (pool->sessions, id);
     if (result) {
-      /* found, retry with a different session id*/
+      /* found, retry with a different session id */
       result = NULL;
+      retry++;
+      if (retry > 100)
+       goto collision;
     }
     else {
       /* not found, create session and insert it in the pool */
@@ -177,6 +312,20 @@ no_session:
     g_warning ("can't create session id with GstRTSPSessionPool %p", pool);
     return NULL;
   }
+collision:
+  {
+    g_warning ("can't find unique sessionid for GstRTSPSessionPool %p", pool);
+    g_mutex_unlock (pool->lock);
+    g_free (id);
+    return NULL;
+  }
+too_many_sessions:
+  {
+    g_warning ("session pool reached max sessions of %d", pool->max_sessions);
+    g_mutex_unlock (pool->lock);
+    g_free (id);
+    return NULL;
+  }
 }
 
 /**
@@ -184,20 +333,22 @@ no_session:
  * @pool: a #GstRTSPSessionPool
  * @sess: a #GstRTSPSession
  *
- * Remove @sess from @pool and Clean it up.
+ * Remove @sess from @pool, releasing the ref that the pool has on @sess.
  *
- * Returns: a new #GstRTSPSession.
+ * Returns: %TRUE if the session was found and removed.
  */
-void
+gboolean
 gst_rtsp_session_pool_remove (GstRTSPSessionPool *pool, GstRTSPSession *sess)
 {
   gboolean found;
 
-  g_return_if_fail (GST_IS_RTSP_SESSION_POOL (pool));
-  g_return_if_fail (GST_IS_RTSP_SESSION (sess));
+  g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), FALSE);
+  g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), FALSE);
 
   g_mutex_lock (pool->lock);
   found = g_hash_table_remove (pool->sessions, sess->sessionid);
   g_mutex_unlock (pool->lock);
+
+  return found;
 }
 
index 9f65f41..30df9bd 100644 (file)
@@ -40,7 +40,7 @@ typedef struct _GstRTSPSessionPoolClass GstRTSPSessionPoolClass;
 
 /**
  * GstRTSPSessionPool:
- *
+ * @max_sessions: the maximum number of sessions.
  * @lock: locking the session hashtable
  * @session: hashtable of sessions indexed by the session id.
  *
@@ -50,6 +50,8 @@ typedef struct _GstRTSPSessionPoolClass GstRTSPSessionPoolClass;
 struct _GstRTSPSessionPool {
   GObject       parent;
 
+  guint         max_sessions;
+
   GMutex       *lock;
   GHashTable   *sessions;
 };
@@ -57,7 +59,7 @@ struct _GstRTSPSessionPool {
 /**
  * GstRTSPSessionPoolClass:
  * @create_session_id: create a new random session id. Subclasses can create
- * custom session ids and should not check if the session exists.
+ *    custom session ids and should not check if the session exists.
  */
 struct _GstRTSPSessionPoolClass {
   GObjectClass  parent_class;
@@ -67,12 +69,20 @@ struct _GstRTSPSessionPoolClass {
 
 GType                 gst_rtsp_session_pool_get_type          (void);
 
+/* creating a session pool */
 GstRTSPSessionPool *  gst_rtsp_session_pool_new               (void);
 
+/* counting sessionss */
+void                  gst_rtsp_session_pool_set_max_sessions  (GstRTSPSessionPool *pool, guint max);
+guint                 gst_rtsp_session_pool_get_max_sessions  (GstRTSPSessionPool *pool);
+
+guint                 gst_rtsp_session_pool_get_n_sessions    (GstRTSPSessionPool *pool);
+
+/* managing sessions */
+GstRTSPSession *      gst_rtsp_session_pool_create            (GstRTSPSessionPool *pool);
 GstRTSPSession *      gst_rtsp_session_pool_find              (GstRTSPSessionPool *pool,
                                                                const gchar *sessionid);
-GstRTSPSession *      gst_rtsp_session_pool_create            (GstRTSPSessionPool *pool);
-void                  gst_rtsp_session_pool_remove            (GstRTSPSessionPool *pool,
+gboolean              gst_rtsp_session_pool_remove            (GstRTSPSessionPool *pool,
                                                                GstRTSPSession *sess);
 
 G_END_DECLS
index 8596ea7..3c538b4 100644 (file)
 
 #undef DEBUG
 
+#define DEFAULT_TIMEOUT        60
+
+enum
+{
+  PROP_0,
+  PROP_SESSIONID,
+  PROP_TIMEOUT,
+  PROP_LAST
+};
+
+static void gst_rtsp_session_get_property (GObject *object, guint propid,
+    GValue *value, GParamSpec *pspec);
+static void gst_rtsp_session_set_property (GObject *object, guint propid,
+    const GValue *value, GParamSpec *pspec);
 static void gst_rtsp_session_finalize (GObject * obj);
 
 G_DEFINE_TYPE (GstRTSPSession, gst_rtsp_session, G_TYPE_OBJECT);
@@ -33,12 +47,26 @@ gst_rtsp_session_class_init (GstRTSPSessionClass * klass)
 
   gobject_class = G_OBJECT_CLASS (klass);
 
+  gobject_class->get_property = gst_rtsp_session_get_property;
+  gobject_class->set_property = gst_rtsp_session_set_property;
   gobject_class->finalize = gst_rtsp_session_finalize;
+
+  g_object_class_install_property (gobject_class, PROP_SESSIONID,
+      g_param_spec_string ("sessionid", "Sessionid", "the session id",
+          NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+         G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_TIMEOUT,
+      g_param_spec_uint ("timeout", "timeout", "the timeout of the session (0 = never)",
+          0, G_MAXUINT, DEFAULT_TIMEOUT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 }
 
 static void
 gst_rtsp_session_init (GstRTSPSession * session)
 {
+  session->timeout = DEFAULT_TIMEOUT;
+  g_get_current_time (&session->create_time);
+  gst_rtsp_session_touch (session);
 }
 
 static void
@@ -100,6 +128,43 @@ gst_rtsp_session_finalize (GObject * obj)
   G_OBJECT_CLASS (gst_rtsp_session_parent_class)->finalize (obj);
 }
 
+static void
+gst_rtsp_session_get_property (GObject *object, guint propid,
+    GValue *value, GParamSpec *pspec)
+{
+  GstRTSPSession *session = GST_RTSP_SESSION (object);
+
+  switch (propid) {
+    case PROP_SESSIONID:
+      g_value_set_string (value, session->sessionid);
+      break;
+    case PROP_TIMEOUT:
+      g_value_set_uint (value, gst_rtsp_session_get_timeout (session));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+  }
+}
+
+static void
+gst_rtsp_session_set_property (GObject *object, guint propid,
+    const GValue *value, GParamSpec *pspec)
+{
+  GstRTSPSession *session = GST_RTSP_SESSION (object);
+
+  switch (propid) {
+    case PROP_SESSIONID:
+      g_free (session->sessionid);
+      session->sessionid = g_value_dup_string (value);
+      break;
+    case PROP_TIMEOUT:
+      gst_rtsp_session_set_timeout (session, g_value_get_uint (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+  }
+}
+
 /**
  * gst_rtsp_session_manage_media:
  * @sess: a #GstRTSPSession
@@ -262,13 +327,77 @@ gst_rtsp_session_new (const gchar *sessionid)
 {
   GstRTSPSession *result;
 
-  result = g_object_new (GST_TYPE_RTSP_SESSION, NULL);
-  result->sessionid = g_strdup (sessionid);
+  g_return_val_if_fail (sessionid != NULL, NULL);
+
+  result = g_object_new (GST_TYPE_RTSP_SESSION, "sessionid", sessionid, NULL);
 
   return result;
 }
 
 /**
+ * gst_rtsp_session_get_sessionid:
+ * @session: a #GstRTSPSession
+ *
+ * Get the sessionid of @session.
+ *
+ * Returns: the sessionid of @session. The value remains valid as long as
+ * @session is alive.
+ */
+const gchar *
+gst_rtsp_session_get_sessionid (GstRTSPSession *session)
+{
+  g_return_val_if_fail (GST_IS_RTSP_SESSION (session), NULL);
+
+  return session->sessionid;
+}
+
+/**
+ * gst_rtsp_session_set_timeout:
+ * @session: a #GstRTSPSession
+ * @timeout: the new timeout
+ *
+ * Configure @session for a timeout of @timeout seconds. The session will be
+ * cleaned up when there is no activity for @timeout seconds.
+ */
+void
+gst_rtsp_session_set_timeout (GstRTSPSession *session, guint timeout)
+{
+  g_return_if_fail (GST_IS_RTSP_SESSION (session));
+
+  session->timeout = timeout;
+}
+
+/**
+ * gst_rtsp_session_get_timeout:
+ * @session: a #GstRTSPSession
+ *
+ * Get the timeout value of @session.
+ *
+ * Returns: the timeout of @session in seconds.
+ */
+guint
+gst_rtsp_session_get_timeout (GstRTSPSession *session)
+{
+  g_return_val_if_fail (GST_IS_RTSP_SESSION (session), 0);
+
+  return session->timeout;
+}
+
+/**
+ * gst_rtsp_session_touch:
+ * @session: a #GstRTSPSession
+ *
+ * Update the last_access time of the session to the current time.
+ */
+void
+gst_rtsp_session_touch (GstRTSPSession *session)
+{
+  g_return_if_fail (GST_IS_RTSP_SESSION (session));
+
+  g_get_current_time (&session->last_access);
+}
+
+/**
  * gst_rtsp_session_stream_init_udp:
  * @stream: a #GstRTSPSessionStream
  * @ct: a client #GstRTSPTransport
@@ -284,6 +413,9 @@ gst_rtsp_session_stream_set_transport (GstRTSPSessionStream *stream,
 {
   GstRTSPTransport *st;
 
+  g_return_val_if_fail (stream != NULL, NULL);
+  g_return_val_if_fail (ct != NULL, NULL);
+
   /* prepare the server transport */
   gst_rtsp_transport_new (&st);
 
@@ -316,6 +448,8 @@ gst_rtsp_session_media_play (GstRTSPSessionMedia *media)
 {
   gboolean ret;
 
+  g_return_val_if_fail (media != NULL, FALSE);
+
   ret = gst_rtsp_media_play (media->media, media->streams);
 
   return ret;
@@ -334,6 +468,8 @@ gst_rtsp_session_media_pause (GstRTSPSessionMedia *media)
 {
   gboolean ret;
 
+  g_return_val_if_fail (media != NULL, FALSE);
+
   ret = gst_rtsp_media_pause (media->media, media->streams);
 
   return ret;
@@ -353,6 +489,8 @@ gst_rtsp_session_media_stop (GstRTSPSessionMedia *media)
 {
   gboolean ret;
 
+  g_return_val_if_fail (media != NULL, FALSE);
+
   ret = gst_rtsp_media_stop (media->media, media->streams);
 
   return ret;
index 08104f1..d87922b 100644 (file)
@@ -81,16 +81,25 @@ struct _GstRTSPSessionMedia
 
 /**
  * GstRTSPSession:
+ * @sessionid: the session id of the session
+ * @timeout: the timeout of the session
+ * @create_time: the time when the session was created
+ * @last_access: the time the session was last accessed
+ * @media: a list of #GstRTSPSessionMedia managed in this session
  *
  * Session information kept by the server for a specific client.
  * One client session, identified with a session id, can handle multiple medias
- * identified with the media object.
+ * identified with the url of a media.
  */
 struct _GstRTSPSession {
   GObject       parent;
 
   gchar        *sessionid;
 
+  guint         timeout;
+  GTimeVal      create_time;
+  GTimeVal      last_access;
+
   GList        *medias;
 };
 
@@ -103,6 +112,14 @@ GType                  gst_rtsp_session_get_type             (void);
 /* create a new session */
 GstRTSPSession *       gst_rtsp_session_new                  (const gchar *sessionid);
 
+const gchar *          gst_rtsp_session_get_sessionid        (GstRTSPSession *session);
+
+void                   gst_rtsp_session_set_timeout          (GstRTSPSession *session, guint timeout);
+guint                  gst_rtsp_session_get_timeout          (GstRTSPSession *session);
+
+/* touch the session, update last_access */
+void                   gst_rtsp_session_touch                (GstRTSPSession *session);
+
 /* handle media in a session */
 GstRTSPSessionMedia *  gst_rtsp_session_manage_media         (GstRTSPSession *sess,
                                                               const GstRTSPUrl *uri,