session-pool: add session-removed signal
[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., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 /**
20  * SECTION:rtsp-session-pool
21  * @short_description: An object for managing sessions
22  * @see_also: #GstRTSPSession
23  *
24  * The #GstRTSPSessionPool object manages a list of #GstRTSPSession objects.
25  *
26  * The maximum number of sessions can be configured with
27  * gst_rtsp_session_pool_set_max_sessions(). The current number of sessions can
28  * be retrieved with gst_rtsp_session_pool_get_n_sessions().
29  *
30  * Use gst_rtsp_session_pool_create() to create a new #GstRTSPSession object.
31  * The session object can be found again with its id and
32  * gst_rtsp_session_pool_find().
33  *
34  * All sessions can be iterated with gst_rtsp_session_pool_filter().
35  *
36  * Run gst_rtsp_session_pool_cleanup() periodically to remove timed out sessions
37  * or use gst_rtsp_session_pool_create_watch() to be notified when session
38  * cleanup should be performed.
39  *
40  * Last reviewed on 2013-07-11 (1.0.0)
41  */
42
43 #include "rtsp-session-pool.h"
44
45 #define GST_RTSP_SESSION_POOL_GET_PRIVATE(obj)  \
46          (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_SESSION_POOL, GstRTSPSessionPoolPrivate))
47
48 struct _GstRTSPSessionPoolPrivate
49 {
50   GMutex lock;                  /* protects everything in this struct */
51   guint max_sessions;
52   GHashTable *sessions;
53 };
54
55 #define DEFAULT_MAX_SESSIONS 0
56
57 enum
58 {
59   PROP_0,
60   PROP_MAX_SESSIONS,
61   PROP_LAST
62 };
63
64 static const gchar session_id_charset[] =
65     { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
66   'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
67   'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
68   'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7',
69   '8', '9', '$', '-', '_', '.', '+'
70 };
71
72 enum
73 {
74   SIGNAL_SESSION_REMOVED,
75   SIGNAL_LAST
76 };
77
78 static guint gst_rtsp_session_pool_signals[SIGNAL_LAST] = { 0 };
79
80 GST_DEBUG_CATEGORY_STATIC (rtsp_session_debug);
81 #define GST_CAT_DEFAULT rtsp_session_debug
82
83 static void gst_rtsp_session_pool_get_property (GObject * object, guint propid,
84     GValue * value, GParamSpec * pspec);
85 static void gst_rtsp_session_pool_set_property (GObject * object, guint propid,
86     const GValue * value, GParamSpec * pspec);
87 static void gst_rtsp_session_pool_finalize (GObject * object);
88
89 static gchar *create_session_id (GstRTSPSessionPool * pool);
90 static GstRTSPSession *create_session (GstRTSPSessionPool * pool,
91     const gchar * id);
92
93 G_DEFINE_TYPE (GstRTSPSessionPool, gst_rtsp_session_pool, G_TYPE_OBJECT);
94
95 static void
96 gst_rtsp_session_pool_class_init (GstRTSPSessionPoolClass * klass)
97 {
98   GObjectClass *gobject_class;
99
100   g_type_class_add_private (klass, sizeof (GstRTSPSessionPoolPrivate));
101
102   gobject_class = G_OBJECT_CLASS (klass);
103
104   gobject_class->get_property = gst_rtsp_session_pool_get_property;
105   gobject_class->set_property = gst_rtsp_session_pool_set_property;
106   gobject_class->finalize = gst_rtsp_session_pool_finalize;
107
108   g_object_class_install_property (gobject_class, PROP_MAX_SESSIONS,
109       g_param_spec_uint ("max-sessions", "Max Sessions",
110           "the maximum amount of sessions (0 = unlimited)",
111           0, G_MAXUINT, DEFAULT_MAX_SESSIONS,
112           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
113
114   gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED] =
115       g_signal_new ("session-removed", G_TYPE_FROM_CLASS (klass),
116       G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPSessionPoolClass,
117           session_removed), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE,
118       1, GST_TYPE_RTSP_SESSION);
119
120   klass->create_session_id = create_session_id;
121   klass->create_session = create_session;
122
123   GST_DEBUG_CATEGORY_INIT (rtsp_session_debug, "rtspsessionpool", 0,
124       "GstRTSPSessionPool");
125 }
126
127 static void
128 gst_rtsp_session_pool_init (GstRTSPSessionPool * pool)
129 {
130   GstRTSPSessionPoolPrivate *priv = GST_RTSP_SESSION_POOL_GET_PRIVATE (pool);
131
132   pool->priv = priv;
133
134   g_mutex_init (&priv->lock);
135   priv->sessions = g_hash_table_new_full (g_str_hash, g_str_equal,
136       NULL, g_object_unref);
137   priv->max_sessions = DEFAULT_MAX_SESSIONS;
138 }
139
140 static GstRTSPFilterResult
141 remove_sessions_func (GstRTSPSessionPool * pool, GstRTSPSession * session,
142     gpointer user_data)
143 {
144   return GST_RTSP_FILTER_REMOVE;
145 }
146
147 static void
148 gst_rtsp_session_pool_finalize (GObject * object)
149 {
150   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
151   GstRTSPSessionPoolPrivate *priv = pool->priv;
152
153   gst_rtsp_session_pool_filter (pool, remove_sessions_func, NULL);
154   g_hash_table_unref (priv->sessions);
155   g_mutex_clear (&priv->lock);
156
157   G_OBJECT_CLASS (gst_rtsp_session_pool_parent_class)->finalize (object);
158 }
159
160 static void
161 gst_rtsp_session_pool_get_property (GObject * object, guint propid,
162     GValue * value, GParamSpec * pspec)
163 {
164   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
165
166   switch (propid) {
167     case PROP_MAX_SESSIONS:
168       g_value_set_uint (value, gst_rtsp_session_pool_get_max_sessions (pool));
169       break;
170     default:
171       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
172       break;
173   }
174 }
175
176 static void
177 gst_rtsp_session_pool_set_property (GObject * object, guint propid,
178     const GValue * value, GParamSpec * pspec)
179 {
180   GstRTSPSessionPool *pool = GST_RTSP_SESSION_POOL (object);
181
182   switch (propid) {
183     case PROP_MAX_SESSIONS:
184       gst_rtsp_session_pool_set_max_sessions (pool, g_value_get_uint (value));
185       break;
186     default:
187       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
188       break;
189   }
190 }
191
192 /**
193  * gst_rtsp_session_pool_new:
194  *
195  * Create a new #GstRTSPSessionPool instance.
196  *
197  * Returns: (transfer full): A new #GstRTSPSessionPool. g_object_unref() after
198  * usage.
199  */
200 GstRTSPSessionPool *
201 gst_rtsp_session_pool_new (void)
202 {
203   GstRTSPSessionPool *result;
204
205   result = g_object_new (GST_TYPE_RTSP_SESSION_POOL, NULL);
206
207   return result;
208 }
209
210 /**
211  * gst_rtsp_session_pool_set_max_sessions:
212  * @pool: a #GstRTSPSessionPool
213  * @max: the maximum number of sessions
214  *
215  * Configure the maximum allowed number of sessions in @pool to @max.
216  * A value of 0 means an unlimited amount of sessions.
217  */
218 void
219 gst_rtsp_session_pool_set_max_sessions (GstRTSPSessionPool * pool, guint max)
220 {
221   GstRTSPSessionPoolPrivate *priv;
222
223   g_return_if_fail (GST_IS_RTSP_SESSION_POOL (pool));
224
225   priv = pool->priv;
226
227   g_mutex_lock (&priv->lock);
228   priv->max_sessions = max;
229   g_mutex_unlock (&priv->lock);
230 }
231
232 /**
233  * gst_rtsp_session_pool_get_max_sessions:
234  * @pool: a #GstRTSPSessionPool
235  *
236  * Get the maximum allowed number of sessions in @pool. 0 means an unlimited
237  * amount of sessions.
238  *
239  * Returns: the maximum allowed number of sessions.
240  */
241 guint
242 gst_rtsp_session_pool_get_max_sessions (GstRTSPSessionPool * pool)
243 {
244   GstRTSPSessionPoolPrivate *priv;
245   guint result;
246
247   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
248
249   priv = pool->priv;
250
251   g_mutex_lock (&priv->lock);
252   result = priv->max_sessions;
253   g_mutex_unlock (&priv->lock);
254
255   return result;
256 }
257
258 /**
259  * gst_rtsp_session_pool_get_n_sessions:
260  * @pool: a #GstRTSPSessionPool
261  *
262  * Get the amount of active sessions in @pool.
263  *
264  * Returns: the amount of active sessions in @pool.
265  */
266 guint
267 gst_rtsp_session_pool_get_n_sessions (GstRTSPSessionPool * pool)
268 {
269   GstRTSPSessionPoolPrivate *priv;
270   guint result;
271
272   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
273
274   priv = pool->priv;
275
276   g_mutex_lock (&priv->lock);
277   result = g_hash_table_size (priv->sessions);
278   g_mutex_unlock (&priv->lock);
279
280   return result;
281 }
282
283 /**
284  * gst_rtsp_session_pool_find:
285  * @pool: the pool to search
286  * @sessionid: the session id
287  *
288  * Find the session with @sessionid in @pool. The access time of the session
289  * will be updated with gst_rtsp_session_touch().
290  *
291  * Returns: (transfer full) (nullable): the #GstRTSPSession with @sessionid
292  * or %NULL when the session did not exist. g_object_unref() after usage.
293  */
294 GstRTSPSession *
295 gst_rtsp_session_pool_find (GstRTSPSessionPool * pool, const gchar * sessionid)
296 {
297   GstRTSPSessionPoolPrivate *priv;
298   GstRTSPSession *result;
299
300   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
301   g_return_val_if_fail (sessionid != NULL, NULL);
302
303   priv = pool->priv;
304
305   g_mutex_lock (&priv->lock);
306   result = g_hash_table_lookup (priv->sessions, sessionid);
307   if (result) {
308     g_object_ref (result);
309     gst_rtsp_session_touch (result);
310   }
311   g_mutex_unlock (&priv->lock);
312
313   return result;
314 }
315
316 static gchar *
317 create_session_id (GstRTSPSessionPool * pool)
318 {
319   gchar id[17];
320   gint i;
321
322   for (i = 0; i < 16; i++) {
323     id[i] =
324         session_id_charset[g_random_int_range (0,
325             G_N_ELEMENTS (session_id_charset))];
326   }
327   id[16] = 0;
328
329   return g_uri_escape_string (id, NULL, FALSE);
330 }
331
332 static GstRTSPSession *
333 create_session (GstRTSPSessionPool * pool, const gchar * id)
334 {
335   return gst_rtsp_session_new (id);
336 }
337
338 /**
339  * gst_rtsp_session_pool_create:
340  * @pool: a #GstRTSPSessionPool
341  *
342  * Create a new #GstRTSPSession object in @pool.
343  *
344  * Returns: (transfer full): a new #GstRTSPSession.
345  */
346 GstRTSPSession *
347 gst_rtsp_session_pool_create (GstRTSPSessionPool * pool)
348 {
349   GstRTSPSessionPoolPrivate *priv;
350   GstRTSPSession *result = NULL;
351   GstRTSPSessionPoolClass *klass;
352   gchar *id = NULL;
353   guint retry;
354
355   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
356
357   priv = pool->priv;
358
359   klass = GST_RTSP_SESSION_POOL_GET_CLASS (pool);
360
361   retry = 0;
362   do {
363     /* start by creating a new random session id, we assume that this is random
364      * enough to not cause a collision, which we will check later  */
365     if (klass->create_session_id)
366       id = klass->create_session_id (pool);
367     else
368       goto no_function;
369
370     if (id == NULL)
371       goto no_session;
372
373     g_mutex_lock (&priv->lock);
374     /* check session limit */
375     if (priv->max_sessions > 0) {
376       if (g_hash_table_size (priv->sessions) >= priv->max_sessions)
377         goto too_many_sessions;
378     }
379     /* check if the sessionid existed */
380     result = g_hash_table_lookup (priv->sessions, id);
381     if (result) {
382       /* found, retry with a different session id */
383       result = NULL;
384       retry++;
385       if (retry > 100)
386         goto collision;
387     } else {
388       /* not found, create session and insert it in the pool */
389       if (klass->create_session)
390         result = create_session (pool, id);
391       if (result == NULL)
392         goto too_many_sessions;
393       /* take additional ref for the pool */
394       g_object_ref (result);
395       g_hash_table_insert (priv->sessions,
396           (gchar *) gst_rtsp_session_get_sessionid (result), result);
397     }
398     g_mutex_unlock (&priv->lock);
399
400     g_free (id);
401   } while (result == NULL);
402
403   return result;
404
405   /* ERRORS */
406 no_function:
407   {
408     GST_WARNING ("no create_session_id vmethod in GstRTSPSessionPool %p", pool);
409     return NULL;
410   }
411 no_session:
412   {
413     GST_WARNING ("can't create session id with GstRTSPSessionPool %p", pool);
414     return NULL;
415   }
416 collision:
417   {
418     GST_WARNING ("can't find unique sessionid for GstRTSPSessionPool %p", pool);
419     g_mutex_unlock (&priv->lock);
420     g_free (id);
421     return NULL;
422   }
423 too_many_sessions:
424   {
425     GST_WARNING ("session pool reached max sessions of %d", priv->max_sessions);
426     g_mutex_unlock (&priv->lock);
427     g_free (id);
428     return NULL;
429   }
430 }
431
432 /**
433  * gst_rtsp_session_pool_remove:
434  * @pool: a #GstRTSPSessionPool
435  * @sess: (transfer none): a #GstRTSPSession
436  *
437  * Remove @sess from @pool, releasing the ref that the pool has on @sess.
438  *
439  * Returns: %TRUE if the session was found and removed.
440  */
441 gboolean
442 gst_rtsp_session_pool_remove (GstRTSPSessionPool * pool, GstRTSPSession * sess)
443 {
444   GstRTSPSessionPoolPrivate *priv;
445   gboolean found;
446
447   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), FALSE);
448   g_return_val_if_fail (GST_IS_RTSP_SESSION (sess), FALSE);
449
450   priv = pool->priv;
451
452   g_mutex_lock (&priv->lock);
453   g_object_ref (sess);
454   found =
455       g_hash_table_remove (priv->sessions,
456       gst_rtsp_session_get_sessionid (sess));
457   if (found) {
458     g_signal_emit (pool, gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED],
459         0, sess);
460   }
461   g_object_unref (sess);
462   g_mutex_unlock (&priv->lock);
463
464   return found;
465 }
466
467 typedef struct
468 {
469   GTimeVal now;
470   GstRTSPSessionPool *pool;
471 } CleanupData;
472
473 static gboolean
474 cleanup_func (gchar * sessionid, GstRTSPSession * sess, CleanupData * data)
475 {
476   gboolean expired;
477
478   expired = gst_rtsp_session_is_expired (sess, &data->now);
479   if (expired) {
480     GST_DEBUG ("session expired, emitting signal");
481     g_signal_emit (data->pool,
482         gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED], 0, sess);
483   }
484   return expired;
485 }
486
487 /**
488  * gst_rtsp_session_pool_cleanup:
489  * @pool: a #GstRTSPSessionPool
490  *
491  * Inspect all the sessions in @pool and remove the sessions that are inactive
492  * for more than their timeout.
493  *
494  * Returns: the amount of sessions that got removed.
495  */
496 guint
497 gst_rtsp_session_pool_cleanup (GstRTSPSessionPool * pool)
498 {
499   GstRTSPSessionPoolPrivate *priv;
500   guint result;
501   CleanupData data;
502
503   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), 0);
504
505   priv = pool->priv;
506
507   g_get_current_time (&data.now);
508   data.pool = pool;
509
510   g_mutex_lock (&priv->lock);
511   result =
512       g_hash_table_foreach_remove (priv->sessions, (GHRFunc) cleanup_func,
513       &data);
514   g_mutex_unlock (&priv->lock);
515
516   return result;
517 }
518
519 typedef struct
520 {
521   GstRTSPSessionPool *pool;
522   GstRTSPSessionPoolFilterFunc func;
523   gpointer user_data;
524   GList *list;
525 } FilterData;
526
527 static gboolean
528 filter_func (gchar * sessionid, GstRTSPSession * sess, FilterData * data)
529 {
530   GstRTSPFilterResult res;
531
532   if (data->func)
533     res = data->func (data->pool, sess, data->user_data);
534   else
535     res = GST_RTSP_FILTER_REF;
536
537   switch (res) {
538     case GST_RTSP_FILTER_REMOVE:
539       g_signal_emit (data->pool,
540           gst_rtsp_session_pool_signals[SIGNAL_SESSION_REMOVED], 0, sess);
541       return TRUE;
542     case GST_RTSP_FILTER_REF:
543       /* keep ref */
544       data->list = g_list_prepend (data->list, g_object_ref (sess));
545       /* fallthrough */
546     default:
547     case GST_RTSP_FILTER_KEEP:
548       return FALSE;
549   }
550 }
551
552 /**
553  * gst_rtsp_session_pool_filter:
554  * @pool: a #GstRTSPSessionPool
555  * @func: (scope call) (allow-none): a callback
556  * @user_data: (closure): user data passed to @func
557  *
558  * Call @func for each session in @pool. The result value of @func determines
559  * what happens to the session. @func will be called with the session pool
560  * locked so no further actions on @pool can be performed from @func.
561  *
562  * If @func returns #GST_RTSP_FILTER_REMOVE, the session will be set to the
563  * expired state with gst_rtsp_session_set_expired() and removed from
564  * @pool.
565  *
566  * If @func returns #GST_RTSP_FILTER_KEEP, the session will remain in @pool.
567  *
568  * If @func returns #GST_RTSP_FILTER_REF, the session will remain in @pool but
569  * will also be added with an additional ref to the result GList of this
570  * function..
571  *
572  * When @func is %NULL, #GST_RTSP_FILTER_REF will be assumed for all sessions.
573  *
574  * Returns: (element-type GstRTSPSession) (transfer full): a GList with all
575  * sessions for which @func returned #GST_RTSP_FILTER_REF. After usage, each
576  * element in the GList should be unreffed before the list is freed.
577  */
578 GList *
579 gst_rtsp_session_pool_filter (GstRTSPSessionPool * pool,
580     GstRTSPSessionPoolFilterFunc func, gpointer user_data)
581 {
582   GstRTSPSessionPoolPrivate *priv;
583   FilterData data;
584
585   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
586
587   priv = pool->priv;
588
589   data.pool = pool;
590   data.func = func;
591   data.user_data = user_data;
592   data.list = NULL;
593
594   g_mutex_lock (&priv->lock);
595   g_hash_table_foreach_remove (priv->sessions, (GHRFunc) filter_func, &data);
596   g_mutex_unlock (&priv->lock);
597
598   return data.list;
599 }
600
601 typedef struct
602 {
603   GSource source;
604   GstRTSPSessionPool *pool;
605   gint timeout;
606 } GstPoolSource;
607
608 static void
609 collect_timeout (gchar * sessionid, GstRTSPSession * sess, GstPoolSource * psrc)
610 {
611   gint timeout;
612   GTimeVal now;
613
614   g_get_current_time (&now);
615
616   timeout = gst_rtsp_session_next_timeout (sess, &now);
617   GST_INFO ("%p: next timeout: %d", sess, timeout);
618   if (psrc->timeout == -1 || timeout < psrc->timeout)
619     psrc->timeout = timeout;
620 }
621
622 static gboolean
623 gst_pool_source_prepare (GSource * source, gint * timeout)
624 {
625   GstRTSPSessionPoolPrivate *priv;
626   GstPoolSource *psrc;
627   gboolean result;
628
629   psrc = (GstPoolSource *) source;
630   psrc->timeout = -1;
631   priv = psrc->pool->priv;
632
633   g_mutex_lock (&priv->lock);
634   g_hash_table_foreach (priv->sessions, (GHFunc) collect_timeout, psrc);
635   g_mutex_unlock (&priv->lock);
636
637   if (timeout)
638     *timeout = psrc->timeout;
639
640   result = psrc->timeout == 0;
641
642   GST_INFO ("prepare %d, %d", psrc->timeout, result);
643
644   return result;
645 }
646
647 static gboolean
648 gst_pool_source_check (GSource * source)
649 {
650   GST_INFO ("check");
651
652   return gst_pool_source_prepare (source, NULL);
653 }
654
655 static gboolean
656 gst_pool_source_dispatch (GSource * source, GSourceFunc callback,
657     gpointer user_data)
658 {
659   gboolean res;
660   GstPoolSource *psrc = (GstPoolSource *) source;
661   GstRTSPSessionPoolFunc func = (GstRTSPSessionPoolFunc) callback;
662
663   GST_INFO ("dispatch");
664
665   if (func)
666     res = func (psrc->pool, user_data);
667   else
668     res = FALSE;
669
670   return res;
671 }
672
673 static void
674 gst_pool_source_finalize (GSource * source)
675 {
676   GstPoolSource *psrc = (GstPoolSource *) source;
677
678   GST_INFO ("finalize %p", psrc);
679
680   g_object_unref (psrc->pool);
681   psrc->pool = NULL;
682 }
683
684 static GSourceFuncs gst_pool_source_funcs = {
685   gst_pool_source_prepare,
686   gst_pool_source_check,
687   gst_pool_source_dispatch,
688   gst_pool_source_finalize
689 };
690
691 /**
692  * gst_rtsp_session_pool_create_watch:
693  * @pool: a #GstRTSPSessionPool
694  *
695  * Create a #GSource that will be dispatched when the session should be cleaned
696  * up.
697  *
698  * Returns: (transfer full): a #GSource
699  */
700 GSource *
701 gst_rtsp_session_pool_create_watch (GstRTSPSessionPool * pool)
702 {
703   GstPoolSource *source;
704
705   g_return_val_if_fail (GST_IS_RTSP_SESSION_POOL (pool), NULL);
706
707   source = (GstPoolSource *) g_source_new (&gst_pool_source_funcs,
708       sizeof (GstPoolSource));
709   source->pool = g_object_ref (pool);
710
711   return (GSource *) source;
712 }