From aedd4652f386747efc65c6d230600e7422c8cc4a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 4 Feb 2009 19:52:50 +0100 Subject: [PATCH] Add beginnings of session timeouts and limits 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 | 12 ++- gst/rtsp-server/rtsp-session-pool.c | 167 ++++++++++++++++++++++++++++++++++-- gst/rtsp-server/rtsp-session-pool.h | 18 +++- gst/rtsp-server/rtsp-session.c | 142 +++++++++++++++++++++++++++++- gst/rtsp-server/rtsp-session.h | 19 +++- 5 files changed, 341 insertions(+), 17 deletions(-) diff --git a/gst/rtsp-server/rtsp-client.c b/gst/rtsp-server/rtsp-client.c index 0f18d21..8405d22 100644 --- a/gst/rtsp-server/rtsp-client.c +++ b/gst/rtsp-server/rtsp-client.c @@ -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); diff --git a/gst/rtsp-server/rtsp-session-pool.c b/gst/rtsp-server/rtsp-session-pool.c index 4260498..8ce18ef 100644 --- a/gst/rtsp-server/rtsp-session-pool.c +++ b/gst/rtsp-server/rtsp-session-pool.c @@ -21,6 +21,19 @@ #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; } diff --git a/gst/rtsp-server/rtsp-session-pool.h b/gst/rtsp-server/rtsp-session-pool.h index 9f65f41..30df9bd 100644 --- a/gst/rtsp-server/rtsp-session-pool.h +++ b/gst/rtsp-server/rtsp-session-pool.h @@ -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 diff --git a/gst/rtsp-server/rtsp-session.c b/gst/rtsp-server/rtsp-session.c index 8596ea7..3c538b4 100644 --- a/gst/rtsp-server/rtsp-session.c +++ b/gst/rtsp-server/rtsp-session.c @@ -22,6 +22,20 @@ #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; diff --git a/gst/rtsp-server/rtsp-session.h b/gst/rtsp-server/rtsp-session.h index 08104f1..d87922b 100644 --- a/gst/rtsp-server/rtsp-session.h +++ b/gst/rtsp-server/rtsp-session.h @@ -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, -- 2.7.4