|
|
383d26 |
From 70365466a45a381ebb54e49cb03579b5fd6c76ef Mon Sep 17 00:00:00 2001
|
|
|
383d26 |
From: Kevin Wolf <kwolf@redhat.com>
|
|
|
383d26 |
Date: Fri, 14 Sep 2018 10:55:05 +0200
|
|
|
383d26 |
Subject: [PATCH 14/49] block: Really pause block jobs on drain
|
|
|
383d26 |
|
|
|
383d26 |
RH-Author: Kevin Wolf <kwolf@redhat.com>
|
|
|
383d26 |
Message-id: <20180914105540.18077-8-kwolf@redhat.com>
|
|
|
383d26 |
Patchwork-id: 82160
|
|
|
383d26 |
O-Subject: [RHV-7.6 qemu-kvm-rhev PATCH 07/42] block: Really pause block jobs on drain
|
|
|
383d26 |
Bugzilla: 1601212
|
|
|
383d26 |
RH-Acked-by: John Snow <jsnow@redhat.com>
|
|
|
383d26 |
RH-Acked-by: Max Reitz <mreitz@redhat.com>
|
|
|
383d26 |
RH-Acked-by: Fam Zheng <famz@redhat.com>
|
|
|
383d26 |
|
|
|
383d26 |
We already requested that block jobs be paused in .bdrv_drained_begin,
|
|
|
383d26 |
but no guarantee was made that the job was actually inactive at the
|
|
|
383d26 |
point where bdrv_drained_begin() returned.
|
|
|
383d26 |
|
|
|
383d26 |
This introduces a new callback BdrvChildRole.bdrv_drained_poll() and
|
|
|
383d26 |
uses it to make bdrv_drain_poll() consider block jobs using the node to
|
|
|
383d26 |
be drained.
|
|
|
383d26 |
|
|
|
383d26 |
For the test case to work as expected, we have to switch from
|
|
|
383d26 |
block_job_sleep_ns() to qemu_co_sleep_ns() so that the test job is even
|
|
|
383d26 |
considered active and must be waited for when draining the node.
|
|
|
383d26 |
|
|
|
383d26 |
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
|
|
|
383d26 |
(cherry picked from commit 89bd030533e3592ca0a995450dcfc5d53e459e20)
|
|
|
383d26 |
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
|
|
|
383d26 |
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
|
|
|
383d26 |
---
|
|
|
383d26 |
block.c | 9 +++++++++
|
|
|
383d26 |
block/io.c | 40 ++++++++++++++++++++++++++++++++++------
|
|
|
383d26 |
block/mirror.c | 8 ++++++++
|
|
|
383d26 |
blockjob.c | 23 +++++++++++++++++++++++
|
|
|
383d26 |
include/block/block.h | 8 ++++++++
|
|
|
383d26 |
include/block/block_int.h | 7 +++++++
|
|
|
383d26 |
include/block/blockjob_int.h | 8 ++++++++
|
|
|
383d26 |
tests/test-bdrv-drain.c | 18 ++++++++++--------
|
|
|
383d26 |
8 files changed, 107 insertions(+), 14 deletions(-)
|
|
|
383d26 |
|
|
|
383d26 |
diff --git a/block.c b/block.c
|
|
|
383d26 |
index 10a1ece..0d9698a 100644
|
|
|
383d26 |
--- a/block.c
|
|
|
383d26 |
+++ b/block.c
|
|
|
383d26 |
@@ -821,6 +821,12 @@ static void bdrv_child_cb_drained_begin(BdrvChild *child)
|
|
|
383d26 |
bdrv_drained_begin(bs);
|
|
|
383d26 |
}
|
|
|
383d26 |
|
|
|
383d26 |
+static bool bdrv_child_cb_drained_poll(BdrvChild *child)
|
|
|
383d26 |
+{
|
|
|
383d26 |
+ BlockDriverState *bs = child->opaque;
|
|
|
383d26 |
+ return bdrv_drain_poll(bs, NULL);
|
|
|
383d26 |
+}
|
|
|
383d26 |
+
|
|
|
383d26 |
static void bdrv_child_cb_drained_end(BdrvChild *child)
|
|
|
383d26 |
{
|
|
|
383d26 |
BlockDriverState *bs = child->opaque;
|
|
|
383d26 |
@@ -905,6 +911,7 @@ const BdrvChildRole child_file = {
|
|
|
383d26 |
.get_parent_desc = bdrv_child_get_parent_desc,
|
|
|
383d26 |
.inherit_options = bdrv_inherited_options,
|
|
|
383d26 |
.drained_begin = bdrv_child_cb_drained_begin,
|
|
|
383d26 |
+ .drained_poll = bdrv_child_cb_drained_poll,
|
|
|
383d26 |
.drained_end = bdrv_child_cb_drained_end,
|
|
|
383d26 |
.attach = bdrv_child_cb_attach,
|
|
|
383d26 |
.detach = bdrv_child_cb_detach,
|
|
|
383d26 |
@@ -929,6 +936,7 @@ const BdrvChildRole child_format = {
|
|
|
383d26 |
.get_parent_desc = bdrv_child_get_parent_desc,
|
|
|
383d26 |
.inherit_options = bdrv_inherited_fmt_options,
|
|
|
383d26 |
.drained_begin = bdrv_child_cb_drained_begin,
|
|
|
383d26 |
+ .drained_poll = bdrv_child_cb_drained_poll,
|
|
|
383d26 |
.drained_end = bdrv_child_cb_drained_end,
|
|
|
383d26 |
.attach = bdrv_child_cb_attach,
|
|
|
383d26 |
.detach = bdrv_child_cb_detach,
|
|
|
383d26 |
@@ -1048,6 +1056,7 @@ const BdrvChildRole child_backing = {
|
|
|
383d26 |
.detach = bdrv_backing_detach,
|
|
|
383d26 |
.inherit_options = bdrv_backing_options,
|
|
|
383d26 |
.drained_begin = bdrv_child_cb_drained_begin,
|
|
|
383d26 |
+ .drained_poll = bdrv_child_cb_drained_poll,
|
|
|
383d26 |
.drained_end = bdrv_child_cb_drained_end,
|
|
|
383d26 |
.inactivate = bdrv_child_cb_inactivate,
|
|
|
383d26 |
.update_filename = bdrv_backing_update_filename,
|
|
|
383d26 |
diff --git a/block/io.c b/block/io.c
|
|
|
383d26 |
index 4d332c3..e260394 100644
|
|
|
383d26 |
--- a/block/io.c
|
|
|
383d26 |
+++ b/block/io.c
|
|
|
383d26 |
@@ -69,6 +69,23 @@ void bdrv_parent_drained_end(BlockDriverState *bs, BdrvChild *ignore)
|
|
|
383d26 |
}
|
|
|
383d26 |
}
|
|
|
383d26 |
|
|
|
383d26 |
+static bool bdrv_parent_drained_poll(BlockDriverState *bs, BdrvChild *ignore)
|
|
|
383d26 |
+{
|
|
|
383d26 |
+ BdrvChild *c, *next;
|
|
|
383d26 |
+ bool busy = false;
|
|
|
383d26 |
+
|
|
|
383d26 |
+ QLIST_FOREACH_SAFE(c, &bs->parents, next_parent, next) {
|
|
|
383d26 |
+ if (c == ignore) {
|
|
|
383d26 |
+ continue;
|
|
|
383d26 |
+ }
|
|
|
383d26 |
+ if (c->role->drained_poll) {
|
|
|
383d26 |
+ busy |= c->role->drained_poll(c);
|
|
|
383d26 |
+ }
|
|
|
383d26 |
+ }
|
|
|
383d26 |
+
|
|
|
383d26 |
+ return busy;
|
|
|
383d26 |
+}
|
|
|
383d26 |
+
|
|
|
383d26 |
static void bdrv_merge_limits(BlockLimits *dst, const BlockLimits *src)
|
|
|
383d26 |
{
|
|
|
383d26 |
dst->opt_transfer = MAX(dst->opt_transfer, src->opt_transfer);
|
|
|
383d26 |
@@ -182,21 +199,32 @@ static void bdrv_drain_invoke(BlockDriverState *bs, bool begin)
|
|
|
383d26 |
}
|
|
|
383d26 |
|
|
|
383d26 |
/* Returns true if BDRV_POLL_WHILE() should go into a blocking aio_poll() */
|
|
|
383d26 |
-static bool bdrv_drain_poll(BlockDriverState *bs)
|
|
|
383d26 |
+bool bdrv_drain_poll(BlockDriverState *bs, BdrvChild *ignore_parent)
|
|
|
383d26 |
+{
|
|
|
383d26 |
+ if (bdrv_parent_drained_poll(bs, ignore_parent)) {
|
|
|
383d26 |
+ return true;
|
|
|
383d26 |
+ }
|
|
|
383d26 |
+
|
|
|
383d26 |
+ return atomic_read(&bs->in_flight);
|
|
|
383d26 |
+}
|
|
|
383d26 |
+
|
|
|
383d26 |
+static bool bdrv_drain_poll_top_level(BlockDriverState *bs,
|
|
|
383d26 |
+ BdrvChild *ignore_parent)
|
|
|
383d26 |
{
|
|
|
383d26 |
/* Execute pending BHs first and check everything else only after the BHs
|
|
|
383d26 |
* have executed. */
|
|
|
383d26 |
while (aio_poll(bs->aio_context, false));
|
|
|
383d26 |
- return atomic_read(&bs->in_flight);
|
|
|
383d26 |
+
|
|
|
383d26 |
+ return bdrv_drain_poll(bs, ignore_parent);
|
|
|
383d26 |
}
|
|
|
383d26 |
|
|
|
383d26 |
-static bool bdrv_drain_recurse(BlockDriverState *bs)
|
|
|
383d26 |
+static bool bdrv_drain_recurse(BlockDriverState *bs, BdrvChild *parent)
|
|
|
383d26 |
{
|
|
|
383d26 |
BdrvChild *child, *tmp;
|
|
|
383d26 |
bool waited;
|
|
|
383d26 |
|
|
|
383d26 |
/* Wait for drained requests to finish */
|
|
|
383d26 |
- waited = BDRV_POLL_WHILE(bs, bdrv_drain_poll(bs));
|
|
|
383d26 |
+ waited = BDRV_POLL_WHILE(bs, bdrv_drain_poll_top_level(bs, parent));
|
|
|
383d26 |
|
|
|
383d26 |
QLIST_FOREACH_SAFE(child, &bs->children, next, tmp) {
|
|
|
383d26 |
BlockDriverState *bs = child->bs;
|
|
|
383d26 |
@@ -213,7 +241,7 @@ static bool bdrv_drain_recurse(BlockDriverState *bs)
|
|
|
383d26 |
*/
|
|
|
383d26 |
bdrv_ref(bs);
|
|
|
383d26 |
}
|
|
|
383d26 |
- waited |= bdrv_drain_recurse(bs);
|
|
|
383d26 |
+ waited |= bdrv_drain_recurse(bs, child);
|
|
|
383d26 |
if (in_main_loop) {
|
|
|
383d26 |
bdrv_unref(bs);
|
|
|
383d26 |
}
|
|
|
383d26 |
@@ -289,7 +317,7 @@ void bdrv_do_drained_begin(BlockDriverState *bs, bool recursive,
|
|
|
383d26 |
|
|
|
383d26 |
bdrv_parent_drained_begin(bs, parent);
|
|
|
383d26 |
bdrv_drain_invoke(bs, true);
|
|
|
383d26 |
- bdrv_drain_recurse(bs);
|
|
|
383d26 |
+ bdrv_drain_recurse(bs, parent);
|
|
|
383d26 |
|
|
|
383d26 |
if (recursive) {
|
|
|
383d26 |
bs->recursive_quiesce_counter++;
|
|
|
383d26 |
diff --git a/block/mirror.c b/block/mirror.c
|
|
|
383d26 |
index 313e6e9..4b27f71 100644
|
|
|
383d26 |
--- a/block/mirror.c
|
|
|
383d26 |
+++ b/block/mirror.c
|
|
|
383d26 |
@@ -976,6 +976,12 @@ static void mirror_pause(Job *job)
|
|
|
383d26 |
mirror_wait_for_all_io(s);
|
|
|
383d26 |
}
|
|
|
383d26 |
|
|
|
383d26 |
+static bool mirror_drained_poll(BlockJob *job)
|
|
|
383d26 |
+{
|
|
|
383d26 |
+ MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
|
|
|
383d26 |
+ return !!s->in_flight;
|
|
|
383d26 |
+}
|
|
|
383d26 |
+
|
|
|
383d26 |
static void mirror_attached_aio_context(BlockJob *job, AioContext *new_context)
|
|
|
383d26 |
{
|
|
|
383d26 |
MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
|
|
|
383d26 |
@@ -1011,6 +1017,7 @@ static const BlockJobDriver mirror_job_driver = {
|
|
|
383d26 |
.pause = mirror_pause,
|
|
|
383d26 |
.complete = mirror_complete,
|
|
|
383d26 |
},
|
|
|
383d26 |
+ .drained_poll = mirror_drained_poll,
|
|
|
383d26 |
.attached_aio_context = mirror_attached_aio_context,
|
|
|
383d26 |
.drain = mirror_drain,
|
|
|
383d26 |
};
|
|
|
383d26 |
@@ -1028,6 +1035,7 @@ static const BlockJobDriver commit_active_job_driver = {
|
|
|
383d26 |
.pause = mirror_pause,
|
|
|
383d26 |
.complete = mirror_complete,
|
|
|
383d26 |
},
|
|
|
383d26 |
+ .drained_poll = mirror_drained_poll,
|
|
|
383d26 |
.attached_aio_context = mirror_attached_aio_context,
|
|
|
383d26 |
.drain = mirror_drain,
|
|
|
383d26 |
};
|
|
|
383d26 |
diff --git a/blockjob.c b/blockjob.c
|
|
|
383d26 |
index 0306533..be5903a 100644
|
|
|
383d26 |
--- a/blockjob.c
|
|
|
383d26 |
+++ b/blockjob.c
|
|
|
383d26 |
@@ -155,6 +155,28 @@ static void child_job_drained_begin(BdrvChild *c)
|
|
|
383d26 |
job_pause(&job->job);
|
|
|
383d26 |
}
|
|
|
383d26 |
|
|
|
383d26 |
+static bool child_job_drained_poll(BdrvChild *c)
|
|
|
383d26 |
+{
|
|
|
383d26 |
+ BlockJob *bjob = c->opaque;
|
|
|
383d26 |
+ Job *job = &bjob->job;
|
|
|
383d26 |
+ const BlockJobDriver *drv = block_job_driver(bjob);
|
|
|
383d26 |
+
|
|
|
383d26 |
+ /* An inactive or completed job doesn't have any pending requests. Jobs
|
|
|
383d26 |
+ * with !job->busy are either already paused or have a pause point after
|
|
|
383d26 |
+ * being reentered, so no job driver code will run before they pause. */
|
|
|
383d26 |
+ if (!job->busy || job_is_completed(job) || job->deferred_to_main_loop) {
|
|
|
383d26 |
+ return false;
|
|
|
383d26 |
+ }
|
|
|
383d26 |
+
|
|
|
383d26 |
+ /* Otherwise, assume that it isn't fully stopped yet, but allow the job to
|
|
|
383d26 |
+ * override this assumption. */
|
|
|
383d26 |
+ if (drv->drained_poll) {
|
|
|
383d26 |
+ return drv->drained_poll(bjob);
|
|
|
383d26 |
+ } else {
|
|
|
383d26 |
+ return true;
|
|
|
383d26 |
+ }
|
|
|
383d26 |
+}
|
|
|
383d26 |
+
|
|
|
383d26 |
static void child_job_drained_end(BdrvChild *c)
|
|
|
383d26 |
{
|
|
|
383d26 |
BlockJob *job = c->opaque;
|
|
|
383d26 |
@@ -164,6 +186,7 @@ static void child_job_drained_end(BdrvChild *c)
|
|
|
383d26 |
static const BdrvChildRole child_job = {
|
|
|
383d26 |
.get_parent_desc = child_job_get_parent_desc,
|
|
|
383d26 |
.drained_begin = child_job_drained_begin,
|
|
|
383d26 |
+ .drained_poll = child_job_drained_poll,
|
|
|
383d26 |
.drained_end = child_job_drained_end,
|
|
|
383d26 |
.stay_at_node = true,
|
|
|
383d26 |
};
|
|
|
383d26 |
diff --git a/include/block/block.h b/include/block/block.h
|
|
|
383d26 |
index 8f87eea..8c91d4c 100644
|
|
|
383d26 |
--- a/include/block/block.h
|
|
|
383d26 |
+++ b/include/block/block.h
|
|
|
383d26 |
@@ -596,6 +596,14 @@ void bdrv_parent_drained_begin(BlockDriverState *bs, BdrvChild *ignore);
|
|
|
383d26 |
void bdrv_parent_drained_end(BlockDriverState *bs, BdrvChild *ignore);
|
|
|
383d26 |
|
|
|
383d26 |
/**
|
|
|
383d26 |
+ * bdrv_drain_poll:
|
|
|
383d26 |
+ *
|
|
|
383d26 |
+ * Poll for pending requests in @bs and its parents (except for
|
|
|
383d26 |
+ * @ignore_parent). This is part of bdrv_drained_begin.
|
|
|
383d26 |
+ */
|
|
|
383d26 |
+bool bdrv_drain_poll(BlockDriverState *bs, BdrvChild *ignore_parent);
|
|
|
383d26 |
+
|
|
|
383d26 |
+/**
|
|
|
383d26 |
* bdrv_drained_begin:
|
|
|
383d26 |
*
|
|
|
383d26 |
* Begin a quiesced section for exclusive access to the BDS, by disabling
|
|
|
383d26 |
diff --git a/include/block/block_int.h b/include/block/block_int.h
|
|
|
383d26 |
index 341cbe8..beeacde 100644
|
|
|
383d26 |
--- a/include/block/block_int.h
|
|
|
383d26 |
+++ b/include/block/block_int.h
|
|
|
383d26 |
@@ -610,6 +610,13 @@ struct BdrvChildRole {
|
|
|
383d26 |
void (*drained_begin)(BdrvChild *child);
|
|
|
383d26 |
void (*drained_end)(BdrvChild *child);
|
|
|
383d26 |
|
|
|
383d26 |
+ /*
|
|
|
383d26 |
+ * Returns whether the parent has pending requests for the child. This
|
|
|
383d26 |
+ * callback is polled after .drained_begin() has been called until all
|
|
|
383d26 |
+ * activity on the child has stopped.
|
|
|
383d26 |
+ */
|
|
|
383d26 |
+ bool (*drained_poll)(BdrvChild *child);
|
|
|
383d26 |
+
|
|
|
383d26 |
/* Notifies the parent that the child has been activated/inactivated (e.g.
|
|
|
383d26 |
* when migration is completing) and it can start/stop requesting
|
|
|
383d26 |
* permissions and doing I/O on it. */
|
|
|
383d26 |
diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h
|
|
|
383d26 |
index 5cd50c6..e4a318d 100644
|
|
|
383d26 |
--- a/include/block/blockjob_int.h
|
|
|
383d26 |
+++ b/include/block/blockjob_int.h
|
|
|
383d26 |
@@ -39,6 +39,14 @@ struct BlockJobDriver {
|
|
|
383d26 |
JobDriver job_driver;
|
|
|
383d26 |
|
|
|
383d26 |
/*
|
|
|
383d26 |
+ * Returns whether the job has pending requests for the child or will
|
|
|
383d26 |
+ * submit new requests before the next pause point. This callback is polled
|
|
|
383d26 |
+ * in the context of draining a job node after requesting that the job be
|
|
|
383d26 |
+ * paused, until all activity on the child has stopped.
|
|
|
383d26 |
+ */
|
|
|
383d26 |
+ bool (*drained_poll)(BlockJob *job);
|
|
|
383d26 |
+
|
|
|
383d26 |
+ /*
|
|
|
383d26 |
* If the callback is not NULL, it will be invoked before the job is
|
|
|
383d26 |
* resumed in a new AioContext. This is the place to move any resources
|
|
|
383d26 |
* besides job->blk to the new AioContext.
|
|
|
383d26 |
diff --git a/tests/test-bdrv-drain.c b/tests/test-bdrv-drain.c
|
|
|
383d26 |
index f5d85c9..49786ea 100644
|
|
|
383d26 |
--- a/tests/test-bdrv-drain.c
|
|
|
383d26 |
+++ b/tests/test-bdrv-drain.c
|
|
|
383d26 |
@@ -681,7 +681,11 @@ static int coroutine_fn test_job_run(Job *job, Error **errp)
|
|
|
383d26 |
|
|
|
383d26 |
job_transition_to_ready(&s->common.job);
|
|
|
383d26 |
while (!s->should_complete) {
|
|
|
383d26 |
- job_sleep_ns(&s->common.job, 100000);
|
|
|
383d26 |
+ /* Avoid block_job_sleep_ns() because it marks the job as !busy. We
|
|
|
383d26 |
+ * want to emulate some actual activity (probably some I/O) here so
|
|
|
383d26 |
+ * that drain has to wait for this acitivity to stop. */
|
|
|
383d26 |
+ qemu_co_sleep_ns(QEMU_CLOCK_REALTIME, 100000);
|
|
|
383d26 |
+ job_pause_point(&s->common.job);
|
|
|
383d26 |
}
|
|
|
383d26 |
|
|
|
383d26 |
return 0;
|
|
|
383d26 |
@@ -728,7 +732,7 @@ static void test_blockjob_common(enum drain_type drain_type)
|
|
|
383d26 |
|
|
|
383d26 |
g_assert_cmpint(job->job.pause_count, ==, 0);
|
|
|
383d26 |
g_assert_false(job->job.paused);
|
|
|
383d26 |
- g_assert_false(job->job.busy); /* We're in job_sleep_ns() */
|
|
|
383d26 |
+ g_assert_true(job->job.busy); /* We're in job_sleep_ns() */
|
|
|
383d26 |
|
|
|
383d26 |
do_drain_begin(drain_type, src);
|
|
|
383d26 |
|
|
|
383d26 |
@@ -738,15 +742,14 @@ static void test_blockjob_common(enum drain_type drain_type)
|
|
|
383d26 |
} else {
|
|
|
383d26 |
g_assert_cmpint(job->job.pause_count, ==, 1);
|
|
|
383d26 |
}
|
|
|
383d26 |
- /* XXX We don't wait until the job is actually paused. Is this okay? */
|
|
|
383d26 |
- /* g_assert_true(job->job.paused); */
|
|
|
383d26 |
+ g_assert_true(job->job.paused);
|
|
|
383d26 |
g_assert_false(job->job.busy); /* The job is paused */
|
|
|
383d26 |
|
|
|
383d26 |
do_drain_end(drain_type, src);
|
|
|
383d26 |
|
|
|
383d26 |
g_assert_cmpint(job->job.pause_count, ==, 0);
|
|
|
383d26 |
g_assert_false(job->job.paused);
|
|
|
383d26 |
- g_assert_false(job->job.busy); /* We're in job_sleep_ns() */
|
|
|
383d26 |
+ g_assert_true(job->job.busy); /* We're in qemu_co_sleep_ns() */
|
|
|
383d26 |
|
|
|
383d26 |
do_drain_begin(drain_type, target);
|
|
|
383d26 |
|
|
|
383d26 |
@@ -756,15 +759,14 @@ static void test_blockjob_common(enum drain_type drain_type)
|
|
|
383d26 |
} else {
|
|
|
383d26 |
g_assert_cmpint(job->job.pause_count, ==, 1);
|
|
|
383d26 |
}
|
|
|
383d26 |
- /* XXX We don't wait until the job is actually paused. Is this okay? */
|
|
|
383d26 |
- /* g_assert_true(job->job.paused); */
|
|
|
383d26 |
+ g_assert_true(job->job.paused);
|
|
|
383d26 |
g_assert_false(job->job.busy); /* The job is paused */
|
|
|
383d26 |
|
|
|
383d26 |
do_drain_end(drain_type, target);
|
|
|
383d26 |
|
|
|
383d26 |
g_assert_cmpint(job->job.pause_count, ==, 0);
|
|
|
383d26 |
g_assert_false(job->job.paused);
|
|
|
383d26 |
- g_assert_false(job->job.busy); /* We're in job_sleep_ns() */
|
|
|
383d26 |
+ g_assert_true(job->job.busy); /* We're in job_sleep_ns() */
|
|
|
383d26 |
|
|
|
383d26 |
ret = job_complete_sync(&job->job, &error_abort);
|
|
|
383d26 |
g_assert_cmpint(ret, ==, 0);
|
|
|
383d26 |
--
|
|
|
383d26 |
1.8.3.1
|
|
|
383d26 |
|