[libgdata] core: Ensure GDataBatchOperation's operations' callbacks are always called
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libgdata] core: Ensure GDataBatchOperation's operations' callbacks are always called
- Date: Fri, 10 Dec 2010 02:29:52 +0000 (UTC)
commit 17142ca07f96d671ad07c8be48df5892bd8187c2
Author: Philip Withnall <philip tecnocode co uk>
Date: Thu Dec 9 21:45:51 2010 +0000
core: Ensure GDataBatchOperation's operations' callbacks are always called
Even in error conditions, the callbacks for each of the operations in a
GDataBatchOperation should be called, or memory leaks could result.
gdata/gdata-batch-feed.c | 6 +-
gdata/gdata-batch-operation.c | 83 +++++++++++++++++++++++++++++++----------
gdata/tests/common.c | 59 +++++++++++++++++++++++++++++
gdata/tests/common.h | 3 +
gdata/tests/documents.c | 31 ++++++++++-----
5 files changed, 149 insertions(+), 33 deletions(-)
---
diff --git a/gdata/gdata-batch-feed.c b/gdata/gdata-batch-feed.c
index 46df2cb..26e8320 100644
--- a/gdata/gdata-batch-feed.c
+++ b/gdata/gdata-batch-feed.c
@@ -145,10 +145,10 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
if (op->type != GDATA_BATCH_OPERATION_DELETION && entry == NULL)
goto error;
- if (entry != NULL) {
- _gdata_batch_operation_run_callback (operation, op, entry, NULL);
+ _gdata_batch_operation_run_callback (operation, op, entry, NULL);
+
+ if (entry != NULL)
g_object_unref (entry);
- }
g_free (status_reason);
xmlBufferFree (status_response);
diff --git a/gdata/gdata-batch-operation.c b/gdata/gdata-batch-operation.c
index df3617a..9783b15 100644
--- a/gdata/gdata-batch-operation.c
+++ b/gdata/gdata-batch-operation.c
@@ -298,13 +298,16 @@ _gdata_batch_operation_get_operation (GDataBatchOperation *self, guint id)
}
/* Run a user-supplied callback for a #BatchOperation whose return value we've just processed. This is designed to be used in an idle handler, so
- * that the callback is run in the main thread. It should not be called if the user-supplied callback is %NULL. */
+ * that the callback is run in the main thread. It can be called if the user-supplied callback is %NULL (e.g. in the case that the callback's been
+ * called before). */
static gboolean
run_callback_cb (BatchOperation *op)
{
- /* We do allow op->callback to be unset, but in that case run_callback_cb() shouldn't be being called */
- g_assert (op->callback != NULL);
- op->callback (op->id, op->type, op->entry, op->error, op->user_data);
+ if (op->callback != NULL)
+ op->callback (op->id, op->type, op->entry, op->error, op->user_data);
+
+ /* Unset the callback so that it can't be called again */
+ op->callback = NULL;
return FALSE;
}
@@ -549,6 +552,9 @@ run_cb (gpointer key, BatchOperation *op, GDataFeed *feed)
* callbacks synchronously (i.e. before gdata_batch_operation_run() returns, and in the same thread that called gdata_batch_operation_run()) as the
* server returns results for each operation.
*
+ * The callbacks for all of the operations in the batch operation are always guaranteed to be called, even if the batch operation as a whole fails.
+ * Each callback will be called exactly once for each time gdata_batch_operation_run() is called.
+ *
* The return value of the function indicates whether the overall batch operation was successful, and doesn't indicate the status of any of the
* operations it comprises. gdata_batch_operation_run() could return %TRUE even if all of its operations failed.
*
@@ -568,6 +574,10 @@ gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable,
GTimeVal updated;
gchar *upload_data;
guint status;
+ GHashTableIter iter;
+ gpointer op_id;
+ BatchOperation *op;
+ GError *child_error = NULL;
g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
@@ -590,33 +600,43 @@ gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable,
priv->has_run = TRUE;
/* Send the message */
- status = _gdata_service_send_message (priv->service, message, cancellable, error);
-
- if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
- /* Redirect error or cancelled */
- g_object_unref (message);
- return FALSE;
- } else if (status != SOUP_STATUS_OK) {
- /* Error */
- GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (priv->service);
- g_assert (klass->parse_error_response != NULL);
- klass->parse_error_response (priv->service, GDATA_OPERATION_BATCH, status, message->reason_phrase, message->response_body->data,
- message->response_body->length, error);
+ status = _gdata_service_send_message (priv->service, message, cancellable, &child_error);
+
+ if (status != SOUP_STATUS_OK) {
+ /* Iff status is SOUP_STATUS_NONE or SOUP_STATUS_CANCELLED, child_error has already been set */
+ if (status != SOUP_STATUS_NONE && status != SOUP_STATUS_CANCELLED) {
+ /* Error */
+ GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (priv->service);
+ g_assert (klass->parse_error_response != NULL);
+ klass->parse_error_response (priv->service, GDATA_OPERATION_BATCH, status, message->reason_phrase, message->response_body->data,
+ message->response_body->length, &child_error);
+ }
g_object_unref (message);
- return FALSE;
+
+ goto error;
}
/* Parse the XML; GDataBatchFeed will fire off the relevant callbacks */
g_assert (message->response_body->data != NULL);
feed = GDATA_FEED (_gdata_parsable_new_from_xml (GDATA_TYPE_BATCH_FEED, message->response_body->data, message->response_body->length,
- self, error));
+ self, &child_error));
g_object_unref (message);
if (feed == NULL)
- return FALSE;
+ goto error;
g_object_unref (feed);
return TRUE;
+
+error:
+ /* Call the callbacks for each of our operations to notify them of the error */
+ g_hash_table_iter_init (&iter, priv->operations);
+ while (g_hash_table_iter_next (&iter, &op_id, (gpointer*) &op) == TRUE)
+ _gdata_batch_operation_run_callback (self, op, NULL, g_error_copy (child_error));
+
+ g_propagate_error (error, child_error);
+
+ return FALSE;
}
static void
@@ -688,6 +708,8 @@ gdata_batch_operation_run_async (GDataBatchOperation *self, GCancellable *cancel
gboolean
gdata_batch_operation_run_finish (GDataBatchOperation *self, GAsyncResult *async_result, GError **error)
{
+ GDataBatchOperationPrivate *priv = self->priv;
+ GError *child_error = NULL;
GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (async_result);
g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), FALSE);
@@ -696,8 +718,29 @@ gdata_batch_operation_run_finish (GDataBatchOperation *self, GAsyncResult *async
g_warn_if_fail (g_simple_async_result_get_source_tag (result) == gdata_batch_operation_run_async);
- if (g_simple_async_result_propagate_error (result, error) == TRUE)
+ if (g_simple_async_result_propagate_error (result, &child_error) == TRUE) {
+ if (priv->has_run == FALSE) {
+ GHashTableIter iter;
+ gpointer op_id;
+ BatchOperation *op;
+
+ /* Temporarily mark the operation as synchronous so that the callbacks get dispatched in this thread */
+ priv->is_async = FALSE;
+
+ /* If has_run hasn't been set, the call to gdata_batch_operation_run() was never made in the thread, and so none of the
+ * operations' callbacks have been called. Call the callbacks for each of our operations to notify them of the error.
+ * If has_run has been set, gdata_batch_operation_run() has already done this for us. */
+ g_hash_table_iter_init (&iter, priv->operations);
+ while (g_hash_table_iter_next (&iter, &op_id, (gpointer*) &op) == TRUE)
+ _gdata_batch_operation_run_callback (self, op, NULL, g_error_copy (child_error));
+
+ priv->is_async = TRUE;
+ }
+
+ g_propagate_error (error, child_error);
+
return FALSE;
+ }
return g_simple_async_result_get_op_res_gboolean (result);
}
diff --git a/gdata/tests/common.c b/gdata/tests/common.c
index 37d3e7d..7391957 100644
--- a/gdata/tests/common.c
+++ b/gdata/tests/common.c
@@ -84,6 +84,7 @@ gdata_test_internet (void)
}
typedef struct {
+ GDataBatchOperation *operation;
guint op_id;
GDataBatchOperationType operation_type;
GDataEntry *entry;
@@ -96,6 +97,8 @@ typedef struct {
static void
batch_operation_data_free (BatchOperationData *data)
{
+ if (data->operation != NULL)
+ g_object_unref (data->operation);
if (data->entry != NULL)
g_object_unref (data->entry);
g_free (data->id);
@@ -110,6 +113,10 @@ test_batch_operation_query_cb (guint operation_id, GDataBatchOperationType opera
{
BatchOperationData *data = user_data;
+ /* Mark the callback as having been run */
+ g_object_set_data (G_OBJECT (data->operation), "test::called-callbacks",
+ GUINT_TO_POINTER (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->operation), "test::called-callbacks")) + 1));
+
/* Check that the @operation_type and @operation_id matches those stored in @data */
g_assert_cmpuint (operation_id, ==, data->op_id);
g_assert_cmpuint (operation_type, ==, data->operation_type);
@@ -159,6 +166,7 @@ gdata_test_batch_operation_query (GDataBatchOperation *operation, const gchar *i
BatchOperationData *data;
data = g_slice_new (BatchOperationData);
+ data->operation = g_object_ref (operation);
data->op_id = 0;
data->operation_type = GDATA_BATCH_OPERATION_QUERY;
data->entry = g_object_ref (entry);
@@ -171,6 +179,10 @@ gdata_test_batch_operation_query (GDataBatchOperation *operation, const gchar *i
data->op_id = op_id;
+ /* We expect a callback to be called when the operation is run */
+ g_object_set_data (G_OBJECT (operation), "test::expected-callbacks",
+ GUINT_TO_POINTER (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (operation), "test::expected-callbacks")) + 1));
+
return op_id;
}
@@ -180,6 +192,10 @@ test_batch_operation_insertion_update_cb (guint operation_id, GDataBatchOperatio
{
BatchOperationData *data = user_data;
+ /* Mark the callback as having been run */
+ g_object_set_data (G_OBJECT (data->operation), "test::called-callbacks",
+ GUINT_TO_POINTER (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->operation), "test::called-callbacks")) + 1));
+
/* Check that the @operation_type and @operation_id matches those stored in @data */
g_assert_cmpuint (operation_id, ==, data->op_id);
g_assert_cmpuint (operation_type, ==, data->operation_type);
@@ -225,6 +241,7 @@ gdata_test_batch_operation_insertion (GDataBatchOperation *operation, GDataEntry
BatchOperationData *data;
data = g_slice_new (BatchOperationData);
+ data->operation = g_object_ref (operation);
data->op_id = 0;
data->operation_type = GDATA_BATCH_OPERATION_INSERTION;
data->entry = g_object_ref (entry);
@@ -237,6 +254,10 @@ gdata_test_batch_operation_insertion (GDataBatchOperation *operation, GDataEntry
data->op_id = op_id;
+ /* We expect a callback to be called when the operation is run */
+ g_object_set_data (G_OBJECT (operation), "test::expected-callbacks",
+ GUINT_TO_POINTER (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (operation), "test::expected-callbacks")) + 1));
+
return op_id;
}
@@ -247,6 +268,7 @@ gdata_test_batch_operation_update (GDataBatchOperation *operation, GDataEntry *e
BatchOperationData *data;
data = g_slice_new (BatchOperationData);
+ data->operation = g_object_ref (operation);
data->op_id = 0;
data->operation_type = GDATA_BATCH_OPERATION_UPDATE;
data->entry = g_object_ref (entry);
@@ -259,6 +281,10 @@ gdata_test_batch_operation_update (GDataBatchOperation *operation, GDataEntry *e
data->op_id = op_id;
+ /* We expect a callback to be called when the operation is run */
+ g_object_set_data (G_OBJECT (operation), "test::expected-callbacks",
+ GUINT_TO_POINTER (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (operation), "test::expected-callbacks")) + 1));
+
return op_id;
}
@@ -267,6 +293,10 @@ test_batch_operation_deletion_cb (guint operation_id, GDataBatchOperationType op
{
BatchOperationData *data = user_data;
+ /* Mark the callback as having been run */
+ g_object_set_data (G_OBJECT (data->operation), "test::called-callbacks",
+ GUINT_TO_POINTER (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (data->operation), "test::called-callbacks")) + 1));
+
/* Check that the @operation_type and @operation_id matches those stored in @data */
g_assert_cmpuint (operation_id, ==, data->op_id);
g_assert_cmpuint (operation_type, ==, data->operation_type);
@@ -291,6 +321,7 @@ gdata_test_batch_operation_deletion (GDataBatchOperation *operation, GDataEntry
BatchOperationData *data;
data = g_slice_new (BatchOperationData);
+ data->operation = g_object_ref (operation);
data->op_id = 0;
data->operation_type = GDATA_BATCH_OPERATION_DELETION;
data->entry = g_object_ref (entry);
@@ -303,5 +334,33 @@ gdata_test_batch_operation_deletion (GDataBatchOperation *operation, GDataEntry
data->op_id = op_id;
+ /* We expect a callback to be called when the operation is run */
+ g_object_set_data (G_OBJECT (operation), "test::expected-callbacks",
+ GUINT_TO_POINTER (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (operation), "test::expected-callbacks")) + 1));
+
return op_id;
}
+
+gboolean
+gdata_test_batch_operation_run (GDataBatchOperation *operation, GCancellable *cancellable, GError **error)
+{
+ gboolean success = gdata_batch_operation_run (operation, cancellable, error);
+
+ /* Assert that callbacks were called exactly once for each operation in the batch operation */
+ g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (operation), "test::expected-callbacks")), ==,
+ GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (operation), "test::called-callbacks")));
+
+ return success;
+}
+
+gboolean
+gdata_test_batch_operation_run_finish (GDataBatchOperation *operation, GAsyncResult *async_result, GError **error)
+{
+ gboolean success = gdata_batch_operation_run_finish (operation, async_result, error);
+
+ /* Assert that callbacks were called exactly once for each operation in the batch operation */
+ g_assert_cmpuint (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (operation), "test::expected-callbacks")), ==,
+ GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (operation), "test::called-callbacks")));
+
+ return success;
+}
diff --git a/gdata/tests/common.h b/gdata/tests/common.h
index 4c9492d..74746ee 100644
--- a/gdata/tests/common.h
+++ b/gdata/tests/common.h
@@ -40,6 +40,9 @@ guint gdata_test_batch_operation_insertion (GDataBatchOperation *operation, GDat
guint gdata_test_batch_operation_update (GDataBatchOperation *operation, GDataEntry *entry, GDataEntry **updated_entry, GError **error);
guint gdata_test_batch_operation_deletion (GDataBatchOperation *operation, GDataEntry *entry, GError **error);
+gboolean gdata_test_batch_operation_run (GDataBatchOperation *operation, GCancellable *cancellable, GError **error);
+gboolean gdata_test_batch_operation_run_finish (GDataBatchOperation *operation, GAsyncResult *async_result, GError **error);
+
G_END_DECLS
#endif /* !GDATA_TEST_COMMON_H */
diff --git a/gdata/tests/documents.c b/gdata/tests/documents.c
index fc1ee0b..0995978 100644
--- a/gdata/tests/documents.c
+++ b/gdata/tests/documents.c
@@ -1125,7 +1125,7 @@ test_batch (gconstpointer service)
gdata_entry_set_title (GDATA_ENTRY (doc), "My First Document");
gdata_test_batch_operation_insertion (operation, GDATA_ENTRY (doc), &inserted_entry, NULL);
- g_assert (gdata_batch_operation_run (operation, NULL, &error) == TRUE);
+ g_assert (gdata_test_batch_operation_run (operation, NULL, &error) == TRUE);
g_assert_no_error (error);
g_clear_error (&error);
@@ -1142,7 +1142,7 @@ test_batch (gconstpointer service)
NULL);
g_assert_cmpuint (op_id, !=, op_id2);
- g_assert (gdata_batch_operation_run (operation, NULL, &error) == TRUE);
+ g_assert (gdata_test_batch_operation_run (operation, NULL, &error) == TRUE);
g_assert_no_error (error);
g_clear_error (&error);
@@ -1155,7 +1155,7 @@ test_batch (gconstpointer service)
gdata_test_batch_operation_query (operation, gdata_entry_get_id (inserted_entry), GDATA_TYPE_DOCUMENTS_TEXT, inserted_entry,
&inserted_entry_updated, NULL);
- g_assert (gdata_batch_operation_run (operation, NULL, &error) == TRUE);
+ g_assert (gdata_test_batch_operation_run (operation, NULL, &error) == TRUE);
g_assert_no_error (error);
g_clear_error (&error);
@@ -1169,7 +1169,7 @@ test_batch (gconstpointer service)
gdata_test_batch_operation_query (operation, gdata_entry_get_id (inserted_entry2), GDATA_TYPE_DOCUMENTS_TEXT, inserted_entry2,
&inserted_entry2_updated, NULL);
- g_assert (gdata_batch_operation_run (operation, NULL, &error) == TRUE);
+ g_assert (gdata_test_batch_operation_run (operation, NULL, &error) == TRUE);
g_assert_no_error (error);
g_clear_error (&error);
@@ -1187,7 +1187,7 @@ test_batch (gconstpointer service)
g_assert_cmpuint (op_id, !=, op_id3);
g_assert_cmpuint (op_id2, !=, op_id3);
- g_assert (gdata_batch_operation_run (operation, NULL, &error) == TRUE);
+ g_assert (gdata_test_batch_operation_run (operation, NULL, &error) == TRUE);
g_assert_no_error (error);
g_assert_error (entry_error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR);
@@ -1203,7 +1203,7 @@ test_batch (gconstpointer service)
* to test error handling */
operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://docs.google.com/feeds/documents/private/full/batch");
gdata_test_batch_operation_update (operation, inserted_entry2, NULL, &entry_error);
- g_assert (gdata_batch_operation_run (operation, NULL, &error) == TRUE);
+ g_assert (gdata_test_batch_operation_run (operation, NULL, &error) == TRUE);
g_assert_no_error (error);
g_assert_error (entry_error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_CONFLICT);
@@ -1216,7 +1216,7 @@ test_batch (gconstpointer service)
/* Run a final batch operation to delete the second entry */
operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://docs.google.com/feeds/documents/private/full/batch");
gdata_test_batch_operation_deletion (operation, inserted_entry3, NULL);
- g_assert (gdata_batch_operation_run (operation, NULL, &error) == TRUE);
+ g_assert (gdata_test_batch_operation_run (operation, NULL, &error) == TRUE);
g_assert_no_error (error);
g_clear_error (&error);
@@ -1255,7 +1255,10 @@ test_batch_async_cb (GDataBatchOperation *operation, GAsyncResult *async_result,
{
GError *error = NULL;
- g_assert (gdata_batch_operation_run_finish (operation, async_result, &error) == TRUE);
+ /* Clear all pending events (such as callbacks for the operations) */
+ while (g_main_context_iteration (NULL, FALSE) == TRUE);
+
+ g_assert (gdata_test_batch_operation_run_finish (operation, async_result, &error) == TRUE);
g_assert_no_error (error);
g_clear_error (&error);
@@ -1288,7 +1291,10 @@ test_batch_async_cancellation_cb (GDataBatchOperation *operation, GAsyncResult *
{
GError *error = NULL;
- g_assert (gdata_batch_operation_run_finish (operation, async_result, &error) == FALSE);
+ /* Clear all pending events (such as callbacks for the operations) */
+ while (g_main_context_iteration (NULL, FALSE) == TRUE);
+
+ g_assert (gdata_test_batch_operation_run_finish (operation, async_result, &error) == FALSE);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
g_clear_error (&error);
@@ -1302,11 +1308,12 @@ test_batch_async_cancellation (BatchAsyncData *data, gconstpointer service)
guint op_id;
GMainLoop *main_loop;
GCancellable *cancellable;
+ GError *error = NULL;
/* Run an async query operation on the document */
operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), "https://docs.google.com/feeds/documents/private/full/batch");
op_id = gdata_test_batch_operation_query (operation, gdata_entry_get_id (GDATA_ENTRY (data->new_doc)), GDATA_TYPE_DOCUMENTS_TEXT,
- GDATA_ENTRY (data->new_doc), NULL, NULL);
+ GDATA_ENTRY (data->new_doc), NULL, &error);
main_loop = g_main_loop_new (NULL, TRUE);
cancellable = g_cancellable_new ();
@@ -1315,6 +1322,10 @@ test_batch_async_cancellation (BatchAsyncData *data, gconstpointer service)
g_cancellable_cancel (cancellable); /* this should cancel the operation before it even starts, as we haven't run the main loop yet */
g_main_loop_run (main_loop);
+
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_clear_error (&error);
+
g_main_loop_unref (main_loop);
g_object_unref (cancellable);
g_object_unref (operation);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]