Generic background jobs
Subsystems (2)
v2 cover Generic background jobs
Before we can make x-blockdev-create a background job, we need to generalise the job infrastructure so that it can be used without any associated block node.
This series extracts a Job object from the block job infrastructure, which should contain everything related to jobs that doesn't require the block layer to be involved.
Job creation with a centralised job-create command (if we even want this) as well as the actual conversion of x-blockdev-create to Job is left for another series.
v2:
- Already applied a small prefix of the series and rebased, so a few patches are missing compared to v1
- Fixed up forgotten renames of identifiers in comments (several patches all over the series) [Max]
- Patch 1 and 2 (new):
QAPI documentation improvements for existing block-job-* commands
- Patch 5 (was patch 10; 'job: Add JobDriver.job_type'):
Added 'const' to job_type()/job_type_str() parameter [Max]
- Patch 7 (was patch 12; 'job: Maintain a list of all jobs'):
block_job_next(NULL) doesn't rely on BlockJob.job being the first field any more [Eric]
- Patch 8 (was patch 13; 'job: Move state transitions to Job'):
Renamed JobVerb paramater from 'bv' to 'verb' [Eric]
- Patch 12 (was patch 17; 'job: Move defer_to_main_loop to Job'):
Added commit message [Max, Eric, John]
Removed stale comment [Max]
- Patch 26 (was patch 31; 'job: Move transactions to Job'):
job_event_pending() can keep returning void [Max]
- Patch 27 (was patch 32; 'job: Move completion and cancellation to Job') and patch 28 (new):
Added TODO in patch 27, and resolved it in patch 28 by moving the job_cancel_all() call out of bdrv_close_all() to the callers [Max]
- Patch 30 (was patch 34; 'job: Add job_dismiss()'):
Fixed uninitialised use of 'job' [Max]
- Patch 33 (was patch 37; 'job: Move progress fields to Job'):
Fixed documentation for job_progress_update/set_remaining() [Eric]
- Patch 34 (new):
Separate patch for adding qapi/job.json that just moves the three Job-related enums JobType, JobStatus and JobVerb (without any commands or events) [Max]
- Patch 35 (was patch 38; 'job: Add JOB_STATUS_CHANGE QMP event'):
Checking JOB_STATUS_CHANGE events more consistently in tests [Max] Updated qemu-iotests 094 reference output [Max]
- Patch 36 (was patch 39; 'job: Add lifecycle QMP commands'):
Removed 'force' option from 'job-cancel' and make force=true the only behaviour. This makes mirror behave the same as other jobs in the context of 'job-cancel'. [Max]
Fixed 'job-pause' documentation [Max]
- Patch 37 (was patch 40; 'job: Add query-jobs QMP command'):
QAPI documentation improvements [Max]
- Patch 38 (new):
Remove redundant BlockJob.driver [Max]
- Patch 40 (was patch 42; 'qemu-iotests: Test job-* with block jobs'):
Filtered out progress in 'query-jobs' immediately after the job is started because on tmpfs the first request will already have completed. [Max]
Kevin Wolf (40):
blockjob: Update block-job-pause/resume documentation
blockjob: Improve BlockJobInfo.offset/len documentation
job: Create Job, JobDriver and job_create()
job: Rename BlockJobType into JobType
job: Add JobDriver.job_type
job: Add job_delete()
job: Maintain a list of all jobs
job: Move state transitions to Job
job: Add reference counting
job: Move cancelled to Job
job: Add Job.aio_context
job: Move defer_to_main_loop to Job
job: Move coroutine and related code to Job
job: Add job_sleep_ns()
job: Move pause/resume functions to Job
job: Replace BlockJob.completed with job_is_completed()
job: Move BlockJobCreateFlags to Job
blockjob: Split block_job_event_pending()
job: Add job_event_*()
job: Move single job finalisation to Job
job: Convert block_job_cancel_async() to Job
job: Add job_drain()
job: Move .complete callback to Job
job: Move job_finish_sync() to Job
job: Switch transactions to JobTxn
job: Move transactions to Job
job: Move completion and cancellation to Job
block: Cancel job in bdrv_close_all() callers
job: Add job_yield()
job: Add job_dismiss()
job: Add job_is_ready()
job: Add job_transition_to_ready()
job: Move progress fields to Job
job: Introduce qapi/job.json
job: Add JOB_STATUS_CHANGE QMP event
job: Add lifecycle QMP commands
job: Add query-jobs QMP command
blockjob: Remove BlockJob.driver
iotests: Move qmp_to_opts() to VM
qemu-iotests: Test job-* with block jobs
qapi/block-core.json | 116 +---- qapi/job.json | 252 ++++++++++ qapi/qapi-schema.json | 1 + include/block/block_int.h | 2 +- include/block/blockjob.h | 324 +----------- include/block/blockjob_int.h | 176 +------ include/qemu/job.h | 562 +++++++++++++++++++++ block.c | 2 +- block/backup.c | 59 ++- block/commit.c | 44 +- block/mirror.c | 113 +++-- block/replication.c | 10 +- block/stream.c | 39 +- blockdev.c | 68 +-- blockjob.c | 1094 ++++++----------------------------------- job-qmp.c | 188 +++++++ job.c | 1000 +++++++++++++++++++++++++++++++++++++ qemu-img.c | 22 +- qemu-nbd.c | 8 +- tests/test-bdrv-drain.c | 63 +-- tests/test-blockjob-txn.c | 74 +-- tests/test-blockjob.c | 141 +++--- vl.c | 1 + MAINTAINERS | 3 + Makefile | 9 + Makefile.objs | 7 +- block/trace-events | 5 - tests/qemu-iotests/030 | 17 +- tests/qemu-iotests/040 | 2 + tests/qemu-iotests/041 | 23 +- tests/qemu-iotests/094.out | 7 + tests/qemu-iotests/095 | 2 +- tests/qemu-iotests/095.out | 6 + tests/qemu-iotests/109 | 2 +- tests/qemu-iotests/109.out | 178 ++++++- tests/qemu-iotests/124 | 8 + tests/qemu-iotests/127.out | 7 + tests/qemu-iotests/141 | 13 +- tests/qemu-iotests/141.out | 29 ++ tests/qemu-iotests/144 | 2 +- tests/qemu-iotests/144.out | 7 + tests/qemu-iotests/155 | 2 +- tests/qemu-iotests/156 | 2 +- tests/qemu-iotests/156.out | 7 + tests/qemu-iotests/185 | 12 +- tests/qemu-iotests/185.out | 10 + tests/qemu-iotests/191 | 4 +- tests/qemu-iotests/191.out | 132 +++++ tests/qemu-iotests/219 | 209 ++++++++ tests/qemu-iotests/219.out | 327 ++++++++++++ tests/qemu-iotests/group | 1 + tests/qemu-iotests/iotests.py | 50 +- trace-events | 14 + 53 files changed, 3578 insertions(+), 1878 deletions(-) create mode 100644 qapi/job.json create mode 100644 include/qemu/job.h create mode 100644 job-qmp.c create mode 100644 job.c create mode 100755 tests/qemu-iotests/219 create mode 100644 tests/qemu-iotests/219.out
Signature · 1 line
2.13.6
PATCH v2 1/40 blockjob: Update block-job-pause/resume documentation
Commit 0ec4dfb8d changed block-job_pause/resume so that they return an error if they don't do anything because the job is already paused/running. It forgot to update the documentation, so do that now.
--- qapi/block-core.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)
qapi/block-core.json +2 -3
@@ -2338,8 +2338,7 @@ # # This command returns immediately after marking the active background block # operation for pausing. It is an error to call this command if no -# operation is in progress. Pausing an already paused job has no cumulative -# effect; a single block-job-resume command will resume the job. +# operation is in progress or if the job is already paused. # # The operation will pause as soon as possible. No event is emitted when # the operation is actually paused. Cancelling a paused job automatically
@@ -2363,7 +2362,7 @@ # # This command returns immediately after resuming a paused background block # operation. It is an error to call this command if no operation is in -# progress. Resuming an already running job is not an error. +# progress or if the job is not paused. # # This command also clears the error status of the job. #
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH v2 01/40] blockjob: Update block-job-pause/resume documentation
Quoted 9 lines · Kevin Wolf
Commit
0ec4dfb8dchanged block-job_pause/resume so that they return an error if they don't do anything because the job is already paused/running. It forgot to update the documentation, so do that now.Signed-off-by Kevin Wolf <[email protected]>--- qapi/block-core.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)
Signature · 3 lines
Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
Re: [Qemu-devel] [PATCH v2 01/40] blockjob: Update block-job-pause/resume documentation
In reply to Kevin WolfQuoted 5 lines · Kevin Wolf
Commit
0ec4dfb8dchanged block-job_pause/resume so that they return an error if they don't do anything because the job is already paused/running. It forgot to update the documentation, so do that now.Signed-off-by Kevin Wolf <[email protected]>
D:
PATCH v2 2/40 blockjob: Improve BlockJobInfo.offset/len documentation
In reply to Kevin WolfClarify that len is just an estimation of the end value of offset, and that offset increases monotonically while len can change arbitrarily.
--- qapi/block-core.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)
qapi/block-core.json +6 -3
@@ -1148,7 +1148,12 @@ # @device: The job identifier. Originally the device name but other # values are allowed since QEMU 2.7 # -# @len: the maximum progress value +# @len: Estimated @offset value at the completion of the job. This value can +# arbitrarily change while the job is running, in both directions. +# +# @offset: Progress made until now. The unit is arbitrary and the value can +# only meaningfully be used for the ratio of @offset to @len. The +# value is monotonically increasing. # # @busy: false if the job is known to be in a quiescent state, with # no pending I/O. Since 1.3.
@@ -1156,8 +1161,6 @@ # @paused: whether the job is paused or, if @busy is true, will # pause itself as soon as possible. Since 1.3. # -# @offset: the current progress value -# # @speed: the rate limit, bytes per second # # @io-status: the status of the job (since 1.3)
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH v2 02/40] blockjob: Improve BlockJobInfo.offset/len documentation
Quoted 2 lines · Kevin Wolf
Clarify that len is just an estimation of the end value of offset, and that offset increases monotonically while len can change arbitrarily.
That's tighter than what libvirt promises (and in fact, there are cases where libvirt synthesizes an offset/len of 1/1 to indicate completion when the information is no longer available from qemu, which means the offset has gone backwards to libvirt clients).
But it matches the qemu implementation, and I'm okay if we want to stick to those semantics of monotonically increasing offset (the more important semantics of being an estimate of time remaining is possible whether or not you have a guarantee of a monotonic increase on one of the two numbers).
Quoted 11 lines
Signed-off-by Kevin Wolf <[email protected]>--- qapi/block-core.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)qapi/block-core.json +0
-0
qapi/block-core.json line 1156
Quoted 10 lines
qapi/block-core.json +6
-1@@ -1148,7 +1148,12 @@ # @device: The job identifier. Originally the device name but other # values are allowed since QEMU 2.7 # -# @len: the maximum progress value +# @len: Estimated @offset value at the completion of the job. This value can +# arbitrarily change while the job is running, in both directions. +# +# @offset: Progress made until now. The unit is arbitrary and the value can +# only meaningfully be used for the ratio of @offset to @len. The +# value is monotonically increasing.
So I'm okay whether or not you drop that last sentence.
You're also rearranging the docs to occur in the order that the struct declares things (good change, and trivial; not sure if you want to call it out in the commit message).
Signature · 3 lines
Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
Re: [Qemu-devel] [PATCH v2 02/40] blockjob: Improve BlockJobInfo.offset/len documentation
In reply to Kevin WolfQuoted 28 lines · Kevin Wolf
Clarify that len is just an estimation of the end value of offset, and that offset increases monotonically while len can change arbitrarily.
Signed-off-by Kevin Wolf <[email protected]>--- qapi/block-core.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)qapi/block-core.json +6
-1@@ -1148,7 +1148,12 @@ # @device: The job identifier. Originally the device name but other # values are allowed since QEMU 2.7 # -# @len: the maximum progress value +# @len: Estimated @offset value at the completion of the job. This value can +# arbitrarily change while the job is running, in both directions. +# +# @offset: Progress made until now. The unit is arbitrary and the value can +# only meaningfully be used for the ratio of @offset to @len. The +# value is monotonically increasing. # # @busy: false if the job is known to be in a quiescent state, with # no pending I/O. Since 1.3.
qapi/block-core.json line 1166
Quoted 8 lines
qapi/block-core.json +0
-2@@ -1156,8 +1161,6 @@ # @paused: whether the job is paused or, if @busy is true, will # pause itself as soon as possible. Since 1.3. # -# @offset: the current progress value -# # @speed: the rate limit, bytes per second # # @io-status: the status of the job (since 1.3)
It matches current actual behavior, so it's probably a good update. It feels like a change in behavior, but it's rather just codifying the existing reality.
OK.
PATCH v2 3/40 job: Create Job, JobDriver and job_create()
In reply to Kevin WolfThis is the first step towards creating an infrastructure for generic background jobs that aren't tied to a block device. For now, Job only stores its ID and JobDriver, the rest stays in BlockJob.
The following patches will move over more parts of BlockJob to Job if they are meaningful outside the context of a block job.
BlockJob.driver is now redundant, but this patch leaves it around to avoid unnecessary churn. The next patches will get rid of almost all of its uses anyway so that it can be removed later with much less churn.
--- include/block/blockjob.h | 9 +++---- include/block/blockjob_int.h | 4 +-- include/qemu/job.h | 60 ++++++++++++++++++++++++++++++++++++++++++++ block/backup.c | 4 ++- block/commit.c | 4 ++- block/mirror.c | 10 +++++--- block/stream.c | 4 ++- blockjob.c | 46 ++++++++++++++++----------------- job.c | 48 +++++++++++++++++++++++++++++++++++ tests/test-bdrv-drain.c | 4 ++- tests/test-blockjob-txn.c | 4 ++- tests/test-blockjob.c | 12 ++++++--- MAINTAINERS | 2 ++ Makefile.objs | 2 +- 14 files changed, 169 insertions(+), 44 deletions(-) create mode 100644 include/qemu/job.h create mode 100644 job.c
include/block/blockjob.h +4 -5
@@ -26,6 +26,7 @@#ifndef BLOCKJOB_H #define BLOCKJOB_H +#include "qemu/job.h" #include "block/block.h" #include "qemu/ratelimit.h"
@@ -40,6 +41,9 @@ typedef struct BlockJobTxn BlockJobTxn;* Long-running operation on a BlockDriverState. */ typedef struct BlockJob { + /** Data belonging to the generic Job infrastructure */ + Job job; + /** The job type, including the job vtable. */ const BlockJobDriver *driver;
@@ -47,11 +51,6 @@ typedef struct BlockJob {BlockBackend *blk;/** -* The ID of the block job. May be NULL for internal jobs. -*/ -char *id; - -/** * The coroutine that executes the job. If not NULL, it is * reentered when busy is false and the job is cancelled. */
include/block/blockjob_int.h +2 -2
@@ -35,8 +35,8 @@* A class type for block job driver. */ struct BlockJobDriver { -/** Derived BlockJob struct size */ -size_t instance_size; +/** Generic JobDriver callbacks and settings */ +JobDriver job_driver;/** String describing the operation, part of query-block-jobs QMP API */ BlockJobType job_type;
include/qemu/job.h new file +60 -0
60 lines not on this page · raw message
block/backup.c +3 -1
@@ -523,7 +523,9 @@ static void coroutine_fn backup_run(void *opaque) }static const BlockJobDriver backup_job_driver = { - .instance_size =sizeof (BackupBlockJob), + .job_driver = { + .instance_size =sizeof (BackupBlockJob), + }, .job_type =BLOCK_JOB_TYPE_BACKUP , .start = backup_run, .commit = backup_commit,
block/commit.c +3 -1
@@ -215,7 +215,9 @@ out: }static const BlockJobDriver commit_job_driver = { - .instance_size =sizeof (CommitBlockJob), + .job_driver = { + .instance_size =sizeof (CommitBlockJob), + }, .job_type =BLOCK_JOB_TYPE_COMMIT , .start = commit_run, };
block/mirror.c +7 -3
@@ -913,7 +913,7 @@ static void mirror_complete(BlockJob *job, Error **errp)if (!s->synced) {error_setg (errp,"The active block job '%s' cannot be completed" , - job->id); + job->job.id);return ; }
@@ -986,7 +986,9 @@ static void mirror_drain(BlockJob *job) }static const BlockJobDriver mirror_job_driver = { - .instance_size =sizeof (MirrorBlockJob), + .job_driver = { + .instance_size =sizeof (MirrorBlockJob), + }, .job_type =BLOCK_JOB_TYPE_MIRROR , .start = mirror_run, .complete = mirror_complete,
@@ -996,7 +998,9 @@ static const BlockJobDriver mirror_job_driver = { };static const BlockJobDriver commit_active_job_driver = { - .instance_size =sizeof (MirrorBlockJob), + .job_driver = { + .instance_size =sizeof (MirrorBlockJob), + }, .job_type =BLOCK_JOB_TYPE_COMMIT , .start = mirror_run, .complete = mirror_complete,
block/stream.c +3 -1
@@ -209,7 +209,9 @@ out: }static const BlockJobDriver stream_job_driver = { - .instance_size =sizeof (StreamBlockJob), + .job_driver = { + .instance_size =sizeof (StreamBlockJob), + }, .job_type =BLOCK_JOB_TYPE_STREAM , .start = stream_run, };
blockjob.c +22 -24
154 lines not on this page · raw message
job.c new file +48 -0
48 lines not on this page · raw message
tests/test-bdrv-drain.c +3 -1
@@ -520,7 +520,9 @@ static void test_job_complete(BlockJob *job, Error **errp) }BlockJobDriver test_job_driver = { - .instance_size =sizeof (TestBlockJob), + .job_driver = { + .instance_size =sizeof (TestBlockJob), + }, .start = test_job_start, .complete = test_job_complete, };
tests/test-blockjob-txn.c +3 -1
@@ -74,7 +74,9 @@ static void test_block_job_cb(void *opaque, int ret) }static const BlockJobDriver test_block_job_driver = { - .instance_size =sizeof (TestBlockJob), + .job_driver = { + .instance_size =sizeof (TestBlockJob), + }, .start = test_block_job_run, };
tests/test-blockjob.c +8 -4
@@ -17,7 +17,9 @@#include "sysemu/block-backend.h" static const BlockJobDriver test_block_job_driver = { - .instance_size =sizeof (BlockJob), + .job_driver = { + .instance_size =sizeof (BlockJob), + }, };static void block_job_cb (void *opaque,int ret)
@@ -38,9 +40,9 @@ static BlockJob *mk_job(BlockBackend *blk, const char *id,g_assert_null (errp);g_assert_nonnull (job);if (id) { - g_assert_cmpstr(job->id, ==, id); + g_assert_cmpstr(job->job.id, ==, id); }else { - g_assert_cmpstr(job->id, ==, blk_name(blk)); + g_assert_cmpstr(job->job.id, ==, blk_name(blk)); } }else {g_assert_nonnull (errp);
@@ -192,7 +194,9 @@ static void coroutine_fn cancel_job_start(void *opaque) }static const BlockJobDriver test_cancel_driver = { - .instance_size =sizeof (CancelJob), + .job_driver = { + .instance_size =sizeof (CancelJob), + }, .start = cancel_job_start, .complete = cancel_job_complete, };
MAINTAINERS +2 -0
@@ -1369,6 +1369,8 @@ L: [email protected] S: Supported F: blockjob.c F: include/block/blockjob.h +F: job.c +F: include/block/job.h F: block/backup.c F: block/commit.c F: block/stream.c
Makefile.objs +1 -1
@@ -63,7 +63,7 @@ chardev-obj-y = chardev/ # block-obj-y is code used by both qemu system emulation and qemu-img block-obj-y += nbd/ -block-obj-y += block.o blockjob.o +block-obj-y += block.o blockjob.o job.o block-obj-y += block/ scsi/ block-obj-y += qemu-io-cmds.o block-obj-$(CONFIG_REPLICATION) += replication.o
Signature · 1 line
2.13.6
PATCH v2 4/40 job: Rename BlockJobType into JobType
In reply to Kevin WolfQAPI types aren't externally visible, so we can rename them without causing problems. Before we add a job type to Job, rename the enum so it can be used for more than just block jobs.
--- qapi/block-core.json | 14 +++++++------- include/block/blockjob_int.h | 2 +- block/backup.c | 2 +- block/commit.c | 2 +- block/mirror.c | 4 ++-- block/stream.c | 2 +- blockjob.c | 6 +++--- 7 files changed, 16 insertions(+), 16 deletions(-)
qapi/block-core.json +7 -7
@@ -1050,9 +1050,9 @@ 'data': ['top', 'full', 'none', 'incremental'] } ## -# @BlockJobType: +# @JobType: # -# Type of a block job. +# Type of a background job. # # @commit: block commit job type, see "block-commit" #
@@ -1064,7 +1064,7 @@ # # Since: 1.7 ## -{ 'enum': 'BlockJobType', +{ 'enum': 'JobType', 'data': ['commit', 'stream', 'mirror', 'backup'] } ##
@@ -4499,7 +4499,7 @@ # ## { 'event': 'BLOCK_JOB_COMPLETED', - 'data': { 'type' : 'BlockJobType', + 'data': { 'type' : 'JobType', 'device': 'str', 'len' : 'int', 'offset': 'int',
@@ -4535,7 +4535,7 @@ # ## { 'event': 'BLOCK_JOB_CANCELLED', - 'data': { 'type' : 'BlockJobType', + 'data': { 'type' : 'JobType', 'device': 'str', 'len' : 'int', 'offset': 'int',
@@ -4600,7 +4600,7 @@ # ## { 'event': 'BLOCK_JOB_READY', - 'data': { 'type' : 'BlockJobType', + 'data': { 'type' : 'JobType', 'device': 'str', 'len' : 'int', 'offset': 'int',
@@ -4627,7 +4627,7 @@ # ## { 'event': 'BLOCK_JOB_PENDING', - 'data': { 'type' : 'BlockJobType', + 'data': { 'type' : 'JobType', 'id' : 'str' } } ##
include/block/blockjob_int.h +1 -1
@@ -39,7 +39,7 @@ struct BlockJobDriver {JobDriver job_driver;/** String describing the operation, part of query-block-jobs QMP API */ -BlockJobType job_type; +JobType job_type;/** Mandatory: Entrypoint for the Coroutine. */ CoroutineEntry *start;
block/backup.c +1 -1
@@ -526,7 +526,7 @@ static const BlockJobDriver backup_job_driver = { .job_driver = { .instance_size = sizeof(BackupBlockJob), }, - .job_type = BLOCK_JOB_TYPE_BACKUP, + .job_type = JOB_TYPE_BACKUP, .start = backup_run, .commit = backup_commit, .abort = backup_abort,
block/commit.c +1 -1
@@ -218,7 +218,7 @@ static const BlockJobDriver commit_job_driver = { .job_driver = { .instance_size = sizeof(CommitBlockJob), }, - .job_type = BLOCK_JOB_TYPE_COMMIT, + .job_type = JOB_TYPE_COMMIT, .start = commit_run, };
block/mirror.c +2 -2
@@ -989,7 +989,7 @@ static const BlockJobDriver mirror_job_driver = { .job_driver = { .instance_size = sizeof(MirrorBlockJob), }, - .job_type = BLOCK_JOB_TYPE_MIRROR, + .job_type = JOB_TYPE_MIRROR, .start = mirror_run, .complete = mirror_complete, .pause = mirror_pause,
@@ -1001,7 +1001,7 @@ static const BlockJobDriver commit_active_job_driver = { .job_driver = { .instance_size = sizeof(MirrorBlockJob), }, - .job_type = BLOCK_JOB_TYPE_COMMIT, + .job_type = JOB_TYPE_COMMIT, .start = mirror_run, .complete = mirror_complete, .pause = mirror_pause,
block/stream.c +1 -1
@@ -212,7 +212,7 @@ static const BlockJobDriver stream_job_driver = { .job_driver = { .instance_size = sizeof(StreamBlockJob), }, - .job_type = BLOCK_JOB_TYPE_STREAM, + .job_type = JOB_TYPE_STREAM, .start = stream_run, };
blockjob.c +3 -3
@@ -310,7 +310,7 @@ static char *child_job_get_parent_desc(BdrvChild *c) {BlockJob *job = c->opaque;return g_strdup_printf ("%s job '%s'" , -BlockJobType_str (job->driver->job_type), +JobType_str (job->driver->job_type), job->job.id); }
@@ -847,7 +847,7 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)return NULL ; } info =g_new0 (BlockJobInfo,1 ); - info->type =g_strdup (BlockJobType_str (job->driver->job_type)); + info->type =g_strdup (JobType_str (job->driver->job_type)); info->device =g_strdup (job->job.id); info->len = job->len; info->busy =atomic_read (&job->busy);
@@ -980,7 +980,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, block_job_sleep_timer_cb, job);error_setg (&job->blocker,"block device is in use by block job: %s" , -BlockJobType_str (driver->job_type)); +JobType_str (driver->job_type));block_job_add_bdrv (job,"main node" , bs,0 ,BLK_PERM_ALL , &error_abort); bs->job = job;
Signature · 1 line
2.13.6
PATCH v2 5/40 job: Add JobDriver.job_type
In reply to Kevin WolfThis moves the job_type field from BlockJobDriver to JobDriver.
--- include/block/blockjob_int.h | 3 --- include/qemu/job.h | 11 +++++++++++ block/backup.c | 2 +- block/commit.c | 2 +- block/mirror.c | 4 ++-- block/stream.c | 2 +- blockjob.c | 16 +++++++--------- job.c | 10 ++++++++++ 8 files changed, 33 insertions(+), 17 deletions(-)
include/block/blockjob_int.h +0 -3
@@ -38,9 +38,6 @@ struct BlockJobDriver {/** Generic JobDriver callbacks and settings */ JobDriver job_driver; -/** String describing the operation, part of query-block-jobs QMP API */ -JobType job_type; -/** Mandatory: Entrypoint for the Coroutine. */ CoroutineEntry *start;
include/qemu/job.h +11 -0
@@ -26,6 +26,8 @@#ifndef JOB_H #define JOB_H +#include "qapi/qapi-types-block-core.h" +typedef struct JobDriver JobDriver ;/**
@@ -45,6 +47,9 @@ typedef struct Job {struct JobDriver {/** Derived Job struct size */ size_t instance_size; + +/** Enum describing the operation */ +JobType job_type; };
@@ -57,4 +62,10 @@ struct JobDriver {*/ void *job_create (const char *job_id,const JobDriver *driver,Error **errp); +/** Returns the JobType of a given Job. */ +JobType job_type (const Job *job); + +/** Returns the enum string for the JobType of a given Job. */ +const char *job_type_str (const Job *job); +#endif
block/backup.c +1 -1
@@ -525,8 +525,8 @@ static void coroutine_fn backup_run(void *opaque)static const BlockJobDriver backup_job_driver = { .job_driver = { .instance_size =sizeof (BackupBlockJob), + .job_type =JOB_TYPE_BACKUP , }, - .job_type =JOB_TYPE_BACKUP , .start = backup_run, .commit = backup_commit, .abort = backup_abort,
block/commit.c +1 -1
@@ -217,8 +217,8 @@ out:static const BlockJobDriver commit_job_driver = { .job_driver = { .instance_size =sizeof (CommitBlockJob), + .job_type =JOB_TYPE_COMMIT , }, - .job_type =JOB_TYPE_COMMIT , .start = commit_run, };
block/mirror.c +2 -2
@@ -988,8 +988,8 @@ static void mirror_drain(BlockJob *job)static const BlockJobDriver mirror_job_driver = { .job_driver = { .instance_size =sizeof (MirrorBlockJob), + .job_type =JOB_TYPE_MIRROR , }, - .job_type =JOB_TYPE_MIRROR , .start = mirror_run, .complete = mirror_complete, .pause = mirror_pause,
@@ -1000,8 +1000,8 @@ static const BlockJobDriver mirror_job_driver = {static const BlockJobDriver commit_active_job_driver = { .job_driver = { .instance_size =sizeof (MirrorBlockJob), + .job_type =JOB_TYPE_COMMIT , }, - .job_type =JOB_TYPE_COMMIT , .start = mirror_run, .complete = mirror_complete, .pause = mirror_pause,
block/stream.c +1 -1
@@ -211,8 +211,8 @@ out:static const BlockJobDriver stream_job_driver = { .job_driver = { .instance_size =sizeof (StreamBlockJob), + .job_type =JOB_TYPE_STREAM , }, - .job_type =JOB_TYPE_STREAM , .start = stream_run, };
blockjob.c +7 -9
@@ -309,9 +309,7 @@ static void block_job_detach_aio_context(void *opaque)static char *child_job_get_parent_desc (BdrvChild *c) {BlockJob *job = c->opaque; -return g_strdup_printf ("%s job '%s'" , -JobType_str (job->driver->job_type), - job->job.id); +return g_strdup_printf ("%s job '%s'" ,job_type_str (&job->job), job->job.id); }static void child_job_drained_begin (BdrvChild *c)
@@ -847,7 +845,7 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp)return NULL ; } info =g_new0 (BlockJobInfo,1 ); - info->type =g_strdup (JobType_str (job->driver->job_type)); + info->type =g_strdup (job_type_str (&job->job)); info->device =g_strdup (job->job.id); info->len = job->len; info->busy =atomic_read (&job->busy);
@@ -878,7 +876,7 @@ static void block_job_event_cancelled(BlockJob *job)return ; } - qapi_event_send_block_job_cancelled(job->driver->job_type, + qapi_event_send_block_job_cancelled(job_type(&job->job), job->job.id, job->len, job->offset,
@@ -892,7 +890,7 @@ static void block_job_event_completed(BlockJob *job, const char *msg)return ; } - qapi_event_send_block_job_completed(job->driver->job_type, + qapi_event_send_block_job_completed(job_type(&job->job), job->job.id, job->len, job->offset,
@@ -906,7 +904,7 @@ static int block_job_event_pending(BlockJob *job) {block_job_state_transition (job,BLOCK_JOB_STATUS_PENDING );if (!job->auto_finalize && !block_job_is_internal (job)) { -qapi_event_send_block_job_pending (job->driver->job_type, +qapi_event_send_block_job_pending (job_type (&job->job), job->job.id, &error_abort); }
@@ -980,7 +978,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, block_job_sleep_timer_cb, job);error_setg (&job->blocker,"block device is in use by block job: %s" , -JobType_str (driver->job_type)); +job_type_str (&job->job));block_job_add_bdrv (job,"main node" , bs,0 ,BLK_PERM_ALL , &error_abort); bs->job = job;
@@ -1184,7 +1182,7 @@ void block_job_event_ready(BlockJob *job)return ; } - qapi_event_send_block_job_ready(job->driver->job_type, + qapi_event_send_block_job_ready(job_type(&job->job), job->job.id, job->len, job->offset,
job.c +10 -0
@@ -29,6 +29,16 @@#include "qemu/job.h" #include "qemu/id.h" +JobType job_type (const Job *job) +{ +return job->driver->job_type; +} + +const char *job_type_str (const Job *job) +{ +return JobType_str (job_type (job)); +} +void *job_create (const char *job_id,const JobDriver *driver,Error **errp) {Job *job;
Signature · 1 line
2.13.6
PATCH v2 6/40 job: Add job_delete()
In reply to Kevin WolfThis moves freeing the Job object and its fields from block_job_unref() to job_delete().
--- include/qemu/job.h | 3 +++ blockjob.c | 3 +-- job.c | 6 ++++++ 3 files changed, 10 insertions(+), 2 deletions(-)
include/qemu/job.h +3 -0
@@ -62,6 +62,9 @@ struct JobDriver {*/ void *job_create (const char *job_id,const JobDriver *driver,Error **errp); +/** Frees the @job object. */ +void job_delete (Job *job); +/** Returns the JobType of a given Job. */ JobType job_type (const Job *job);
blockjob.c +1 -2
@@ -261,9 +261,8 @@ void block_job_unref(BlockJob *job) block_job_detach_aio_context, job);blk_unref (job->blk);error_free (job->blocker); -g_free (job->job.id);assert (!timer_pending (&job->sleep_timer)); -g_free (job); +job_delete (&job->job); } }
job.c +6 -0
@@ -56,3 +56,9 @@ void *job_create(const char *job_id, const JobDriver *driver, Error **errp)return job; } + +void job_delete (Job *job) +{ +g_free (job->id); +g_free (job); +}
Signature · 1 line
2.13.6
PATCH v2 7/40 job: Maintain a list of all jobs
In reply to Kevin WolfThis moves the job list from BlockJob to Job. Now we can check for duplicate IDs in job_create().
--- include/block/blockjob.h | 3 --- include/qemu/job.h | 19 +++++++++++++++++++ blockjob.c | 46 ++++++++++++++++++++++++---------------------- job.c | 31 +++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 25 deletions(-)
include/block/blockjob.h +0 -3
@@ -105,9 +105,6 @@ typedef struct BlockJob {*/ bool deferred_to_main_loop; -/** Element of the list of block jobs */ -QLIST_ENTRY (BlockJob) job_list; -/** Status that is published by the query-block-jobs QMP API */ BlockDeviceIoStatus iostatus;
include/qemu/job.h +19 -0
@@ -27,6 +27,7 @@#define JOB_H #include "qapi/qapi-types-block-core.h" +#include "qemu/queue.h" typedef struct JobDriver JobDriver ;
@@ -39,6 +40,9 @@ typedef struct Job {/** The type of this job. */ const JobDriver *driver; + +/** Element of the list of jobs */ +QLIST_ENTRY (Job) job_list; } Job;/**
@@ -71,4 +75,19 @@ JobType job_type(const Job *job);/** Returns the enum string for the JobType of a given Job. */ const char *job_type_str (const Job *job); +/** +* Get the next element from the list of block jobs after @job, or the +* first one if @job is %NULL. +* +* Returns the requested job, or %NULL if there are no more jobs left. +*/ +Job *job_next (Job *job); + +/** +* Get the job identified by @id (which must not be %NULL). +* +* Returns the requested job, or %NULL if it doesn't exist. +*/ +Job *job_get (const char *id); +#endif
blockjob.c +24 -22
95 lines not on this page · raw message
job.c +31 -0
@@ -29,6 +29,8 @@#include "qemu/job.h" #include "qemu/id.h" +static QLIST_HEAD(, Job) jobs = QLIST_HEAD_INITIALIZER(jobs); +JobType job_type (const Job *job) {return job->driver->job_type;
@@ -39,6 +41,27 @@ const char *job_type_str(const Job *job)return JobType_str (job_type (job)); } +Job *job_next (Job *job) +{ +if (!job) { +return QLIST_FIRST (&jobs); + } +return QLIST_NEXT (job, job_list); +} + +Job *job_get (const char *id) +{ +Job *job; + + QLIST_FOREACH(job, &jobs, job_list) { +if (job->id && !strcmp (id, job->id)) { +return job; + } + } + +return NULL ; +} +void *job_create (const char *job_id,const JobDriver *driver,Error **errp) {Job *job;
@@ -48,17 +71,25 @@ void *job_create(const char *job_id, const JobDriver *driver, Error **errp)error_setg (errp,"Invalid job ID '%s'" , job_id);return NULL ; } +if (job_get (job_id)) { +error_setg (errp,"Job ID '%s' already in use" , job_id); +return NULL ; + } } job =g_malloc0 (driver->instance_size); job->driver = driver; job->id =g_strdup (job_id); +QLIST_INSERT_HEAD (&jobs, job, job_list); +return job; }void job_delete (Job *job) { +QLIST_REMOVE (job, job_list); +g_free (job->id);g_free (job); }
Signature · 1 line
2.13.6
PATCH v2 8/40 job: Move state transitions to Job
In reply to Kevin WolfThis moves BlockJob.status and the closely related functions (block_)job_state_transition() and (block_)job_apply_verb to Job. The two QAPI enums are renamed to JobStatus and JobVerb.
--- qapi/block-core.json | 16 ++++---- include/block/blockjob.h | 3 -- include/qemu/job.h | 13 ++++++ blockjob.c | 102 +++++++++++------------------------------------ job.c | 56 ++++++++++++++++++++++++++ tests/test-blockjob.c | 39 +++++++++--------- block/trace-events | 2 - trace-events | 4 ++ 8 files changed, 123 insertions(+), 112 deletions(-)
qapi/block-core.json +8 -8
@@ -1068,9 +1068,9 @@ 'data': ['commit', 'stream', 'mirror', 'backup'] } ## -# @BlockJobVerb: +# @JobVerb: # -# Represents command verbs that can be applied to a blockjob. +# Represents command verbs that can be applied to a job. # # @cancel: see @block-job-cancel #
@@ -1088,14 +1088,14 @@ # # Since: 2.12 ## -{ 'enum': 'BlockJobVerb', +{ 'enum': 'JobVerb', 'data': ['cancel', 'pause', 'resume', 'set-speed', 'complete', 'dismiss', 'finalize' ] } ## -# @BlockJobStatus: +# @JobStatus: # -# Indicates the present state of a given blockjob in its lifetime. +# Indicates the present state of a given job in its lifetime. # # @undefined: Erroneous, default state. Should not ever be visible. #
@@ -1134,7 +1134,7 @@ # # Since: 2.12 ## -{ 'enum': 'BlockJobStatus', +{ 'enum': 'JobStatus', 'data': ['undefined', 'created', 'running', 'paused', 'ready', 'standby', 'waiting', 'pending', 'aborting', 'concluded', 'null' ] }
@@ -1184,7 +1184,7 @@ 'data': {'type': 'str', 'device': 'str', 'len': 'int', 'offset': 'int', 'busy': 'bool', 'paused': 'bool', 'speed': 'int', 'io-status': 'BlockDeviceIoStatus', 'ready': 'bool', - 'status': 'BlockJobStatus', + 'status': 'JobStatus', 'auto-finalize': 'bool', 'auto-dismiss': 'bool', '*error': 'str' } }
@@ -2416,7 +2416,7 @@ # QEMU 2.12+ job lifetime management semantics. # # This command will refuse to operate on any job that has not yet reached -# its terminal state, BLOCK_JOB_STATUS_CONCLUDED. For jobs that make use of +# its terminal state, JOB_STATUS_CONCLUDED. For jobs that make use of the # BLOCK_JOB_READY event, block-job-cancel or block-job-complete will still need # to be used as appropriate. #
include/block/blockjob.h +0 -3
@@ -147,9 +147,6 @@ typedef struct BlockJob {*/ QEMUTimer sleep_timer; -/** Current state; See @BlockJobStatus for details. */ -BlockJobStatus status; -/** True if this job should automatically finalize itself */ bool auto_finalize;
include/qemu/job.h +13 -0
@@ -41,6 +41,9 @@ typedef struct Job {/** The type of this job. */ const JobDriver *driver; +/** Current state; See @JobStatus for details. */ +JobStatus status; +/** Element of the list of jobs */ QLIST_ENTRY (Job) job_list; } Job;
@@ -90,4 +93,14 @@ Job *job_next(Job *job);*/ Job *job_get (const char *id); +/** +* Check whether the verb @verb can be applied to @job in its current state. +* Returns 0 if the verb can be applied; otherwise errp is set and -EPERM +* returned. +*/ +int job_apply_verb (Job *job,JobVerb verb,Error **errp); + +/* TODO To be removed from the public interface */ +void job_state_transition (Job *job,JobStatus s1); +#endif
blockjob.c +23 -79
231 lines not on this page · raw message
job.c +56 -0
71 lines not on this page · raw message
tests/test-blockjob.c +19 -20
@@ -211,7 +211,7 @@ static CancelJob *create_common(BlockJob **pjob) job =mk_job (blk,"Steve" , &test_cancel_driver, true,BLOCK_JOB_MANUAL_FINALIZE |BLOCK_JOB_MANUAL_DISMISS );block_job_ref (job); -assert (job->status ==BLOCK_JOB_STATUS_CREATED ); +assert (job->job.status ==JOB_STATUS_CREATED ); s =container_of (job, CancelJob, common); s->blk = blk;
@@ -223,15 +223,14 @@ static void cancel_common(CancelJob *s) {BlockJob *job = &s->common;BlockBackend *blk = s->blk; -BlockJobStatus sts = job->status; +JobStatus sts = job->job.status;block_job_cancel_sync (job); -if ((sts !=BLOCK_JOB_STATUS_CREATED ) && - (sts !=BLOCK_JOB_STATUS_CONCLUDED )) { +if (sts !=JOB_STATUS_CREATED && sts !=JOB_STATUS_CONCLUDED ) {BlockJob *dummy = job;block_job_dismiss (&dummy, &error_abort); } -assert (job->status ==BLOCK_JOB_STATUS_NULL ); +assert (job->job.status ==JOB_STATUS_NULL );block_job_unref (job);destroy_blk (blk); }
@@ -253,7 +252,7 @@ static void test_cancel_running(void) s =create_common (&job);block_job_start (job); -assert (job->status ==BLOCK_JOB_STATUS_RUNNING ); +assert (job->job.status ==JOB_STATUS_RUNNING );cancel_common (s); }
@@ -266,11 +265,11 @@ static void test_cancel_paused(void) s =create_common (&job);block_job_start (job); -assert (job->status ==BLOCK_JOB_STATUS_RUNNING ); +assert (job->job.status ==JOB_STATUS_RUNNING );block_job_user_pause (job, &error_abort);block_job_enter (job); -assert (job->status ==BLOCK_JOB_STATUS_PAUSED ); +assert (job->job.status ==JOB_STATUS_PAUSED );cancel_common (s); }
@@ -283,11 +282,11 @@ static void test_cancel_ready(void) s =create_common (&job);block_job_start (job); -assert (job->status ==BLOCK_JOB_STATUS_RUNNING ); +assert (job->job.status ==JOB_STATUS_RUNNING ); s->should_converge = true;block_job_enter (job); -assert (job->status ==BLOCK_JOB_STATUS_READY ); +assert (job->job.status ==JOB_STATUS_READY );cancel_common (s); }
@@ -300,15 +299,15 @@ static void test_cancel_standby(void) s =create_common (&job);block_job_start (job); -assert (job->status ==BLOCK_JOB_STATUS_RUNNING ); +assert (job->job.status ==JOB_STATUS_RUNNING ); s->should_converge = true;block_job_enter (job); -assert (job->status ==BLOCK_JOB_STATUS_READY ); +assert (job->job.status ==JOB_STATUS_READY );block_job_user_pause (job, &error_abort);block_job_enter (job); -assert (job->status ==BLOCK_JOB_STATUS_STANDBY ); +assert (job->job.status ==JOB_STATUS_STANDBY );cancel_common (s); }
@@ -321,18 +320,18 @@ static void test_cancel_pending(void) s =create_common (&job);block_job_start (job); -assert (job->status ==BLOCK_JOB_STATUS_RUNNING ); +assert (job->job.status ==JOB_STATUS_RUNNING ); s->should_converge = true;block_job_enter (job); -assert (job->status ==BLOCK_JOB_STATUS_READY ); +assert (job->job.status ==JOB_STATUS_READY );block_job_complete (job, &error_abort);block_job_enter (job);while (!s->completed) {aio_poll (qemu_get_aio_context (), true); } -assert (job->status ==BLOCK_JOB_STATUS_PENDING ); +assert (job->job.status ==JOB_STATUS_PENDING );cancel_common (s); }
@@ -345,21 +344,21 @@ static void test_cancel_concluded(void) s =create_common (&job);block_job_start (job); -assert (job->status ==BLOCK_JOB_STATUS_RUNNING ); +assert (job->job.status ==JOB_STATUS_RUNNING ); s->should_converge = true;block_job_enter (job); -assert (job->status ==BLOCK_JOB_STATUS_READY ); +assert (job->job.status ==JOB_STATUS_READY );block_job_complete (job, &error_abort);block_job_enter (job);while (!s->completed) {aio_poll (qemu_get_aio_context (), true); } -assert (job->status ==BLOCK_JOB_STATUS_PENDING ); +assert (job->job.status ==JOB_STATUS_PENDING );block_job_finalize (job, &error_abort); -assert (job->status ==BLOCK_JOB_STATUS_CONCLUDED ); +assert (job->job.status ==JOB_STATUS_CONCLUDED );cancel_common (s); }
block/trace-events +0 -2
@@ -6,8 +6,6 @@ bdrv_lock_medium(void *bs, bool locked) "bs %p locked %d" # blockjob.c block_job_completed(void *job, int ret, int jret) "job %p ret %d corrected ret %d" -block_job_state_transition(void *job, int ret, const char *legal, const char *s0, const char *s1) "job %p (ret: %d) attempting %s transition (%s-->%s)" -block_job_apply_verb(void *job, const char *state, const char *verb, const char *legal) "job %p in state %s; applying verb %s (%s)" # block/block-backend.c blk_co_preadv(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags) "blk %p bs %p offset %"PRId64" bytes %u flags 0x%x"
trace-events +4 -0
@@ -104,6 +104,10 @@ gdbstub_err_invalid_rle(void) "got invalid RLE sequence" gdbstub_err_checksum_invalid(uint8_t ch) "got invalid command checksum digit: 0x%02x" gdbstub_err_checksum_incorrect(uint8_t expected, uint8_t got) "got command packet with incorrect checksum, expected=0x%02x, received=0x%02x" +# job.c +job_state_transition(void *job, int ret, const char *legal, const char *s0, const char *s1) "job %p (ret: %d) attempting %s transition (%s-->%s)" +job_apply_verb(void *job, const char *state, const char *verb, const char *legal) "job %p in state %s; applying verb %s (%s)" + ### Guest events, keep at bottom
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH v2 08/40] job: Move state transitions to Job
Quoted 11 lines · Kevin Wolf
This moves BlockJob.status and the closely related functions (block_)job_state_transition() and (block_)job_apply_verb to Job. The two QAPI enums are renamed to JobStatus and JobVerb.
Signed-off-by Kevin Wolf <[email protected]>Reviewed-by Max Reitz <[email protected]>Reviewed-by John Snow <[email protected]>---
blockjob.c +0
-0@@ -1077,14 +1021,14 @@ void coroutine_fn block_job_pause_point(BlockJob *job) }
blockjob.c void coroutine_fn block_job_pause_point(BlockJob *job) · line 1027
Quoted 10 lines
blockjob.c +4
-4if (block_job_should_pause (job) && !block_job_is_cancelled (job)) { -BlockJobStatus status = job->status; -block_job_state_transition (job, status ==BLOCK_JOB_STATUS_READY ? \ -BLOCK_JOB_STATUS_STANDBY : \ -BLOCK_JOB_STATUS_PAUSED ); +JobStatus status = job->job.status; +job_state_transition (&job->job, status ==JOB_STATUS_READY + ?JOB_STATUS_STANDBY + :JOB_STATUS_PAUSED );
Nice that you are getting rid of the pointless \. Here, you split the ?: operator by wrapping the ? onto the start of the next line...
job.c line 79
Quoted 8 lines
job.c +7
-0+++ b/job.c +int job_apply_verb (Job *job,JobVerb verb,Error **errp) +{ +assert (verb >=0 && verb <=JOB_VERB__MAX ); +trace_job_apply_verb (job,JobStatus_str (job->status),JobVerb_str (verb), + JobVerbTable[verb][job->status] ? +"allowed" :"prohibited" );
while here, you put it at the end of the previous line. We have a slight preference for one style over the other (there are also false positives in both of these greps):
$ git grep '?$' | wc -c 27251 $ git grep '^ *?' | wc -c 9619
But I don't care which style you use, other than pointing out that a consistent style within the patch doesn't hurt. :)
So, whether or not you make a whitespace-only tweak to one of those two spots,
Signature · 3 lines
Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
PATCH v2 9/40 job: Add reference counting
In reply to Kevin WolfThis moves reference counting from BlockJob to Job.
In order to keep calling the BlockJob cleanup code when the job is deleted via job_unref(), introduce a new JobDriver.free callback. Every block job must use block_job_free() for this callback, this is asserted in block_job_create().
--- include/block/blockjob.h | 21 ------------------- include/block/blockjob_int.h | 7 +++++++ include/qemu/job.h | 19 ++++++++++++++++-- block/backup.c | 1 + block/commit.c | 1 + block/mirror.c | 2 ++ block/stream.c | 1 + blockjob.c | 48 +++++++++++++++++++------------------------- job.c | 22 ++++++++++++++++---- qemu-img.c | 4 ++-- tests/test-bdrv-drain.c | 1 + tests/test-blockjob-txn.c | 1 + tests/test-blockjob.c | 6 ++++-- 13 files changed, 76 insertions(+), 58 deletions(-)
include/block/blockjob.h +0 -21
@@ -132,9 +132,6 @@ typedef struct BlockJob {/** The opaque value that is passed to the completion function. */ void *opaque; -/** Reference count of the block job */ -int refcnt; -/** True when job has reported completion by calling block_job_completed. */ bool completed;
@@ -400,24 +397,6 @@ void block_job_iostatus_reset(BlockJob *job);BlockJobTxn *block_job_txn_new (void );/** -* block_job_ref: -* -* Add a reference to BlockJob refcnt, it will be decreased with -* block_job_unref, and then be freed if it comes to be the last -* reference. -*/ -void block_job_ref (BlockJob *job); - -/** -* block_job_unref: -* -* Release a reference that was previously acquired with block_job_ref -* or block_job_create. If it's the last reference to the object, it will be -* freed. -*/ -void block_job_unref (BlockJob *job); - -/** * block_job_txn_unref: * * Release a reference that was previously acquired with block_job_txn_add_job
include/block/blockjob_int.h +7 -0
@@ -144,6 +144,13 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, BlockCompletionFunc *cb, void *opaque, Error **errp);/** +* block_job_free: +* Callback to be used for JobDriver.free in all block jobs. Frees block job +* specific resources in @job. +*/ +void block_job_free (Job *job); + +/** * block_job_sleep_ns: * @job: The job that calls the function. * @ns: How many nanoseconds to stop for.
include/qemu/job.h +17 -2
@@ -41,6 +41,9 @@ typedef struct Job {/** The type of this job. */ const JobDriver *driver; +/** Reference count of the block job */ +int refcnt; +/** Current state; See @JobStatus for details. */ JobStatus status;
@@ -57,6 +60,9 @@ struct JobDriver {/** Enum describing the operation */ JobType job_type; + +/** Called when the job is freed */ +void (*free)(Job *job); };
@@ -69,8 +75,17 @@ struct JobDriver {*/ void *job_create (const char *job_id,const JobDriver *driver,Error **errp); -/** Frees the @job object. */ -void job_delete (Job *job); +/** +* Add a reference to Job refcnt, it will be decreased with job_unref, and then +* be freed if it comes to be the last reference. +*/ +void job_ref (Job *job); + +/** +* Release a reference that was previously acquired with job_ref() or +* job_create(). If it's the last reference to the object, it will be freed. +*/ +void job_unref (Job *job);/** Returns the JobType of a given Job. */ JobType job_type (const Job *job);
block/backup.c +1 -0
@@ -526,6 +526,7 @@ static const BlockJobDriver backup_job_driver = { .job_driver = { .instance_size = sizeof(BackupBlockJob), .job_type = JOB_TYPE_BACKUP, + .free = block_job_free, }, .start = backup_run, .commit = backup_commit,
block/commit.c +1 -0
@@ -218,6 +218,7 @@ static const BlockJobDriver commit_job_driver = { .job_driver = { .instance_size = sizeof(CommitBlockJob), .job_type = JOB_TYPE_COMMIT, + .free = block_job_free, }, .start = commit_run, };
block/mirror.c +2 -0
@@ -989,6 +989,7 @@ static const BlockJobDriver mirror_job_driver = { .job_driver = { .instance_size = sizeof(MirrorBlockJob), .job_type = JOB_TYPE_MIRROR, + .free = block_job_free, }, .start = mirror_run, .complete = mirror_complete,
@@ -1001,6 +1002,7 @@ static const BlockJobDriver commit_active_job_driver = { .job_driver = { .instance_size = sizeof(MirrorBlockJob), .job_type = JOB_TYPE_COMMIT, + .free = block_job_free, }, .start = mirror_run, .complete = mirror_complete,
block/stream.c +1 -0
@@ -212,6 +212,7 @@ static const BlockJobDriver stream_job_driver = { .job_driver = { .instance_size = sizeof(StreamBlockJob), .job_type = JOB_TYPE_STREAM, + .free = block_job_free, }, .start = stream_run, };
blockjob.c +21 -27
107 lines not on this page · raw message
job.c +18 -4
@@ -134,6 +134,7 @@ void *job_create(const char *job_id, const JobDriver *driver, Error **errp) job =g_malloc0 (driver->instance_size); job->driver = driver; job->id =g_strdup (job_id); + job->refcnt =1 ;job_state_transition (job,JOB_STATUS_CREATED );
@@ -142,10 +143,23 @@ void *job_create(const char *job_id, const JobDriver *driver, Error **errp)return job; } -void job_delete (Job *job) +void job_ref (Job *job) { -QLIST_REMOVE (job, job_list); + ++job->refcnt; +} + +void job_unref (Job *job) +{ +if (--job->refcnt ==0 ) { +assert (job->status ==JOB_STATUS_NULL ); -g_free (job->id); -g_free (job); +if (job->driver->free) { + job->driver->free (job); + } + +QLIST_REMOVE (job, job_list); + +g_free (job->id); +g_free (job); + } }
qemu-img.c +2 -2
@@ -862,7 +862,7 @@ static void run_block_job(BlockJob *job, Error **errp)int ret =0 ;aio_context_acquire (aio_context); -block_job_ref (job); +job_ref (&job->job); do { aio_poll(aio_context, true); qemu_progress_print(job->len ?
@@ -874,7 +874,7 @@ static void run_block_job(BlockJob *job, Error **errp) }else { ret = job->ret; } -block_job_unref (job); +job_unref (&job->job);aio_context_release (aio_context);/* publish completion progress only when success */
tests/test-bdrv-drain.c +1 -0
@@ -522,6 +522,7 @@ static void test_job_complete(BlockJob *job, Error **errp)BlockJobDriver test_job_driver = { .job_driver = { .instance_size =sizeof (TestBlockJob), + .free = block_job_free, }, .start = test_job_start, .complete = test_job_complete,
tests/test-blockjob-txn.c +1 -0
@@ -76,6 +76,7 @@ static void test_block_job_cb(void *opaque, int ret)static const BlockJobDriver test_block_job_driver = { .job_driver = { .instance_size =sizeof (TestBlockJob), + .free = block_job_free, }, .start = test_block_job_run, };
tests/test-blockjob.c +4 -2
@@ -19,6 +19,7 @@static const BlockJobDriver test_block_job_driver = { .job_driver = { .instance_size =sizeof (BlockJob), + .free = block_job_free, }, };
@@ -196,6 +197,7 @@ static void coroutine_fn cancel_job_start(void *opaque)static const BlockJobDriver test_cancel_driver = { .job_driver = { .instance_size =sizeof (CancelJob), + .free = block_job_free, }, .start = cancel_job_start, .complete = cancel_job_complete,
@@ -210,7 +212,7 @@ static CancelJob *create_common(BlockJob **pjob) blk =create_blk (NULL ); job =mk_job (blk,"Steve" , &test_cancel_driver, true,BLOCK_JOB_MANUAL_FINALIZE |BLOCK_JOB_MANUAL_DISMISS ); -block_job_ref (job); +job_ref (&job->job);assert (job->job.status ==JOB_STATUS_CREATED ); s =container_of (job, CancelJob, common); s->blk = blk;
@@ -231,7 +233,7 @@ static void cancel_common(CancelJob *s)block_job_dismiss (&dummy, &error_abort); }assert (job->job.status ==JOB_STATUS_NULL ); -block_job_unref (job); +job_unref (&job->job);destroy_blk (blk); }
Signature · 1 line
2.13.6
PATCH v2 10/40 job: Move cancelled to Job
In reply to Kevin WolfWe cannot yet move the whole logic around job cancelling to Job because it depends on quite a few other things that are still only in BlockJob, but we can move the cancelled field at least.
--- include/block/blockjob.h | 8 -------- include/block/blockjob_int.h | 8 -------- include/qemu/job.h | 11 +++++++++++ block/backup.c | 6 +++--- block/commit.c | 4 ++-- block/mirror.c | 20 ++++++++++---------- block/stream.c | 4 ++-- blockjob.c | 28 +++++++++++++--------------- job.c | 5 +++++ tests/test-blockjob-txn.c | 6 +++--- tests/test-blockjob.c | 2 +- 11 files changed, 50 insertions(+), 52 deletions(-)
include/block/blockjob.h +0 -8
@@ -57,14 +57,6 @@ typedef struct BlockJob {Coroutine *co;/** -* Set to true if the job should cancel itself. The flag must -* always be tested just before toggling the busy flag from false -* to true. After a job has been cancelled, it should only yield -* if #aio_poll will ("sooner or later") reenter the coroutine. -*/ -bool cancelled; - -/** * Set to true if the job should abort immediately without waiting * for data to be in sync. */
include/block/blockjob_int.h +0 -8
@@ -196,14 +196,6 @@ void block_job_early_fail(BlockJob *job);void block_job_completed (BlockJob *job,int ret);/** -* block_job_is_cancelled: -* @job: The job being queried. -* -* Returns whether the job is scheduled for cancellation. -*/ -bool block_job_is_cancelled (BlockJob *job); - -/** * block_job_pause_point: * @job: The job that is ready to pause. *
include/qemu/job.h +11 -0
@@ -47,6 +47,14 @@ typedef struct Job {/** Current state; See @JobStatus for details. */ JobStatus status; +/** +* Set to true if the job should cancel itself. The flag must +* always be tested just before toggling the busy flag from false +* to true. After a job has been cancelled, it should only yield +* if #aio_poll will ("sooner or later") reenter the coroutine. +*/ +bool cancelled; +/** Element of the list of jobs */ QLIST_ENTRY (Job) job_list; } Job;
@@ -93,6 +101,9 @@ JobType job_type(const Job *job);/** Returns the enum string for the JobType of a given Job. */ const char *job_type_str (const Job *job); +/** Returns whether the job is scheduled for cancellation. */ +bool job_is_cancelled (Job *job); +/** * Get the next element from the list of block jobs after @job, or the * first one if @job is %NULL.
block/backup.c +3 -3
@@ -329,7 +329,7 @@ static bool coroutine_fn yield_and_check(BackupBlockJob *job) {uint64_t delay_ns; -if (block_job_is_cancelled (&job->common)) { +if (job_is_cancelled (&job->common.job)) {return true; }
@@ -339,7 +339,7 @@ static bool coroutine_fn yield_and_check(BackupBlockJob *job) job->bytes_read =0 ;block_job_sleep_ns (&job->common, delay_ns); -if (block_job_is_cancelled (&job->common)) { +if (job_is_cancelled (&job->common.job)) {return true; }
@@ -441,7 +441,7 @@ static void coroutine_fn backup_run(void *opaque)if (job->sync_mode ==MIRROR_SYNC_MODE_NONE ) {/* All bits are set in copy_bitmap to allow any cluster to be copied. * This does not actually require them to be copied. */ -while (!block_job_is_cancelled (&job->common)) { +while (!job_is_cancelled (&job->common.job)) {/* Yield until the job is cancelled. We just let our before_write * notify callback service CoW requests. */ block_job_yield (&job->common);
block/commit.c +2 -2
@@ -90,7 +90,7 @@ static void commit_complete(BlockJob *job, void *opaque)* the normal backing chain can be restored. */ blk_unref (s->base); -if (!block_job_is_cancelled (&s->common) && ret ==0 ) { +if (!job_is_cancelled (&s->common.job) && ret ==0 ) {/* success */ ret =bdrv_drop_intermediate (s->commit_top_bs, base, s->backing_file_str);
@@ -172,7 +172,7 @@ static void coroutine_fn commit_run(void *opaque)* with no pending I/O here so that bdrv_drain_all() returns. */ block_job_sleep_ns (&s->common, delay_ns); -if (block_job_is_cancelled (&s->common)) { +if (job_is_cancelled (&s->common.job)) {break ; }/* Copy if allocated above the base */
block/mirror.c +10 -10
@@ -622,7 +622,7 @@ static int coroutine_fn mirror_dirty_init(MirrorBlockJob *s)mirror_throttle (s); -if (block_job_is_cancelled (&s->common)) { +if (job_is_cancelled (&s->common.job)) { s->initial_zeroing_ongoing = false;return 0 ; }
@@ -650,7 +650,7 @@ static int coroutine_fn mirror_dirty_init(MirrorBlockJob *s)mirror_throttle (s); -if (block_job_is_cancelled (&s->common)) { +if (job_is_cancelled (&s->common.job)) {return 0 ; }
@@ -695,7 +695,7 @@ static void coroutine_fn mirror_run(void *opaque)checking for a NULL string */ int ret =0 ; -if (block_job_is_cancelled (&s->common)) { +if (job_is_cancelled (&s->common.job)) { goto immediate_exit; }
@@ -729,10 +729,10 @@ static void coroutine_fn mirror_run(void *opaque)/* Report BLOCK_JOB_READY and wait for complete. */ block_job_event_ready (&s->common); s->synced = true; -while (!block_job_is_cancelled (&s->common) && !s->should_complete) { +while (!job_is_cancelled (&s->common.job) && !s->should_complete) {block_job_yield (&s->common); } - s->common.cancelled = false; + s->common.job.cancelled = false; goto immediate_exit; }
@@ -768,7 +768,7 @@ static void coroutine_fn mirror_run(void *opaque) s->last_pause_ns =qemu_clock_get_ns (QEMU_CLOCK_REALTIME );if (!s->is_none_mode) { ret =mirror_dirty_init (s); -if (ret <0 ||block_job_is_cancelled (&s->common)) { +if (ret <0 ||job_is_cancelled (&s->common.job)) { goto immediate_exit; } }
@@ -828,7 +828,7 @@ static void coroutine_fn mirror_run(void *opaque) } should_complete = s->should_complete || -block_job_is_cancelled (&s->common); +job_is_cancelled (&s->common.job); cnt =bdrv_get_dirty_count (s->dirty_bitmap); }
@@ -856,7 +856,7 @@ static void coroutine_fn mirror_run(void *opaque)* completion. */ assert (QLIST_EMPTY (&bs->tracked_requests)); - s->common.cancelled = false; + s->common.job.cancelled = false; need_drain = false;break ; }
@@ -869,7 +869,7 @@ static void coroutine_fn mirror_run(void *opaque) }trace_mirror_before_sleep (s, cnt, s->synced, delay_ns);block_job_sleep_ns (&s->common, delay_ns); -if (block_job_is_cancelled (&s->common) && +if (job_is_cancelled (&s->common.job) && (!s->synced || s->common.force)) {break ;
@@ -884,7 +884,7 @@ immediate_exit:* the target is a copy of the source. */ assert (ret <0 || ((s->common.force || !s->synced) && -block_job_is_cancelled (&s->common))); +job_is_cancelled (&s->common.job)));assert (need_drain);mirror_wait_for_all_io (s); }
block/stream.c +2 -2
@@ -66,7 +66,7 @@ static void stream_complete(BlockJob *job, void *opaque)BlockDriverState *base = s->base;Error *local_err =NULL ; -if (!block_job_is_cancelled (&s->common) && bs->backing && +if (!job_is_cancelled (&s->common.job) && bs->backing && data->ret ==0 ) {const char *base_id =NULL , *base_fmt =NULL ;if (base) {
@@ -141,7 +141,7 @@ static void coroutine_fn stream_run(void *opaque)* with no pending I/O here so that bdrv_drain_all() returns. */ block_job_sleep_ns (&s->common, delay_ns); -if (block_job_is_cancelled (&s->common)) { +if (job_is_cancelled (&s->common.job)) {break ; }
blockjob.c +13 -15
@@ -379,7 +379,7 @@ static void block_job_conclude(BlockJob *job)static void block_job_update_rc (BlockJob *job) { -if (!job->ret &&block_job_is_cancelled (job)) { +if (!job->ret &&job_is_cancelled (&job->job)) { job->ret = -ECANCELED ; }if (job->ret) {
@@ -438,7 +438,7 @@ static int block_job_finalize_single(BlockJob *job)/* Emit events only if we actually started */ if (block_job_started (job)) { -if (block_job_is_cancelled (job)) { +if (job_is_cancelled (&job->job)) {block_job_event_cancelled (job); }else {const char *msg =NULL ;
@@ -464,7 +464,7 @@ static void block_job_cancel_async(BlockJob *job, bool force) job->user_paused = false; job->pause_count--; } - job->cancelled = true; + job->job.cancelled = true;/* To prevent 'force == false' overriding a previous 'force == true' */ job->force |= force; }
@@ -519,7 +519,8 @@ static int block_job_finish_sync(BlockJob *job,while (!job->completed) {aio_poll (qemu_get_aio_context (), true); } - ret = (job->cancelled && job->ret ==0 ) ? -ECANCELED : job->ret; + ret = (job_is_cancelled (&job->job) && job->ret ==0 ) + ? -ECANCELED : job->ret;job_unref (&job->job);return ret; }
@@ -557,7 +558,7 @@ static void block_job_completed_txn_abort(BlockJob *job) other_job =QLIST_FIRST (&txn->jobs); ctx =blk_get_aio_context (other_job->blk);if (!other_job->completed) { -assert (other_job->cancelled); +assert (job_is_cancelled (&other_job->job));block_job_finish_sync (other_job,NULL ,NULL ); }block_job_finalize_single (other_job);
@@ -651,7 +652,9 @@ void block_job_complete(BlockJob *job, Error **errp)if (job_apply_verb (&job->job,JOB_VERB_COMPLETE , errp)) {return ; } -if (job->pause_count || job->cancelled || !job->driver->complete) { +if (job->pause_count ||job_is_cancelled (&job->job) || + !job->driver->complete) + {error_setg (errp,"The active block job '%s' cannot be completed" , job->job.id);return ;
@@ -1006,7 +1009,7 @@ void coroutine_fn block_job_pause_point(BlockJob *job)if (!block_job_should_pause (job)) {return ; } -if (block_job_is_cancelled (job)) { +if (job_is_cancelled (&job->job)) {return ; }
@@ -1014,7 +1017,7 @@ void coroutine_fn block_job_pause_point(BlockJob *job) job->driver->pause (job); } -if (block_job_should_pause (job) && !block_job_is_cancelled (job)) { +if (block_job_should_pause (job) && !job_is_cancelled (&job->job)) {JobStatus status = job->job.status; job_state_transition(&job->job, status == JOB_STATUS_READY ? JOB_STATUS_STANDBY
@@ -1066,17 +1069,12 @@ void block_job_enter(BlockJob *job)block_job_enter_cond (job,NULL ); } -bool block_job_is_cancelled (BlockJob *job) -{ -return job->cancelled; -} -void block_job_sleep_ns (BlockJob *job,int64_t ns) {assert (job->busy);/* Check cancellation *before* setting busy = false, too! */ -if (block_job_is_cancelled (job)) { +if (job_is_cancelled (&job->job)) {return ; }
@@ -1092,7 +1090,7 @@ void block_job_yield(BlockJob *job)assert (job->busy);/* Check cancellation *before* setting busy = false, too! */ -if (block_job_is_cancelled (job)) { +if (job_is_cancelled (&job->job)) {return ; }
job.c +5 -0
@@ -95,6 +95,11 @@ const char *job_type_str(const Job *job)return JobType_str (job_type (job)); } +bool job_is_cancelled (Job *job) +{ +return job->cancelled; +} +Job *job_next (Job *job) {if (!job) {
tests/test-blockjob-txn.c +3 -3
@@ -29,7 +29,7 @@ static void test_block_job_complete(BlockJob *job, void *opaque)BlockDriverState *bs =blk_bs (job->blk);int rc = (intptr_t )opaque; -if (block_job_is_cancelled (job)) { +if (job_is_cancelled (&job->job)) { rc = -ECANCELED ; }
@@ -49,7 +49,7 @@ static void coroutine_fn test_block_job_run(void *opaque)block_job_yield (job); } -if (block_job_is_cancelled (job)) { +if (job_is_cancelled (&job->job)) {break ; } }
@@ -66,7 +66,7 @@ typedef struct {static void test_block_job_cb (void *opaque,int ret) {TestBlockJobCBData *data = opaque; -if (!ret &&block_job_is_cancelled (&data->job->common)) { +if (!ret &&job_is_cancelled (&data->job->common.job)) { ret = -ECANCELED ; } *data->result = ret;
tests/test-blockjob.c +1 -1
@@ -179,7 +179,7 @@ static void coroutine_fn cancel_job_start(void *opaque)CancelJob *s = opaque;while (!s->should_complete) { -if (block_job_is_cancelled (&s->common)) { +if (job_is_cancelled (&s->common.job)) { goto defer; }
Signature · 1 line
2.13.6
PATCH v2 11/40 job: Add Job.aio_context
In reply to Kevin WolfWhen block jobs need an AioContext, they just take it from their main block node. Generic jobs don't have a main block node, so we need to assign them an AioContext explicitly.
--- include/qemu/job.h | 7 ++++++- blockjob.c | 5 ++++- job.c | 4 +++- 3 files changed, 13 insertions(+), 3 deletions(-)
include/qemu/job.h +6 -1
@@ -47,6 +47,9 @@ typedef struct Job {/** Current state; See @JobStatus for details. */ JobStatus status; +/** AioContext to run the job coroutine in */ +AioContext *aio_context; +/** * Set to true if the job should cancel itself. The flag must * always be tested just before toggling the busy flag from false
@@ -79,9 +82,11 @@ struct JobDriver {* * @job_id: The id of the newly-created job, or %NULL for internal jobs * @driver: The class object for the newly-created job. +* @ctx: The AioContext to run the job coroutine in. * @errp: Error object. */ -void *job_create (const char *job_id,const JobDriver *driver,Error **errp); +void *job_create (const char *job_id,const JobDriver *driver,AioContext *ctx, +Error **errp);/** * Add a reference to Job refcnt, it will be decreased with job_unref, and then
blockjob.c +4 -1
@@ -216,6 +216,7 @@ static void block_job_attached_aio_context(AioContext *new_context, {BlockJob *job = opaque; + job->job.aio_context = new_context;if (job->driver->attached_aio_context) { job->driver->attached_aio_context (job, new_context); }
@@ -247,6 +248,7 @@ static void block_job_detach_aio_context(void *opaque)block_job_drain (job); } + job->job.aio_context =NULL ;job_unref (&job->job); }
@@ -899,7 +901,8 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,return NULL ; } - job =job_create (job_id, &driver->job_driver, errp); + job =job_create (job_id, &driver->job_driver,blk_get_aio_context (blk), + errp);if (job ==NULL ) {blk_unref (blk);return NULL ;
job.c +3 -1
@@ -121,7 +121,8 @@ Job *job_get(const char *id)return NULL ; } -void *job_create (const char *job_id,const JobDriver *driver,Error **errp) +void *job_create (const char *job_id,const JobDriver *driver,AioContext *ctx, +Error **errp) {Job *job;
@@ -140,6 +141,7 @@ void *job_create(const char *job_id, const JobDriver *driver, Error **errp) job->driver = driver; job->id =g_strdup (job_id); job->refcnt =1 ; + job->aio_context = ctx;job_state_transition (job,JOB_STATUS_CREATED );
Signature · 1 line
2.13.6
PATCH v2 12/40 job: Move defer_to_main_loop to Job
In reply to Kevin WolfMove the defer_to_main_loop functionality from BlockJob to Job.
The code can be simplified because we can use job->aio_context in job_defer_to_main_loop_bh() now, instead of having to access the BlockDriverState.
Probably taking the data->aio_context lock in addition was already unnecessary in the old code because we didn't actually make use of anything protected by the old AioContext except getting the new AioContext, in case it changed between scheduling the BH and running it. But it's certainly unnecessary now that the BDS isn't accessed at all any more.
--- include/block/blockjob.h | 5 ---- include/block/blockjob_int.h | 19 --------------- include/qemu/job.h | 20 ++++++++++++++++ block/backup.c | 7 +++--- block/commit.c | 11 +++++---- block/mirror.c | 15 ++++++------ block/stream.c | 14 +++++------ blockjob.c | 57 ++++---------------------------------------- job.c | 32 +++++++++++++++++++++++++ tests/test-bdrv-drain.c | 7 +++--- tests/test-blockjob-txn.c | 13 +++++----- tests/test-blockjob.c | 7 +++--- 12 files changed, 97 insertions(+), 110 deletions(-)
include/block/blockjob.h +0 -5
@@ -92,11 +92,6 @@ typedef struct BlockJob {*/ bool ready; -/** -* Set to true when the job has deferred work to the main loop. -*/ -bool deferred_to_main_loop; -/** Status that is published by the query-block-jobs QMP API */ BlockDeviceIoStatus iostatus;
include/block/blockjob_int.h +0 -19
@@ -233,23 +233,4 @@ void block_job_event_ready(BlockJob *job);BlockErrorAction block_job_error_action (BlockJob *job,BlockdevOnError on_err,int is_read,int error); -typedef void BlockJobDeferToMainLoopFn (BlockJob *job,void *opaque); - -/** -* block_job_defer_to_main_loop: -* @job: The job -* @fn: The function to run in the main loop -* @opaque: The opaque value that is passed to @fn -* -* This function must be called by the main job coroutine just before it -* returns. @fn is executed in the main loop with the BlockDriverState -* AioContext acquired. Block jobs must call bdrv_unref(), bdrv_close(), and -* anything that uses bdrv_drain_all() in the main loop. -* -* The @job AioContext is held while @fn executes. -*/ -void block_job_defer_to_main_loop (BlockJob *job, -BlockJobDeferToMainLoopFn *fn, -void *opaque); -#endif
include/qemu/job.h +20 -0
@@ -58,6 +58,9 @@ typedef struct Job {*/ bool cancelled; +/** Set to true when the job has deferred work to the main loop. */ +bool deferred_to_main_loop; +/** Element of the list of jobs */ QLIST_ENTRY (Job) job_list; } Job;
@@ -131,6 +134,23 @@ Job *job_get(const char *id);*/ int job_apply_verb (Job *job,JobVerb verb,Error **errp); +typedef void JobDeferToMainLoopFn (Job *job,void *opaque); + +/** +* @job: The job +* @fn: The function to run in the main loop +* @opaque: The opaque value that is passed to @fn +* +* This function must be called by the main job coroutine just before it +* returns. @fn is executed in the main loop with the job AioContext acquired. +* +* Block jobs must call bdrv_unref(), bdrv_close(), and anything that uses +* bdrv_drain_all() in the main loop. +* +* The @job AioContext is held while @fn executes. +*/ +void job_defer_to_main_loop (Job *job,JobDeferToMainLoopFn *fn,void *opaque); +/* TODO To be removed from the public interface */ void job_state_transition (Job *job,JobStatus s1);
block/backup.c +4 -3
@@ -317,11 +317,12 @@ typedef struct {int ret; } BackupCompleteData; -static void backup_complete (BlockJob *job,void *opaque) +static void backup_complete (Job *job,void *opaque) { +BlockJob *bjob =container_of (job, BlockJob, job);BackupCompleteData *data = opaque; -block_job_completed (job, data->ret); +block_job_completed (bjob, data->ret);g_free (data); }
@@ -519,7 +520,7 @@ static void coroutine_fn backup_run(void *opaque) data =g_malloc (sizeof (*data)); data->ret = ret; -block_job_defer_to_main_loop (&job->common, backup_complete, data); +job_defer_to_main_loop (&job->common.job, backup_complete, data); }static const BlockJobDriver backup_job_driver = {
block/commit.c +6 -5
@@ -72,9 +72,10 @@ typedef struct {int ret; } CommitCompleteData; -static void commit_complete (BlockJob *job,void *opaque) +static void commit_complete (Job *job,void *opaque) { -CommitBlockJob *s =container_of (job, CommitBlockJob, common); +CommitBlockJob *s =container_of (job, CommitBlockJob, common.job); +BlockJob *bjob = &s->common;CommitCompleteData *data = opaque;BlockDriverState *top =blk_bs (s->top);BlockDriverState *base =blk_bs (s->base);
@@ -90,7 +91,7 @@ static void commit_complete(BlockJob *job, void *opaque)* the normal backing chain can be restored. */ blk_unref (s->base); -if (!job_is_cancelled (&s->common.job) && ret ==0 ) { +if (!job_is_cancelled (job) && ret ==0 ) {/* success */ ret =bdrv_drop_intermediate (s->commit_top_bs, base, s->backing_file_str);
@@ -114,7 +115,7 @@ static void commit_complete(BlockJob *job, void *opaque)* block_job_finish_sync()), block_job_completed() won't free it and * therefore the blockers on the intermediate nodes remain. This would * cause bdrv_set_backing_hd() to fail. */ -block_job_remove_all_bdrv (job); +block_job_remove_all_bdrv (bjob);block_job_completed (&s->common, ret);g_free (data);
@@ -211,7 +212,7 @@ out: data =g_malloc (sizeof (*data)); data->ret = ret; -block_job_defer_to_main_loop (&s->common, commit_complete, data); +job_defer_to_main_loop (&s->common.job, commit_complete, data); }static const BlockJobDriver commit_job_driver = {
block/mirror.c +8 -7
@@ -484,9 +484,10 @@ typedef struct {int ret; } MirrorExitData; -static void mirror_exit (BlockJob *job,void *opaque) +static void mirror_exit (Job *job,void *opaque) { -MirrorBlockJob *s =container_of (job, MirrorBlockJob, common); +MirrorBlockJob *s =container_of (job, MirrorBlockJob, common.job); +BlockJob *bjob = &s->common;MirrorExitData *data = opaque;AioContext *replace_aio_context =NULL ;BlockDriverState *src = s->source;
@@ -568,7 +569,7 @@ static void mirror_exit(BlockJob *job, void *opaque)* the blockers on the intermediate nodes so that the resulting state is * valid. Also give up permissions on mirror_top_bs->backing, which might * block the removal. */ -block_job_remove_all_bdrv (job); +block_job_remove_all_bdrv (bjob);bdrv_child_try_set_perm (mirror_top_bs->backing,0 ,BLK_PERM_ALL , &error_abort);bdrv_replace_node (mirror_top_bs,backing_bs (mirror_top_bs), &error_abort);
@@ -576,9 +577,9 @@ static void mirror_exit(BlockJob *job, void *opaque)/* We just changed the BDS the job BB refers to (with either or both of the * bdrv_replace_node() calls), so switch the BB back so the cleanup does * the right thing. We don't need any permissions any more now. */ -blk_remove_bs (job->blk); -blk_set_perm (job->blk,0 ,BLK_PERM_ALL , &error_abort); -blk_insert_bs (job->blk, mirror_top_bs, &error_abort); +blk_remove_bs (bjob->blk); +blk_set_perm (bjob->blk,0 ,BLK_PERM_ALL , &error_abort); +blk_insert_bs (bjob->blk, mirror_top_bs, &error_abort);block_job_completed (&s->common, data->ret);
@@ -901,7 +902,7 @@ immediate_exit:if (need_drain) {bdrv_drained_begin (bs); } -block_job_defer_to_main_loop (&s->common, mirror_exit, data); +job_defer_to_main_loop (&s->common.job, mirror_exit, data); }static void mirror_complete (BlockJob *job,Error **errp)
block/stream.c +7 -7
@@ -58,16 +58,16 @@ typedef struct {int ret; } StreamCompleteData; -static void stream_complete (BlockJob *job,void *opaque) +static void stream_complete (Job *job,void *opaque) { -StreamBlockJob *s =container_of (job, StreamBlockJob, common); +StreamBlockJob *s =container_of (job, StreamBlockJob, common.job); +BlockJob *bjob = &s->common;StreamCompleteData *data = opaque; -BlockDriverState *bs =blk_bs (job->blk); +BlockDriverState *bs =blk_bs (bjob->blk);BlockDriverState *base = s->base;Error *local_err =NULL ; -if (!job_is_cancelled (&s->common.job) && bs->backing && - data->ret ==0 ) { +if (!job_is_cancelled (job) && bs->backing && data->ret ==0 ) {const char *base_id =NULL , *base_fmt =NULL ;if (base) { base_id = s->backing_file_str;
@@ -88,7 +88,7 @@ out:/* Reopen the image back in read-only mode if necessary */ if (s->bs_flags !=bdrv_get_flags (bs)) {/* Give up write permissions before making it read-only */ -blk_set_perm (job->blk,0 ,BLK_PERM_ALL , &error_abort); +blk_set_perm (bjob->blk,0 ,BLK_PERM_ALL , &error_abort);bdrv_reopen (bs, s->bs_flags,NULL ); }
@@ -205,7 +205,7 @@ out:/* Modify backing chain and close BDSes in main loop */ data =g_malloc (sizeof (*data)); data->ret = ret; -block_job_defer_to_main_loop (&s->common, stream_complete, data); +job_defer_to_main_loop (&s->common.job, stream_complete, data); }static const BlockJobDriver stream_job_driver = {
blockjob.c +5 -52
90 lines not on this page · raw message
job.c +32 -0
@@ -28,6 +28,7 @@#include "qapi/error.h" #include "qemu/job.h" #include "qemu/id.h" +#include "qemu/main-loop.h" #include "trace-root.h" static QLIST_HEAD(, Job) jobs = QLIST_HEAD_INITIALIZER(jobs);
@@ -170,3 +171,34 @@ void job_unref(Job *job)g_free (job); } } + +typedef struct { +Job *job; +JobDeferToMainLoopFn *fn; +void *opaque; +}JobDeferToMainLoopData ; + +static void job_defer_to_main_loop_bh (void *opaque) +{ +JobDeferToMainLoopData *data = opaque; +Job *job = data->job; +AioContext *aio_context = job->aio_context; + +aio_context_acquire (aio_context); + data->fn (data->job, data->opaque); +aio_context_release (aio_context); + +g_free (data); +} + +void job_defer_to_main_loop (Job *job,JobDeferToMainLoopFn *fn,void *opaque) +{ +JobDeferToMainLoopData *data =g_malloc (sizeof (*data)); + data->job = job; + data->fn = fn; + data->opaque = opaque; + job->deferred_to_main_loop = true; + +aio_bh_schedule_oneshot (qemu_get_aio_context (), + job_defer_to_main_loop_bh, data); +}
tests/test-bdrv-drain.c +4 -3
@@ -496,9 +496,10 @@ typedef struct TestBlockJob {bool should_complete; } TestBlockJob; -static void test_job_completed (BlockJob *job,void *opaque) +static void test_job_completed (Job *job,void *opaque) { -block_job_completed (job,0 ); +BlockJob *bjob =container_of (job, BlockJob, job); +block_job_completed (bjob,0 ); } static void coroutine_fn test_job_start(void *opaque)
@@ -510,7 +511,7 @@ static void coroutine_fn test_job_start(void *opaque)block_job_sleep_ns (&s->common,100000 ); } -block_job_defer_to_main_loop (&s->common, test_job_completed,NULL ); +job_defer_to_main_loop (&s->common.job, test_job_completed,NULL ); }static void test_job_complete (BlockJob *job,Error **errp)
tests/test-blockjob-txn.c +7 -6
@@ -24,16 +24,17 @@ typedef struct {int *result; } TestBlockJob; -static void test_block_job_complete (BlockJob *job,void *opaque) +static void test_block_job_complete (Job *job,void *opaque) { -BlockDriverState *bs =blk_bs (job->blk); +BlockJob *bjob =container_of (job, BlockJob, job); +BlockDriverState *bs =blk_bs (bjob->blk);int rc = (intptr_t )opaque; -if (job_is_cancelled (&job->job)) { +if (job_is_cancelled (job)) { rc = -ECANCELED ; } -block_job_completed (job, rc); +block_job_completed (bjob, rc);bdrv_unref (bs); }
@@ -54,8 +55,8 @@ static void coroutine_fn test_block_job_run(void *opaque) } } -block_job_defer_to_main_loop (job, test_block_job_complete, - (void *)(intptr_t )s->rc); +job_defer_to_main_loop (&job->job, test_block_job_complete, + (void *)(intptr_t )s->rc); } typedef struct {
tests/test-blockjob.c +4 -3
@@ -161,11 +161,12 @@ typedef struct CancelJob {bool completed; } CancelJob; -static void cancel_job_completed (BlockJob *job,void *opaque) +static void cancel_job_completed (Job *job,void *opaque) { +BlockJob *bjob =container_of (job, BlockJob, job);CancelJob *s = opaque; s->completed = true; -block_job_completed (job,0 ); +block_job_completed (bjob,0 ); }static void cancel_job_complete (BlockJob *job,Error **errp)
@@ -191,7 +192,7 @@ static void coroutine_fn cancel_job_start(void *opaque) } defer: -block_job_defer_to_main_loop (&s->common, cancel_job_completed, s); +job_defer_to_main_loop (&s->common.job, cancel_job_completed, s); }static const BlockJobDriver test_cancel_driver = {
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH v2 12/40] job: Move defer_to_main_loop to Job
Quoted 13 lines · Kevin Wolf
Move the
defer_to_main_loopfunctionality from BlockJob to Job.The code can be simplified because we can use
job->aio_contextinjob_defer_to_main_loop_bh()now, instead of having to access the BlockDriverState.Probably taking the
data->aio_contextlock in addition was already unnecessary in the old code because we didn't actually make use of anything protected by the old AioContext except getting the new AioContext, in case it changed between scheduling the BH and running it. But it's certainly unnecessary now that the BDS isn't accessed at all any more.
Quoted 2 lines
Signed-off-by Kevin Wolf <[email protected]>Reviewed-by Max Reitz <[email protected]>
PATCH v2 13/40 job: Move coroutine and related code to Job
In reply to Kevin WolfThis commit moves some core functions for dealing with the job coroutine from BlockJob to Job. This includes primarily entering the coroutine (both for the first and reentering) and yielding explicitly and at pause points.
--- include/block/blockjob.h | 40 -------- include/block/blockjob_int.h | 26 ----- include/qemu/job.h | 76 +++++++++++++++ block/backup.c | 2 +- block/commit.c | 4 +- block/mirror.c | 22 ++--- block/replication.c | 2 +- block/stream.c | 4 +- blockdev.c | 8 +- blockjob.c | 219 ++++++++----------------------------------- job.c | 137 +++++++++++++++++++++++++++ tests/test-bdrv-drain.c | 38 ++++---- tests/test-blockjob-txn.c | 12 +-- tests/test-blockjob.c | 14 +-- 14 files changed, 305 insertions(+), 299 deletions(-)
include/block/blockjob.h +0 -40
@@ -51,43 +51,18 @@ typedef struct BlockJob {BlockBackend *blk;/** -* The coroutine that executes the job. If not NULL, it is -* reentered when busy is false and the job is cancelled. -*/ -Coroutine *co; - -/** * Set to true if the job should abort immediately without waiting * for data to be in sync. */ bool force;/** -* Counter for pause request. If non-zero, the block job is either paused, -* or if busy == true will pause itself as soon as possible. -*/ -int pause_count; - -/** * Set to true if the job is paused by user. Can be unpaused with the * block-job-resume QMP command. */ bool user_paused;/** -* Set to false by the job while the coroutine has yielded and may be -* re-entered by block_job_enter(). There may still be I/O or event loop -* activity pending. Accessed under block_job_mutex (in blockjob.c). -*/ -bool busy; - -/** -* Set to true by the job while it is in a quiescent state, where -* no I/O or event loop activity is pending. -*/ -bool paused; - -/** * Set to true when the job is ready to be completed. */ bool ready;
@@ -125,12 +100,6 @@ typedef struct BlockJob {/** ret code passed to block_job_completed. */ int ret; -/** -* Timer that is used by @block_job_sleep_ns. Accessed under -* block_job_mutex (in blockjob.c). -*/ -QEMUTimer sleep_timer; -/** True if this job should automatically finalize itself */ bool auto_finalize;
@@ -208,15 +177,6 @@ void block_job_remove_all_bdrv(BlockJob *job);void block_job_set_speed (BlockJob *job,int64_t speed,Error **errp);/** -* block_job_start: -* @job: A job that has not yet been started. -* -* Begins execution of a block job. -* Takes ownership of one reference to the job object. -*/ -void block_job_start (BlockJob *job); - -/** * block_job_cancel: * @job: The job to be canceled. * @force: Quit a job without waiting for data to be in sync.
include/block/blockjob_int.h +0 -26
@@ -38,9 +38,6 @@ struct BlockJobDriver {/** Generic JobDriver callbacks and settings */ JobDriver job_driver; -/** Mandatory: Entrypoint for the Coroutine. */ -CoroutineEntry *start; -/** * Optional callback for job types whose completion must be triggered * manually.
@@ -85,20 +82,6 @@ struct BlockJobDriver {*/ void (*clean)(BlockJob *job); -/** -* If the callback is not NULL, it will be invoked when the job transitions -* into the paused state. Paused jobs must not perform any asynchronous -* I/O or event loop activity. This callback is used to quiesce jobs. -*/ - void coroutine_fn (*pause)(BlockJob *job); - -/** -* If the callback is not NULL, it will be invoked when the job transitions -* out of the paused state. Any asynchronous I/O or event loop activity -* should be restarted from this callback. -*/ - void coroutine_fn (*resume)(BlockJob *job); -/* * If the callback is not NULL, it will be invoked before the job is * resumed in a new AioContext. This is the place to move any resources
@@ -196,15 +179,6 @@ void block_job_early_fail(BlockJob *job);void block_job_completed (BlockJob *job,int ret);/** -* block_job_pause_point: -* @job: The job that is ready to pause. -* -* Pause now if block_job_pause() has been called. Block jobs that perform -* lots of I/O must call this between requests so that the job can be paused. -*/ -void coroutine_fn block_job_pause_point(BlockJob *job); - -/** * block_job_enter: * @job: The job to enter. *
include/qemu/job.h +76 -0
105 lines not on this page · raw message
block/backup.c +1 -1
@@ -528,8 +528,8 @@ static const BlockJobDriver backup_job_driver = { .instance_size = sizeof(BackupBlockJob), .job_type = JOB_TYPE_BACKUP, .free = block_job_free, + .start = backup_run, }, - .start = backup_run, .commit = backup_commit, .abort = backup_abort, .clean = backup_clean,
block/commit.c +2 -2
@@ -220,8 +220,8 @@ static const BlockJobDriver commit_job_driver = { .instance_size = sizeof(CommitBlockJob), .job_type = JOB_TYPE_COMMIT, .free = block_job_free, + .start = commit_run, }, - .start = commit_run, }; static int coroutine_fn bdrv_commit_top_preadv(BlockDriverState *bs,
@@ -371,7 +371,7 @@ void commit_start(const char *job_id, BlockDriverState *bs, s->on_error = on_error;trace_commit_start (bs, base, top, s); -block_job_start (&s->common); +job_start (&s->common.job);return ; fail:
block/mirror.c +11 -11
@@ -126,7 +126,7 @@ static void mirror_iteration_done(MirrorOp *op, int ret)g_free (op);if (s->waiting_for_io) { -qemu_coroutine_enter (s->common.co); +qemu_coroutine_enter (s->common.job.co); } }
@@ -345,7 +345,7 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)mirror_wait_for_io (s); } -block_job_pause_point (&s->common); +job_pause_point (&s->common.job);/* Find the number of consective dirty chunks following the first dirty * one, and wait for in flight requests in them. */
@@ -597,7 +597,7 @@ static void mirror_throttle(MirrorBlockJob *s) s->last_pause_ns = now;block_job_sleep_ns (&s->common,0 ); }else { -block_job_pause_point (&s->common); +job_pause_point (&s->common.job); } }
@@ -786,7 +786,7 @@ static void coroutine_fn mirror_run(void *opaque) goto immediate_exit; } -block_job_pause_point (&s->common); +job_pause_point (&s->common.job); cnt =bdrv_get_dirty_count (s->dirty_bitmap);/* cnt is the number of dirty bytes remaining and s->bytes_in_flight is
@@ -957,9 +957,9 @@ static void mirror_complete(BlockJob *job, Error **errp)block_job_enter (&s->common); } -static void mirror_pause (BlockJob *job) +static void mirror_pause (Job *job) { -MirrorBlockJob *s =container_of (job, MirrorBlockJob, common); +MirrorBlockJob *s =container_of (job, MirrorBlockJob, common.job);mirror_wait_for_all_io (s); }
@@ -991,10 +991,10 @@ static const BlockJobDriver mirror_job_driver = { .instance_size = sizeof(MirrorBlockJob), .job_type = JOB_TYPE_MIRROR, .free = block_job_free, + .start = mirror_run, + .pause = mirror_pause, }, - .start = mirror_run, .complete = mirror_complete, - .pause = mirror_pause, .attached_aio_context = mirror_attached_aio_context, .drain = mirror_drain, };
@@ -1004,10 +1004,10 @@ static const BlockJobDriver commit_active_job_driver = { .instance_size = sizeof(MirrorBlockJob), .job_type = JOB_TYPE_COMMIT, .free = block_job_free, + .start = mirror_run, + .pause = mirror_pause, }, - .start = mirror_run, .complete = mirror_complete, - .pause = mirror_pause, .attached_aio_context = mirror_attached_aio_context, .drain = mirror_drain, };
@@ -1244,7 +1244,7 @@ static void mirror_start_job(const char *job_id, BlockDriverState *bs, }trace_mirror_start (bs, s, opaque); -block_job_start (&s->common); +job_start (&s->common.job);return ; fail:
block/replication.c +1 -1
@@ -576,7 +576,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,aio_context_release (aio_context);return ; } -block_job_start (job); +job_start (&job->job);break ;default :aio_context_release (aio_context);
block/stream.c +2 -2
@@ -213,8 +213,8 @@ static const BlockJobDriver stream_job_driver = { .instance_size = sizeof(StreamBlockJob), .job_type = JOB_TYPE_STREAM, .free = block_job_free, + .start = stream_run, }, - .start = stream_run, }; void stream_start(const char *job_id, BlockDriverState *bs,
@@ -262,7 +262,7 @@ void stream_start(const char *job_id, BlockDriverState *bs, s->on_error = on_error;trace_stream_start (bs, base, s); -block_job_start (&s->common); +job_start (&s->common.job);return ; fail:
blockdev.c +4 -4
@@ -1910,7 +1910,7 @@ static void drive_backup_commit(BlkActionState *common)aio_context_acquire (aio_context);assert (state->job); -block_job_start (state->job); +job_start (&state->job->job);aio_context_release (aio_context); }
@@ -2008,7 +2008,7 @@ static void blockdev_backup_commit(BlkActionState *common)aio_context_acquire (aio_context);assert (state->job); -block_job_start (state->job); +job_start (&state->job->job);aio_context_release (aio_context); }
@@ -3425,7 +3425,7 @@ void qmp_drive_backup(DriveBackup *arg, Error **errp)BlockJob *job; job =do_drive_backup (arg,NULL , errp);if (job) { -block_job_start (job); +job_start (&job->job); } }
@@ -3513,7 +3513,7 @@ void qmp_blockdev_backup(BlockdevBackup *arg, Error **errp)BlockJob *job; job =do_blockdev_backup (arg,NULL , errp);if (job) { -block_job_start (job); +job_start (&job->job); } }
blockjob.c +39 -180
386 lines not on this page · raw message
job.c +137 -0
161 lines not on this page · raw message
tests/test-bdrv-drain.c +19 -19
@@ -524,8 +524,8 @@ BlockJobDriver test_job_driver = { .job_driver = { .instance_size = sizeof(TestBlockJob), .free = block_job_free, + .start = test_job_start, }, - .start = test_job_start, .complete = test_job_complete, };
@@ -549,47 +549,47 @@ static void test_blockjob_common(enum drain_type drain_type) job =block_job_create ("job0" , &test_job_driver,NULL , src,0 ,BLK_PERM_ALL ,0 ,0 ,NULL ,NULL , &error_abort);block_job_add_bdrv (job,"target" , target,0 ,BLK_PERM_ALL , &error_abort); -block_job_start (job); +job_start (&job->job); - g_assert_cmpint(job->pause_count, ==, 0); -g_assert_false (job->paused); -g_assert_false (job->busy);/* We're in block_job_sleep_ns() */ + g_assert_cmpint(job->job.pause_count, ==, 0); +g_assert_false (job->job.paused); +g_assert_false (job->job.busy);/* We're in block_job_sleep_ns() */ do_drain_begin (drain_type, src);if (drain_type ==BDRV_DRAIN_ALL ) {/* bdrv_drain_all() drains both src and target */ - g_assert_cmpint(job->pause_count, ==, 2); + g_assert_cmpint(job->job.pause_count, ==, 2); }else { - g_assert_cmpint(job->pause_count, ==, 1); + g_assert_cmpint(job->job.pause_count, ==, 1); }/* XXX We don't wait until the job is actually paused. Is this okay? */ -/* g_assert_true(job->paused); */ -g_assert_false (job->busy);/* The job is paused */ +/* g_assert_true(job->job.paused); */ +g_assert_false (job->job.busy);/* The job is paused */ do_drain_end (drain_type, src); - g_assert_cmpint(job->pause_count, ==, 0); -g_assert_false (job->paused); -g_assert_false (job->busy);/* We're in block_job_sleep_ns() */ + g_assert_cmpint(job->job.pause_count, ==, 0); +g_assert_false (job->job.paused); +g_assert_false (job->job.busy);/* We're in block_job_sleep_ns() */ do_drain_begin (drain_type, target);if (drain_type ==BDRV_DRAIN_ALL ) {/* bdrv_drain_all() drains both src and target */ - g_assert_cmpint(job->pause_count, ==, 2); + g_assert_cmpint(job->job.pause_count, ==, 2); }else { - g_assert_cmpint(job->pause_count, ==, 1); + g_assert_cmpint(job->job.pause_count, ==, 1); }/* XXX We don't wait until the job is actually paused. Is this okay? */ -/* g_assert_true(job->paused); */ -g_assert_false (job->busy);/* The job is paused */ +/* g_assert_true(job->job.paused); */ +g_assert_false (job->job.busy);/* The job is paused */ do_drain_end (drain_type, target); - g_assert_cmpint(job->pause_count, ==, 0); -g_assert_false (job->paused); -g_assert_false (job->busy);/* We're in block_job_sleep_ns() */ + g_assert_cmpint(job->job.pause_count, ==, 0); +g_assert_false (job->job.paused); +g_assert_false (job->job.busy);/* We're in block_job_sleep_ns() */ ret =block_job_complete_sync (job, &error_abort); g_assert_cmpint(ret, ==, 0);
tests/test-blockjob-txn.c +6 -6
@@ -78,8 +78,8 @@ static const BlockJobDriver test_block_job_driver = { .job_driver = { .instance_size = sizeof(TestBlockJob), .free = block_job_free, + .start = test_block_job_run, }, - .start = test_block_job_run, };/* Create a block job that completes with a given return code after a given
@@ -125,7 +125,7 @@ static void test_single_job(int expected) txn =block_job_txn_new (); job =test_block_job_start (1 , true, expected, &result, txn); -block_job_start (job); +job_start (&job->job);if (expected == -ECANCELED ) {block_job_cancel (job, false);
@@ -165,8 +165,8 @@ static void test_pair_jobs(int expected1, int expected2) txn =block_job_txn_new (); job1 =test_block_job_start (1 , true, expected1, &result1, txn); job2 =test_block_job_start (2 , true, expected2, &result2, txn); -block_job_start (job1); -block_job_start (job2); +job_start (&job1->job); +job_start (&job2->job);/* Release our reference now to trigger as many nice * use-after-free bugs as possible.
@@ -227,8 +227,8 @@ static void test_pair_jobs_fail_cancel_race(void) txn =block_job_txn_new (); job1 =test_block_job_start (1 , true, -ECANCELED , &result1, txn); job2 =test_block_job_start (2 , false,0 , &result2, txn); -block_job_start (job1); -block_job_start (job2); +job_start (&job1->job); +job_start (&job2->job);block_job_cancel (job1, false);
tests/test-blockjob.c +7 -7
@@ -199,8 +199,8 @@ static const BlockJobDriver test_cancel_driver = { .job_driver = { .instance_size = sizeof(CancelJob), .free = block_job_free, + .start = cancel_job_start, }, - .start = cancel_job_start, .complete = cancel_job_complete, };
@@ -254,7 +254,7 @@ static void test_cancel_running(void) s =create_common (&job); -block_job_start (job); +job_start (&job->job);assert (job->job.status ==JOB_STATUS_RUNNING );cancel_common (s);
@@ -267,7 +267,7 @@ static void test_cancel_paused(void) s =create_common (&job); -block_job_start (job); +job_start (&job->job);assert (job->job.status ==JOB_STATUS_RUNNING );block_job_user_pause (job, &error_abort);
@@ -284,7 +284,7 @@ static void test_cancel_ready(void) s =create_common (&job); -block_job_start (job); +job_start (&job->job);assert (job->job.status ==JOB_STATUS_RUNNING ); s->should_converge = true;
@@ -301,7 +301,7 @@ static void test_cancel_standby(void) s =create_common (&job); -block_job_start (job); +job_start (&job->job);assert (job->job.status ==JOB_STATUS_RUNNING ); s->should_converge = true;
@@ -322,7 +322,7 @@ static void test_cancel_pending(void) s =create_common (&job); -block_job_start (job); +job_start (&job->job);assert (job->job.status ==JOB_STATUS_RUNNING ); s->should_converge = true;
@@ -346,7 +346,7 @@ static void test_cancel_concluded(void) s =create_common (&job); -block_job_start (job); +job_start (&job->job);assert (job->job.status ==JOB_STATUS_RUNNING ); s->should_converge = true;
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH v2 13/40] job: Move coroutine and related code to Job
Quoted 6 lines · Kevin Wolf
This commit moves some core functions for dealing with the job coroutine from BlockJob to Job. This includes primarily entering the coroutine (both for the first and reentering) and yielding explicitly and at pause points.
Signed-off-by Kevin Wolf <[email protected]>
The _cond functions get shuffled around a bit, you address Max's comments and otherwise it's the same as in V1.
PATCH v2 14/40 job: Add job_sleep_ns()
In reply to Kevin WolfThere is nothing block layer specific about block_job_sleep_ns(), so move the function to Job.
--- include/block/blockjob_int.h | 11 ----------- include/qemu/job.h | 19 ++++++++++++++++++- block/backup.c | 2 +- block/commit.c | 2 +- block/mirror.c | 4 ++-- block/stream.c | 2 +- blockjob.c | 27 --------------------------- job.c | 32 ++++++++++++++++++++++++++++++++ tests/test-bdrv-drain.c | 8 ++++---- tests/test-blockjob-txn.c | 2 +- tests/test-blockjob.c | 2 +- 11 files changed, 61 insertions(+), 50 deletions(-)
include/block/blockjob_int.h +0 -11
@@ -134,17 +134,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,void block_job_free (Job *job);/** -* block_job_sleep_ns: -* @job: The job that calls the function. -* @ns: How many nanoseconds to stop for. -* -* Put the job to sleep (assuming that it wasn't canceled) for @ns -* %QEMU_CLOCK_REALTIME nanoseconds. Canceling the job will immediately -* interrupt the wait. -*/ -void block_job_sleep_ns (BlockJob *job,int64_t ns); - -/** * block_job_yield: * @job: The job that calls the function. *
include/qemu/job.h +18 -1
@@ -58,7 +58,7 @@ typedef struct Job {Coroutine *co;/** -* Timer that is used by @block_job_sleep_ns. Accessed under job_mutex (in +* Timer that is used by @job_sleep_ns. Accessed under job_mutex (in * job.c). */ QEMUTimer sleep_timer;
@@ -168,6 +168,13 @@ void job_enter_cond(Job *job, bool(*fn)(Job *job));void job_start (Job *job);/** +* @job: The job to enter. +* +* Continue the specified job by entering the coroutine. +*/ +void job_enter (Job *job); + +/** * @job: The job that is ready to pause. * * Pause now if job_pause() has been called. Jobs that perform lots of I/O
@@ -175,6 +182,16 @@ void job_start(Job *job);*/ void coroutine_fn job_pause_point(Job *job); +/** +* @job: The job that calls the function. +* @ns: How many nanoseconds to stop for. +* +* Put the job to sleep (assuming that it wasn't canceled) for @ns +* %QEMU_CLOCK_REALTIME nanoseconds. Canceling the job will immediately +* interrupt the wait. +*/ +void coroutine_fn job_sleep_ns(Job *job, int64_t ns); +/** Returns the JobType of a given Job. */ JobType job_type (const Job *job);
block/backup.c +1 -1
@@ -338,7 +338,7 @@ static bool coroutine_fn yield_and_check(BackupBlockJob *job)* return. Without a yield, the VM would not reboot. */ delay_ns =block_job_ratelimit_get_delay (&job->common, job->bytes_read); job->bytes_read =0 ; -block_job_sleep_ns (&job->common, delay_ns); +job_sleep_ns (&job->common.job, delay_ns);if (job_is_cancelled (&job->common.job)) {return true;
block/commit.c +1 -1
@@ -172,7 +172,7 @@ static void coroutine_fn commit_run(void *opaque)/* Note that even when no rate limit is applied we need to yield * with no pending I/O here so that bdrv_drain_all() returns. */ -block_job_sleep_ns (&s->common, delay_ns); +job_sleep_ns (&s->common.job, delay_ns);if (job_is_cancelled (&s->common.job)) {break ; }
block/mirror.c +2 -2
@@ -595,7 +595,7 @@ static void mirror_throttle(MirrorBlockJob *s)if (now - s->last_pause_ns >BLOCK_JOB_SLICE_TIME ) { s->last_pause_ns = now; -block_job_sleep_ns (&s->common,0 ); +job_sleep_ns (&s->common.job,0 ); }else {job_pause_point (&s->common.job); }
@@ -869,7 +869,7 @@ static void coroutine_fn mirror_run(void *opaque) cnt == 0 ? BLOCK_JOB_SLICE_TIME : 0); }trace_mirror_before_sleep (s, cnt, s->synced, delay_ns); -block_job_sleep_ns (&s->common, delay_ns); +job_sleep_ns (&s->common.job, delay_ns);if (job_is_cancelled (&s->common.job) && (!s->synced || s->common.force)) {
block/stream.c +1 -1
@@ -140,7 +140,7 @@ static void coroutine_fn stream_run(void *opaque)/* Note that even when no rate limit is applied we need to yield * with no pending I/O here so that bdrv_drain_all() returns. */ -block_job_sleep_ns (&s->common, delay_ns); +job_sleep_ns (&s->common.job, delay_ns);if (job_is_cancelled (&s->common.job)) {break ; }
blockjob.c +0 -27
@@ -181,7 +181,6 @@ void block_job_free(Job *job) block_job_detach_aio_context, bjob);blk_unref (bjob->blk);error_free (bjob->blocker); -assert (!timer_pending (&bjob->job.sleep_timer)); } static void block_job_attached_aio_context(AioContext *new_context,
@@ -290,13 +289,6 @@ const BlockJobDriver *block_job_driver(BlockJob *job)return job->driver; } -static void block_job_sleep_timer_cb (void *opaque) -{ -BlockJob *job = opaque; - -block_job_enter (job); -} -static void block_job_decommission (BlockJob *job) {assert (job);
@@ -866,9 +858,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, job->opaque = opaque; job->auto_finalize = !(flags &BLOCK_JOB_MANUAL_FINALIZE ); job->auto_dismiss = !(flags &BLOCK_JOB_MANUAL_DISMISS ); -aio_timer_init (qemu_get_aio_context (), &job->job.sleep_timer, -QEMU_CLOCK_REALTIME ,SCALE_NS , - block_job_sleep_timer_cb, job);error_setg (&job->blocker,"block device is in use by block job: %s" ,job_type_str (&job->job));
@@ -931,22 +920,6 @@ void block_job_enter(BlockJob *job)job_enter_cond (&job->job,NULL ); } -void block_job_sleep_ns (BlockJob *job,int64_t ns) -{ -assert (job->job.busy); - -/* Check cancellation *before* setting busy = false, too! */ -if (job_is_cancelled (&job->job)) { -return ; - } - -if (!job_should_pause (&job->job)) { -job_do_yield (&job->job,qemu_clock_get_ns (QEMU_CLOCK_REALTIME ) + ns); - } - -job_pause_point (&job->job); -} -void block_job_yield (BlockJob *job) {assert (job->job.busy);
job.c +32 -0
@@ -152,6 +152,13 @@ Job *job_get(const char *id)return NULL ; } +static void job_sleep_timer_cb (void *opaque) +{ +Job *job = opaque; + +job_enter (job); +} +void *job_create (const char *job_id,const JobDriver *driver,AioContext *ctx,Error **errp) {
@@ -178,6 +185,9 @@ void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx, job->pause_count =1 ;job_state_transition (job,JOB_STATUS_CREATED ); +aio_timer_init (qemu_get_aio_context (), &job->sleep_timer, +QEMU_CLOCK_REALTIME ,SCALE_NS , + job_sleep_timer_cb, job);QLIST_INSERT_HEAD (&jobs, job, job_list);
@@ -193,6 +203,7 @@ void job_unref(Job *job) {if (--job->refcnt ==0 ) {assert (job->status ==JOB_STATUS_NULL ); +assert (!timer_pending (&job->sleep_timer));if (job->driver->free) { job->driver->free (job);
@@ -232,6 +243,11 @@ void job_enter_cond(Job *job, bool(*fn)(Job *job))aio_co_wake (job->co); } +void job_enter (Job *job) +{ +job_enter_cond (job,NULL ); +} +/* Yield, and schedule a timer to reenter the coroutine after @ns nanoseconds. * Reentering the job coroutine with block_job_enter() before the timer has * expired is allowed and cancels the timer.
@@ -283,6 +299,22 @@ void coroutine_fn job_pause_point(Job *job) } } +void coroutine_fn job_sleep_ns(Job *job, int64_t ns) +{ +assert (job->busy); + +/* Check cancellation *before* setting busy = false, too! */ +if (job_is_cancelled (job)) { +return ; + } + +if (!job_should_pause (job)) { +job_do_yield (job,qemu_clock_get_ns (QEMU_CLOCK_REALTIME ) + ns); + } + +job_pause_point (job); +} +/** * All jobs must allow a pause point before entering their job proper. This * ensures that jobs can be paused prior to being started, then resumed later.
tests/test-bdrv-drain.c +4 -4
@@ -508,7 +508,7 @@ static void coroutine_fn test_job_start(void *opaque)block_job_event_ready (&s->common);while (!s->should_complete) { -block_job_sleep_ns (&s->common,100000 ); +job_sleep_ns (&s->common.job,100000 ); }job_defer_to_main_loop (&s->common.job, test_job_completed,NULL );
@@ -553,7 +553,7 @@ static void test_blockjob_common(enum drain_type drain_type) g_assert_cmpint(job->job.pause_count, ==, 0);g_assert_false (job->job.paused); -g_assert_false (job->job.busy);/* We're in block_job_sleep_ns() */ +g_assert_false (job->job.busy);/* We're in job_sleep_ns() */ do_drain_begin (drain_type, src);
@@ -571,7 +571,7 @@ static void test_blockjob_common(enum drain_type drain_type) g_assert_cmpint(job->job.pause_count, ==, 0);g_assert_false (job->job.paused); -g_assert_false (job->job.busy);/* We're in block_job_sleep_ns() */ +g_assert_false (job->job.busy);/* We're in job_sleep_ns() */ do_drain_begin (drain_type, target);
@@ -589,7 +589,7 @@ static void test_blockjob_common(enum drain_type drain_type) g_assert_cmpint(job->job.pause_count, ==, 0);g_assert_false (job->job.paused); -g_assert_false (job->job.busy);/* We're in block_job_sleep_ns() */ +g_assert_false (job->job.busy);/* We're in job_sleep_ns() */ ret =block_job_complete_sync (job, &error_abort); g_assert_cmpint(ret, ==, 0);
tests/test-blockjob-txn.c +1 -1
@@ -45,7 +45,7 @@ static void coroutine_fn test_block_job_run(void *opaque)while (s->iterations--) {if (s->use_timer) { -block_job_sleep_ns (job,0 ); +job_sleep_ns (&job->job,0 ); }else {block_job_yield (job); }
tests/test-blockjob.c +1 -1
@@ -188,7 +188,7 @@ static void coroutine_fn cancel_job_start(void *opaque)block_job_event_ready (&s->common); } -block_job_sleep_ns (&s->common,100000 ); +job_sleep_ns (&s->common.job,100000 ); } defer:
Signature · 1 line
2.13.6
PATCH v2 15/40 job: Move pause/resume functions to Job
In reply to Kevin WolfWhile we already moved the state related to job pausing to Job, the functions to do were still BlockJob only. This commit moves them over to Job.
--- include/block/blockjob.h | 32 ----------------- include/block/blockjob_int.h | 7 ++++ include/qemu/job.h | 37 ++++++++++++++++++++ block/backup.c | 1 + block/commit.c | 1 + block/mirror.c | 2 ++ block/stream.c | 1 + blockdev.c | 6 ++-- blockjob.c | 81 +++++++++----------------------------------- job.c | 59 ++++++++++++++++++++++++++++++++ tests/test-bdrv-drain.c | 1 + tests/test-blockjob-txn.c | 1 + tests/test-blockjob.c | 6 ++-- 13 files changed, 133 insertions(+), 102 deletions(-)
include/block/blockjob.h +0 -32
@@ -57,12 +57,6 @@ typedef struct BlockJob {bool force;/** -* Set to true if the job is paused by user. Can be unpaused with the -* block-job-resume QMP command. -*/ -bool user_paused; - -/** * Set to true when the job is ready to be completed. */ bool ready;
@@ -248,32 +242,6 @@ void block_job_progress_set_remaining(BlockJob *job, uint64_t remaining);BlockJobInfo *block_job_query (BlockJob *job,Error **errp);/** -* block_job_user_pause: -* @job: The job to be paused. -* -* Asynchronously pause the specified job. -* Do not allow a resume until a matching call to block_job_user_resume. -*/ -void block_job_user_pause (BlockJob *job,Error **errp); - -/** -* block_job_paused: -* @job: The job to query. -* -* Returns true if the job is user-paused. -*/ -bool block_job_user_paused (BlockJob *job); - -/** -* block_job_user_resume: -* @job: The job to be resumed. -* -* Resume the specified job. -* Must be paired with a preceding block_job_user_pause. -*/ -void block_job_user_resume (BlockJob *job,Error **errp); - -/** * block_job_user_cancel: * @job: The job to be cancelled. * @force: Quit a job without waiting for data to be in sync.
include/block/blockjob_int.h +7 -0
@@ -134,6 +134,13 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,void block_job_free (Job *job);/** +* block_job_user_resume: +* Callback to be used for JobDriver.user_resume in all block jobs. Resets the +* iostatus when the user resumes @job. +*/ +void block_job_user_resume (Job *job); + +/** * block_job_yield: * @job: The job that calls the function. *
include/qemu/job.h +37 -0
@@ -83,6 +83,12 @@ typedef struct Job {bool paused;/** +* Set to true if the job is paused by user. Can be unpaused with the +* block-job-resume QMP command. +*/ +bool user_paused; + +/** * Set to true if the job should cancel itself. The flag must * always be tested just before toggling the busy flag from false * to true. After a job has been cancelled, it should only yield
@@ -124,6 +130,12 @@ struct JobDriver {*/ void coroutine_fn (*resume)(Job *job); +/** +* Called when the job is resumed by the user (i.e. user_paused becomes +* false). .user_resume is called before .resume. +*/ +void (*user_resume)(Job *job); +/** Called when the job is freed */ void (*free)(Job *job); };
@@ -203,6 +215,31 @@ const char *job_type_str(const Job *job);bool job_is_cancelled (Job *job);/** +* Request @job to pause at the next pause point. Must be paired with +* job_resume(). If the job is supposed to be resumed by user action, call +* job_user_pause() instead. +*/ +void job_pause (Job *job); + +/** Resumes a @job paused with job_pause. */ +void job_resume (Job *job); + +/** +* Asynchronously pause the specified @job. +* Do not allow a resume until a matching call to job_user_resume. +*/ +void job_user_pause (Job *job,Error **errp); + +/** Returns true if the job is user-paused. */ +bool job_user_paused (Job *job); + +/** +* Resume the specified @job. +* Must be paired with a preceding job_user_pause. +*/ +void job_user_resume (Job *job,Error **errp); + +/** * Get the next element from the list of block jobs after @job, or the * first one if @job is %NULL. *
block/backup.c +1 -0
@@ -528,6 +528,7 @@ static const BlockJobDriver backup_job_driver = { .instance_size = sizeof(BackupBlockJob), .job_type = JOB_TYPE_BACKUP, .free = block_job_free, + .user_resume = block_job_user_resume, .start = backup_run, }, .commit = backup_commit,
block/commit.c +1 -0
@@ -220,6 +220,7 @@ static const BlockJobDriver commit_job_driver = { .instance_size = sizeof(CommitBlockJob), .job_type = JOB_TYPE_COMMIT, .free = block_job_free, + .user_resume = block_job_user_resume, .start = commit_run, }, };
block/mirror.c +2 -0
@@ -991,6 +991,7 @@ static const BlockJobDriver mirror_job_driver = { .instance_size = sizeof(MirrorBlockJob), .job_type = JOB_TYPE_MIRROR, .free = block_job_free, + .user_resume = block_job_user_resume, .start = mirror_run, .pause = mirror_pause, },
@@ -1004,6 +1005,7 @@ static const BlockJobDriver commit_active_job_driver = { .instance_size = sizeof(MirrorBlockJob), .job_type = JOB_TYPE_COMMIT, .free = block_job_free, + .user_resume = block_job_user_resume, .start = mirror_run, .pause = mirror_pause, },
block/stream.c +1 -0
@@ -214,6 +214,7 @@ static const BlockJobDriver stream_job_driver = { .job_type = JOB_TYPE_STREAM, .free = block_job_free, .start = stream_run, + .user_resume = block_job_user_resume, }, };
blockdev.c +3 -3
@@ -3844,7 +3844,7 @@ void qmp_block_job_cancel(const char *device, force = false; } -if (block_job_user_paused (job) && !force) { +if (job_user_paused (&job->job) && !force) {error_setg (errp,"The block job for device '%s' is currently paused" , device); goto out;
@@ -3866,7 +3866,7 @@ void qmp_block_job_pause(const char *device, Error **errp) }trace_qmp_block_job_pause (job); -block_job_user_pause (job, errp); +job_user_pause (&job->job, errp);aio_context_release (aio_context); }
@@ -3880,7 +3880,7 @@ void qmp_block_job_resume(const char *device, Error **errp) }trace_qmp_block_job_resume (job); -block_job_user_resume (job, errp); +job_user_resume (&job->job, errp);aio_context_release (aio_context); }
blockjob.c +16 -65
145 lines not on this page · raw message
job.c +59 -0
65 lines not on this page · raw message
tests/test-bdrv-drain.c +1 -0
@@ -524,6 +524,7 @@ BlockJobDriver test_job_driver = { .job_driver = { .instance_size = sizeof(TestBlockJob), .free = block_job_free, + .user_resume = block_job_user_resume, .start = test_job_start, }, .complete = test_job_complete,
tests/test-blockjob-txn.c +1 -0
@@ -78,6 +78,7 @@ static const BlockJobDriver test_block_job_driver = { .job_driver = { .instance_size = sizeof(TestBlockJob), .free = block_job_free, + .user_resume = block_job_user_resume, .start = test_block_job_run, }, };
tests/test-blockjob.c +4 -2
@@ -20,6 +20,7 @@ static const BlockJobDriver test_block_job_driver = { .job_driver = { .instance_size = sizeof(BlockJob), .free = block_job_free, + .user_resume = block_job_user_resume, }, };
@@ -199,6 +200,7 @@ static const BlockJobDriver test_cancel_driver = { .job_driver = { .instance_size = sizeof(CancelJob), .free = block_job_free, + .user_resume = block_job_user_resume, .start = cancel_job_start, }, .complete = cancel_job_complete,
@@ -270,7 +272,7 @@ static void test_cancel_paused(void)job_start (&job->job);assert (job->job.status ==JOB_STATUS_RUNNING ); -block_job_user_pause (job, &error_abort); +job_user_pause (&job->job, &error_abort);block_job_enter (job);assert (job->job.status ==JOB_STATUS_PAUSED );
@@ -308,7 +310,7 @@ static void test_cancel_standby(void)block_job_enter (job);assert (job->job.status ==JOB_STATUS_READY ); -block_job_user_pause (job, &error_abort); +job_user_pause (&job->job, &error_abort);block_job_enter (job);assert (job->job.status ==JOB_STATUS_STANDBY );
Signature · 1 line
2.13.6
PATCH v2 16/40 job: Replace BlockJob.completed with job_is_completed()
In reply to Kevin WolfSince we introduced an explicit status to block job, BlockJob.completed is redundant because it can be derived from the status. Remove the field from BlockJob and add a function to derive it from the status at the Job level.
--- include/block/blockjob.h | 3 --- include/qemu/job.h | 3 +++ blockjob.c | 16 +++++++--------- job.c | 22 ++++++++++++++++++++++ qemu-img.c | 4 ++-- 5 files changed, 34 insertions(+), 14 deletions(-)
include/block/blockjob.h +0 -3
@@ -88,9 +88,6 @@ typedef struct BlockJob {/** The opaque value that is passed to the completion function. */ void *opaque; -/** True when job has reported completion by calling block_job_completed. */ -bool completed; -/** ret code passed to block_job_completed. */ int ret;
include/qemu/job.h +3 -0
@@ -214,6 +214,9 @@ const char *job_type_str(const Job *job);/** Returns whether the job is scheduled for cancellation. */ bool job_is_cancelled (Job *job); +/** Returns whether the job is in a completed state. */ +bool job_is_completed (Job *job); +/** * Request @job to pause at the next pause point. Must be paired with * job_resume(). If the job is supposed to be resumed by user action, call
blockjob.c +7 -9
@@ -193,7 +193,7 @@ static void block_job_detach_aio_context(void *opaque)job_pause (&job->job); -while (!job->job.paused && !job->completed) { +while (!job->job.paused && !job_is_completed (&job->job)) {block_job_drain (job); }
@@ -269,7 +269,6 @@ const BlockJobDriver *block_job_driver(BlockJob *job)static void block_job_decommission (BlockJob *job) {assert (job); - job->completed = true; job->job.busy = false; job->job.paused = false; job->job.deferred_to_main_loop = true;
@@ -334,7 +333,7 @@ static void block_job_clean(BlockJob *job)static int block_job_finalize_single (BlockJob *job) { -assert (job->completed); +assert (job_is_completed (&job->job));/* Ensure abort is called for late-transactional failures */ block_job_update_rc (job);
@@ -427,10 +426,10 @@ static int block_job_finish_sync(BlockJob *job,/* block_job_drain calls block_job_enter, and it should be enough to * induce progress until the job completes or moves to the main thread. */ -while (!job->job.deferred_to_main_loop && !job->completed) { +while (!job->job.deferred_to_main_loop && !job_is_completed (&job->job)) {block_job_drain (job); } -while (!job->completed) { +while (!job_is_completed (&job->job)) {aio_poll (qemu_get_aio_context (), true); } ret = (job_is_cancelled (&job->job) && job->ret ==0 )
@@ -471,7 +470,7 @@ static void block_job_completed_txn_abort(BlockJob *job)while (!QLIST_EMPTY (&txn->jobs)) { other_job =QLIST_FIRST (&txn->jobs); ctx =blk_get_aio_context (other_job->blk); -if (!other_job->completed) { +if (!job_is_completed (&other_job->job)) {assert (job_is_cancelled (&other_job->job));block_job_finish_sync (other_job,NULL ,NULL ); }
@@ -513,7 +512,7 @@ static void block_job_completed_txn_success(BlockJob *job)* txn. */ QLIST_FOREACH(other_job, &txn->jobs, txn_list) { -if (!other_job->completed) { +if (!job_is_completed (&other_job->job)) {return ; }assert (other_job->ret ==0 );
@@ -847,9 +846,8 @@ void block_job_early_fail(BlockJob *job)void block_job_completed (BlockJob *job,int ret) { -assert (job && job->txn && !job->completed); +assert (job && job->txn && !job_is_completed (&job->job));assert (blk_bs (job->blk)->job == job); - job->completed = true; job->ret = ret;block_job_update_rc (job);trace_block_job_completed (job, ret, job->ret);
job.c +22 -0
@@ -121,6 +121,28 @@ bool job_is_cancelled(Job *job)return job->cancelled; } +bool job_is_completed (Job *job) +{ +switch (job->status) { +case JOB_STATUS_UNDEFINED : +case JOB_STATUS_CREATED : +case JOB_STATUS_RUNNING : +case JOB_STATUS_PAUSED : +case JOB_STATUS_READY : +case JOB_STATUS_STANDBY : +return false; +case JOB_STATUS_WAITING : +case JOB_STATUS_PENDING : +case JOB_STATUS_ABORTING : +case JOB_STATUS_CONCLUDED : +case JOB_STATUS_NULL : +return true; +default : +g_assert_not_reached (); + } +return false; +} +bool job_started (Job *job) {return job->co;
qemu-img.c +2 -2
@@ -867,9 +867,9 @@ static void run_block_job(BlockJob *job, Error **errp)aio_poll (aio_context, true);qemu_progress_print (job->len ? ((float )job->offset / job->len *100.f ) :0.0f ,0 ); - }while (!job->ready && !job->completed); + }while (!job->ready && !job_is_completed (&job->job)); -if (!job->completed) { +if (!job_is_completed (&job->job)) { ret =block_job_complete_sync (job, errp); }else { ret = job->ret;
Signature · 1 line
2.13.6
PATCH v2 17/40 job: Move BlockJobCreateFlags to Job
In reply to Kevin WolfThis renames the BlockJobCreateFlags constants, moves a few JOB_INTERNAL checks to job_create() and the auto_{finalize,dismiss} fields from BlockJob to Job.
--- include/block/blockjob.h | 17 ----------------- include/block/blockjob_int.h | 3 +-- include/qemu/job.h | 20 +++++++++++++++++++- block/commit.c | 2 +- block/mirror.c | 2 +- block/replication.c | 4 ++-- block/stream.c | 2 +- blockdev.c | 14 +++++++------- blockjob.c | 27 +++++++-------------------- job.c | 11 ++++++++++- qemu-img.c | 2 +- tests/test-blockjob-txn.c | 2 +- tests/test-blockjob.c | 4 ++-- 13 files changed, 53 insertions(+), 57 deletions(-)
include/block/blockjob.h +0 -17
@@ -91,27 +91,10 @@ typedef struct BlockJob {/** ret code passed to block_job_completed. */ int ret; -/** True if this job should automatically finalize itself */ -bool auto_finalize; - -/** True if this job should automatically dismiss itself */ -bool auto_dismiss; -BlockJobTxn *txn;QLIST_ENTRY (BlockJob) txn_list; } BlockJob; -typedef enum BlockJobCreateFlags { -/* Default behavior */ -BLOCK_JOB_DEFAULT =0x00 , -/* BlockJob is not QMP-created and should not send QMP events */ -BLOCK_JOB_INTERNAL =0x01 , -/* BlockJob requires manual finalize step */ -BLOCK_JOB_MANUAL_FINALIZE =0x02 , -/* BlockJob requires manual dismiss step */ -BLOCK_JOB_MANUAL_DISMISS =0x04 , -}BlockJobCreateFlags ; -/** * block_job_next: * @job: A block job, or %NULL.
include/block/blockjob_int.h +1 -2
@@ -106,8 +106,7 @@ struct BlockJobDriver {* @bs: The block * @perm, @shared_perm: Permissions to request for @bs * @speed: The maximum speed, in bytes per second, or 0 for unlimited. -* @flags: Creation flags for the Block Job. -* See @BlockJobCreateFlags +* @flags: Creation flags for the Block Job. See @JobCreateFlags. * @cb: Completion function for the job. * @opaque: Opaque pointer value passed to @cb. * @errp: Error object.
include/qemu/job.h +19 -1
@@ -99,6 +99,12 @@ typedef struct Job {/** Set to true when the job has deferred work to the main loop. */ bool deferred_to_main_loop; +/** True if this job should automatically finalize itself */ +bool auto_finalize; + +/** True if this job should automatically dismiss itself */ +bool auto_dismiss; +/** Element of the list of jobs */ QLIST_ENTRY (Job) job_list; } Job;
@@ -140,6 +146,17 @@ struct JobDriver {void (*free)(Job *job); }; +typedef enum JobCreateFlags { +/* Default behavior */ +JOB_DEFAULT =0x00 , +/* Job is not QMP-created and should not send QMP events */ +JOB_INTERNAL =0x01 , +/* Job requires manual finalize step */ +JOB_MANUAL_FINALIZE =0x02 , +/* Job requires manual dismiss step */ +JOB_MANUAL_DISMISS =0x04 , +}JobCreateFlags ; +/** * Create a new long-running job and return it.
@@ -147,10 +164,11 @@ struct JobDriver {* @job_id: The id of the newly-created job, or %NULL for internal jobs * @driver: The class object for the newly-created job. * @ctx: The AioContext to run the job coroutine in. +* @flags: Creation flags for the job. See @JobCreateFlags. * @errp: Error object. */ void *job_create (const char *job_id,const JobDriver *driver,AioContext *ctx, -Error **errp); +int flags,Error **errp);/** * Add a reference to Job refcnt, it will be decreased with job_unref, and then
block/commit.c +1 -1
@@ -282,7 +282,7 @@ void commit_start(const char *job_id, BlockDriverState *bs, } s =block_job_create (job_id, &commit_job_driver,NULL , bs,0 ,BLK_PERM_ALL , - speed,BLOCK_JOB_DEFAULT ,NULL ,NULL , errp); + speed,JOB_DEFAULT ,NULL ,NULL , errp);if (!s) {return ; }
block/mirror.c +1 -1
@@ -1284,7 +1284,7 @@ void mirror_start(const char *job_id, BlockDriverState *bs, } is_none_mode = mode ==MIRROR_SYNC_MODE_NONE ; base = mode ==MIRROR_SYNC_MODE_TOP ?backing_bs (bs) :NULL ; - mirror_start_job(job_id, bs, BLOCK_JOB_DEFAULT, target, replaces, + mirror_start_job(job_id, bs, JOB_DEFAULT, target, replaces, speed, granularity, buf_size, backing_mode, on_source_error, on_target_error, unmap, NULL, NULL, &mirror_job_driver, is_none_mode, base, false,
block/replication.c +2 -2
@@ -568,7 +568,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode, job =backup_job_create (NULL , s->secondary_disk->bs, s->hidden_disk->bs,0 ,MIRROR_SYNC_MODE_NONE ,NULL , false,BLOCKDEV_ON_ERROR_REPORT , -BLOCKDEV_ON_ERROR_REPORT ,BLOCK_JOB_INTERNAL , +BLOCKDEV_ON_ERROR_REPORT ,JOB_INTERNAL , backup_job_completed, bs,NULL , &local_err);if (local_err) {error_propagate (errp, local_err);
@@ -693,7 +693,7 @@ static void replication_stop(ReplicationState *rs, bool failover, Error **errp) s->stage =BLOCK_REPLICATION_FAILOVER ;commit_active_start (NULL , s->active_disk->bs, s->secondary_disk->bs, -BLOCK_JOB_INTERNAL ,0 ,BLOCKDEV_ON_ERROR_REPORT , +JOB_INTERNAL ,0 ,BLOCKDEV_ON_ERROR_REPORT ,NULL , replication_done, bs, true, errp);break ;default :
block/stream.c +1 -1
@@ -242,7 +242,7 @@ void stream_start(const char *job_id, BlockDriverState *bs,BLK_PERM_GRAPH_MOD ,BLK_PERM_CONSISTENT_READ |BLK_PERM_WRITE_UNCHANGED |BLK_PERM_WRITE , - speed, BLOCK_JOB_DEFAULT, NULL, NULL, errp); + speed, JOB_DEFAULT, NULL, NULL, errp);if (!s) { goto fail; }
blockdev.c +7 -7
@@ -3244,7 +3244,7 @@ void qmp_block_commit(bool has_job_id, const char *job_id, const char *device, goto out; }commit_active_start (has_job_id ? job_id :NULL , bs, base_bs, -BLOCK_JOB_DEFAULT , speed, on_error, +JOB_DEFAULT , speed, on_error, filter_node_name,NULL ,NULL , false, &local_err); }else {BlockDriverState *overlay_bs =bdrv_find_overlay (bs, top_bs);
@@ -3275,7 +3275,7 @@ static BlockJob *do_drive_backup(DriveBackup *backup, BlockJobTxn *txn,AioContext *aio_context;QDict *options =NULL ;Error *local_err =NULL ; -int flags, job_flags =BLOCK_JOB_DEFAULT ; +int flags, job_flags =JOB_DEFAULT ;int64_t size;bool set_backing_hd = false;
@@ -3398,10 +3398,10 @@ static BlockJob *do_drive_backup(DriveBackup *backup, BlockJobTxn *txn, } }if (!backup->auto_finalize) { - job_flags |=BLOCK_JOB_MANUAL_FINALIZE ; + job_flags |=JOB_MANUAL_FINALIZE ; }if (!backup->auto_dismiss) { - job_flags |=BLOCK_JOB_MANUAL_DISMISS ; + job_flags |=JOB_MANUAL_DISMISS ; } job = backup_job_create(backup->job_id, bs, target_bs, backup->speed,
@@ -3442,7 +3442,7 @@ BlockJob *do_blockdev_backup(BlockdevBackup *backup, BlockJobTxn *txn,Error *local_err =NULL ;AioContext *aio_context;BlockJob *job =NULL ; -int job_flags =BLOCK_JOB_DEFAULT ; +int job_flags =JOB_DEFAULT ;if (!backup->has_speed) { backup->speed =0 ;
@@ -3491,10 +3491,10 @@ BlockJob *do_blockdev_backup(BlockdevBackup *backup, BlockJobTxn *txn, } }if (!backup->auto_finalize) { - job_flags |=BLOCK_JOB_MANUAL_FINALIZE ; + job_flags |=JOB_MANUAL_FINALIZE ; }if (!backup->auto_dismiss) { - job_flags |=BLOCK_JOB_MANUAL_DISMISS ; + job_flags |=JOB_MANUAL_DISMISS ; } job = backup_job_create(backup->job_id, bs, target_bs, backup->speed, backup->sync, NULL, backup->compress,
blockjob.c +7 -20
@@ -285,7 +285,7 @@ static void block_job_do_dismiss(BlockJob *job)static void block_job_conclude (BlockJob *job) {job_state_transition (&job->job,JOB_STATUS_CONCLUDED ); -if (job->auto_dismiss || !job_started (&job->job)) { +if (job->job.auto_dismiss || !job_started (&job->job)) {block_job_do_dismiss (job); } }
@@ -483,7 +483,7 @@ static void block_job_completed_txn_abort(BlockJob *job)static int block_job_needs_finalize (BlockJob *job) { -return !job->auto_finalize; +return !job->job.auto_finalize; }static void block_job_do_finalize (BlockJob *job)
@@ -688,8 +688,8 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp) info->io_status = job->iostatus; info->ready = job->ready; info->status = job->job.status; - info->auto_finalize = job->auto_finalize; - info->auto_dismiss = job->auto_dismiss; + info->auto_finalize = job->job.auto_finalize; + info->auto_dismiss = job->job.auto_dismiss; info->has_error = job->ret !=0 ; info->error = job->ret ?g_strdup (strerror (-job->ret)) :NULL ;return info;
@@ -736,7 +736,7 @@ static void block_job_event_completed(BlockJob *job, const char *msg)static int block_job_event_pending (BlockJob *job) {job_state_transition (&job->job,JOB_STATUS_PENDING ); -if (!job->auto_finalize && !block_job_is_internal (job)) { +if (!job->job.auto_finalize && !block_job_is_internal (job)) {qapi_event_send_block_job_pending (job_type (&job->job), job->job.id, &error_abort);
@@ -763,19 +763,8 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,return NULL ; } -if (job_id ==NULL && !(flags &BLOCK_JOB_INTERNAL )) { +if (job_id ==NULL && !(flags &JOB_INTERNAL )) { job_id =bdrv_get_device_name (bs); -if (!*job_id) { -error_setg (errp,"An explicit job ID is required for this node" ); -return NULL ; - } - } - -if (job_id) { -if (flags &BLOCK_JOB_INTERNAL ) { -error_setg (errp,"Cannot specify job ID for internal block job" ); -return NULL ; - } } blk =blk_new (perm, shared_perm);
@@ -786,7 +775,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, } job =job_create (job_id, &driver->job_driver,blk_get_aio_context (blk), - errp); + flags, errp);if (job ==NULL ) {blk_unref (blk);return NULL ;
@@ -800,8 +789,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, job->blk = blk; job->cb = cb; job->opaque = opaque; - job->auto_finalize = !(flags &BLOCK_JOB_MANUAL_FINALIZE ); - job->auto_dismiss = !(flags &BLOCK_JOB_MANUAL_DISMISS );error_setg (&job->blocker,"block device is in use by block job: %s" ,job_type_str (&job->job));
job.c +10 -1
@@ -182,11 +182,15 @@ static void job_sleep_timer_cb(void *opaque) }void *job_create (const char *job_id,const JobDriver *driver,AioContext *ctx, -Error **errp) +int flags,Error **errp) {Job *job;if (job_id) { +if (flags &JOB_INTERNAL ) { +error_setg (errp,"Cannot specify job ID for internal job" ); +return NULL ; + }if (!id_wellformed (job_id)) {error_setg (errp,"Invalid job ID '%s'" , job_id);return NULL ;
@@ -195,6 +199,9 @@ void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx,error_setg (errp,"Job ID '%s' already in use" , job_id);return NULL ; } + }else if (!(flags &JOB_INTERNAL )) { +error_setg (errp,"An explicit job ID is required" ); +return NULL ; } job =g_malloc0 (driver->instance_size);
@@ -205,6 +212,8 @@ void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx, job->busy = false; job->paused = true; job->pause_count =1 ; + job->auto_finalize = !(flags &JOB_MANUAL_FINALIZE ); + job->auto_dismiss = !(flags &JOB_MANUAL_DISMISS );job_state_transition (job,JOB_STATUS_CREATED ); aio_timer_init(qemu_get_aio_context(), &job->sleep_timer,
qemu-img.c +1 -1
@@ -1015,7 +1015,7 @@ static int img_commit(int argc, char **argv) aio_context =bdrv_get_aio_context (bs);aio_context_acquire (aio_context); -commit_active_start ("commit" , bs, base_bs,BLOCK_JOB_DEFAULT ,0 , +commit_active_start ("commit" , bs, base_bs,JOB_DEFAULT ,0 ,BLOCKDEV_ON_ERROR_REPORT ,NULL , common_block_job_cb, &cbi, false, &local_err);aio_context_release (aio_context);
tests/test-blockjob-txn.c +1 -1
@@ -107,7 +107,7 @@ static BlockJob *test_block_job_start(unsigned int iterations,snprintf (job_id,sizeof (job_id),"job%u" , counter++); s =block_job_create (job_id, &test_block_job_driver, txn, bs, -0 ,BLK_PERM_ALL ,0 ,BLOCK_JOB_DEFAULT , +0 ,BLK_PERM_ALL ,0 ,JOB_DEFAULT , test_block_job_cb, data, &error_abort); s->iterations = iterations; s->use_timer = use_timer;
tests/test-blockjob.c +2 -2
@@ -59,7 +59,7 @@ static BlockJob *do_test_id(BlockBackend *blk, const char *id, bool should_succeed) {return mk_job (blk, id, &test_block_job_driver, - should_succeed,BLOCK_JOB_DEFAULT ); + should_succeed,JOB_DEFAULT ); }/* This creates a BlockBackend (optionally with a name) with a
@@ -214,7 +214,7 @@ static CancelJob *create_common(BlockJob **pjob) blk =create_blk (NULL ); job =mk_job (blk,"Steve" , &test_cancel_driver, true, -BLOCK_JOB_MANUAL_FINALIZE |BLOCK_JOB_MANUAL_DISMISS ); +JOB_MANUAL_FINALIZE |JOB_MANUAL_DISMISS );job_ref (&job->job);assert (job->job.status ==JOB_STATUS_CREATED ); s =container_of (job, CancelJob, common);
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH v2 17/40] job: Move BlockJobCreateFlags to Job
job.c void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx, · line 216
Quoted 2 lines · Kevin Wolf
job.c +2
-0+ job->auto_finalize = !(flags &JOB_MANUAL_FINALIZE ); + job->auto_dismiss = !(flags &JOB_MANUAL_DISMISS );
Job API might be a good chance to say "No, this is the default behavior for this API."
I don't know how possible this is, but could we remove these behavior flags for jobs (but keep them for block jobs), and then any legacy block job creation interfaces we have can enable/disable them as the user requested,
and the block job layer itself has hooks that persuade the core job layer to automatically transition without user input, if appropriate.
(Unless that happens later?)
Re: [Qemu-devel] [PATCH v2 17/40] job: Move BlockJobCreateFlags to Job
Am 24.05.2018 um 00:24 hat John Snow geschrieben:
Quoted 14 lines · Kevin Wolf
job.c void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx, · line 216
Quoted 2 lines
job.c +2
-0+ job->auto_finalize = !(flags &JOB_MANUAL_FINALIZE ); + job->auto_dismiss = !(flags &JOB_MANUAL_DISMISS );Job API might be a good chance to say "No, this is the default behavior for this API."
I don't know how possible this is, but could we remove these behavior flags for jobs (but keep them for block jobs), and then any legacy block job creation interfaces we have can enable/disable them as the user requested,
Quoted 4 lines
and the block job layer itself has hooks that persuade the core job layer to automatically transition without user input, if appropriate.
(Unless that happens later?)
That's really a question for job-create, which we don't have yet. I'm not sure whether job-create would expose those options, and if so, what the defaults would be.
I think auto-dismiss=false is a good default because the reason for having a manual job-dismiss is shared by all jobs (you want to be able to query the job result after it finished).
auto-finalize is different. We introduced it to avoid surprise graph changes, but this implies that every job changes the block graph when it completes. This is obviously not true for non-block jobs. They might still commonly change something else that shouldn't be done as a surprise, but I am working right now on at least a single job type that doesn't do anything like that on completion (blockdev-create).
For now, I made it unconditionally auto-finalize=true and auto-dismiss=false without offering the options in QMP, but that's open for discussion.
Kevin
Re: [Qemu-devel] [PATCH v2 17/40] job: Move BlockJobCreateFlags to Job
Quoted 20 lines · Kevin Wolf
Am 24.05.2018 um 00:24 hat John Snow geschrieben:
Quoted 14 lines · Kevin Wolf
job.c void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx, · line 216
Quoted 2 lines
job.c +2
-0+ job->auto_finalize = !(flags &JOB_MANUAL_FINALIZE ); + job->auto_dismiss = !(flags &JOB_MANUAL_DISMISS );Job API might be a good chance to say "No, this is the default behavior for this API."
I don't know how possible this is, but could we remove these behavior flags for jobs (but keep them for block jobs), and then any legacy block job creation interfaces we have can enable/disable them as the user requested,
Quoted 4 lines
and the block job layer itself has hooks that persuade the core job layer to automatically transition without user input, if appropriate.
(Unless that happens later?)
Quoted 8 lines
That's really a question for job-create, which we don't have yet. I'm not sure whether job-create would expose those options, and if so, what the defaults would be.
I think auto-dismiss=false is a good default because the reason for having a manual job-dismiss is shared by all jobs (you want to be able to query the job result after it finished).
Sounds good to me.
Quoted 7 lines
auto-finalize is different. We introduced it to avoid surprise graph changes, but this implies that every job changes the block graph when it completes. This is obviously not true for non-block jobs. They might still commonly change something else that shouldn't be done as a surprise, but I am working right now on at least a single job type that doesn't do anything like that on completion (blockdev-create).
I'll have to stay tuned.
Quoted 3 lines
For now, I made it unconditionally auto-finalize=true and auto-dismiss=false without offering the options in QMP, but that's open for discussion.
Sure. I was wondering if we didn't want to change the internal API's defaults around, but I guess that can happen whenever based on what the QMP looks like.
Quoted 3 lines
Kevin
PATCH v2 18/40 blockjob: Split block_job_event_pending()
In reply to Kevin Wolfblock_job_event_pending() doesn't only send a QMP event, but it also transitions to the PENDING state. Split the function so that we get one part only sending the event (like other block_job_event_* functions) and another part that does the state transition.
--- blockjob.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-)
blockjob.c +18 -9
@@ -38,7 +38,7 @@static void block_job_event_cancelled (BlockJob *job);static void block_job_event_completed (BlockJob *job,const char *msg); -static int block_job_event_pending (BlockJob *job); +static void block_job_event_pending (BlockJob *job);/* Transactional group of block jobs */ struct BlockJobTxn {
@@ -500,6 +500,15 @@ static void block_job_do_finalize(BlockJob *job) } } +static int block_job_transition_to_pending (BlockJob *job) +{ +job_state_transition (&job->job,JOB_STATUS_PENDING ); +if (!job->job.auto_finalize) { +block_job_event_pending (job); + } +return 0 ; +} +static void block_job_completed_txn_success (BlockJob *job) {BlockJobTxn *txn = job->txn;
@@ -518,7 +527,7 @@ static void block_job_completed_txn_success(BlockJob *job)assert (other_job->ret ==0 ); } -block_job_txn_apply (txn, block_job_event_pending, false); +block_job_txn_apply (txn, block_job_transition_to_pending, false);/* If no jobs need manual finalization, automatically do so */ if (block_job_txn_apply (txn, block_job_needs_finalize, false) ==0 ) {
@@ -733,15 +742,15 @@ static void block_job_event_completed(BlockJob *job, const char *msg) &error_abort); } -static int block_job_event_pending (BlockJob *job) +static void block_job_event_pending (BlockJob *job) { -job_state_transition (&job->job,JOB_STATUS_PENDING ); -if (!job->job.auto_finalize && !block_job_is_internal (job)) { -qapi_event_send_block_job_pending (job_type (&job->job), - job->job.id, - &error_abort); +if (block_job_is_internal (job)) { +return ; } -return 0 ; + +qapi_event_send_block_job_pending (job_type (&job->job), + job->job.id, + &error_abort); }/*
Signature · 1 line
2.13.6
PATCH v2 19/40 job: Add job_event_*()
In reply to Kevin WolfGo through the Job layer in order to send QMP events. For the moment, these functions only call a notifier in the BlockJob layer that sends the existing commands.
This uses notifiers rather than JobDriver callbacks because internal users of jobs won't receive QMP events, but might still be interested in getting notified for the events.
--- include/block/blockjob.h | 9 +++++++++ include/qemu/job.h | 18 ++++++++++++++++++ blockjob.c | 41 +++++++++++++++++++++++++++-------------- job.c | 19 +++++++++++++++++++ 4 files changed, 73 insertions(+), 14 deletions(-)
include/block/blockjob.h +9 -0
@@ -82,6 +82,15 @@ typedef struct BlockJob {/** Block other operations when block job is running */ Error *blocker; +/** Called when a cancelled job is finalised. */ +Notifier finalize_cancelled_notifier; + +/** Called when a successfully completed job is finalised. */ +Notifier finalize_completed_notifier; + +/** Called when the job transitions to PENDING */ +Notifier pending_notifier; +/** BlockDriverStates that are involved in this block job */ GSList *nodes;
include/qemu/job.h +18 -0
@@ -105,6 +105,15 @@ typedef struct Job {/** True if this job should automatically dismiss itself */ bool auto_dismiss; +/** Notifiers called when a cancelled job is finalised */ +NotifierList on_finalize_cancelled; + +/** Notifiers called when a successfully completed job is finalised */ +NotifierList on_finalize_completed; + +/** Notifiers called when the job transitions to PENDING */ +NotifierList on_pending; +/** Element of the list of jobs */ QLIST_ENTRY (Job) job_list; } Job;
@@ -182,6 +191,15 @@ void job_ref(Job *job);*/ void job_unref (Job *job); +/** To be called when a cancelled job is finalised. */ +void job_event_cancelled (Job *job); + +/** To be called when a successfully completed job is finalised. */ +void job_event_completed (Job *job); + +/** To be called when the job transitions to PENDING */ +void job_event_pending (Job *job); +/** * Conditionally enter the job coroutine if the job is ready to run, not * already busy and fn() returns true. fn() is called while under the job_lock
blockjob.c +27 -14
91 lines not on this page · raw message
job.c +19 -0
@@ -215,6 +215,10 @@ void *job_create(const char *job_id, const JobDriver *driver, AioContext *ctx, job->auto_finalize = !(flags &JOB_MANUAL_FINALIZE ); job->auto_dismiss = !(flags &JOB_MANUAL_DISMISS ); +notifier_list_init (&job->on_finalize_cancelled); +notifier_list_init (&job->on_finalize_completed); +notifier_list_init (&job->on_pending); +job_state_transition (job,JOB_STATUS_CREATED ); aio_timer_init(qemu_get_aio_context(), &job->sleep_timer, QEMU_CLOCK_REALTIME, SCALE_NS,
@@ -247,6 +251,21 @@ void job_unref(Job *job) } } +void job_event_cancelled (Job *job) +{ +notifier_list_notify (&job->on_finalize_cancelled, job); +} + +void job_event_completed (Job *job) +{ +notifier_list_notify (&job->on_finalize_completed, job); +} + +void job_event_pending (Job *job) +{ +notifier_list_notify (&job->on_pending, job); +} +void job_enter_cond (Job *job,bool (*fn)(Job *job)) {if (!job_started (job)) {
Signature · 1 line
2.13.6
PATCH v2 20/40 job: Move single job finalisation to Job
In reply to Kevin WolfThis moves the finalisation of a single job from BlockJob to Job.
Some part of this code depends on job transactions, and job transactions call this code, we introduce some temporary calls from Job functions to BlockJob ones. This will be fixed once transactions move to Job, too.
--- include/block/blockjob.h | 9 --- include/block/blockjob_int.h | 36 ----------- include/qemu/job.h | 53 +++++++++++++++- block/backup.c | 22 +++---- block/commit.c | 2 +- block/mirror.c | 2 +- blockjob.c | 142 ++++++++----------------------------------- job.c | 100 +++++++++++++++++++++++++++++- qemu-img.c | 2 +- tests/test-blockjob.c | 10 +-- 10 files changed, 194 insertions(+), 184 deletions(-)
include/block/blockjob.h +0 -9
@@ -76,9 +76,6 @@ typedef struct BlockJob {/** Rate limiting data structure for implementing @speed. */ RateLimit limit; -/** The completion function that will be called when the job completes. */ -BlockCompletionFunc *cb; -/** Block other operations when block job is running */ Error *blocker;
@@ -94,12 +91,6 @@ typedef struct BlockJob {/** BlockDriverStates that are involved in this block job */ GSList *nodes; -/** The opaque value that is passed to the completion function. */ -void *opaque; - -/** ret code passed to block_job_completed. */ -int ret; -BlockJobTxn *txn;QLIST_ENTRY (BlockJob) txn_list; } BlockJob;
include/block/blockjob_int.h +0 -36
@@ -54,34 +54,6 @@ struct BlockJobDriver {*/ int (*prepare)(BlockJob *job); -/** -* If the callback is not NULL, it will be invoked when all the jobs -* belonging to the same transaction complete; or upon this job's -* completion if it is not in a transaction. Skipped if NULL. -* -* All jobs will complete with a call to either .commit() or .abort() but -* never both. -*/ -void (*commit)(BlockJob *job); - -/** -* If the callback is not NULL, it will be invoked when any job in the -* same transaction fails; or upon this job's failure (due to error or -* cancellation) if it is not in a transaction. Skipped if NULL. -* -* All jobs will complete with a call to either .commit() or .abort() but -* never both. -*/ -void (*abort)(BlockJob *job); - -/** -* If the callback is not NULL, it will be invoked after a call to either -* .commit() or .abort(). Regardless of which callback is invoked after -* completion, .clean() will always be called, even if the job does not -* belong to a transaction group. -*/ -void (*clean)(BlockJob *job); -/* * If the callback is not NULL, it will be invoked before the job is * resumed in a new AioContext. This is the place to move any resources
@@ -156,14 +128,6 @@ void block_job_yield(BlockJob *job);int64_t block_job_ratelimit_get_delay (BlockJob *job,uint64_t n);/** -* block_job_early_fail: -* @bs: The block device. -* -* The block job could not be started, free it. -*/ -void block_job_early_fail (BlockJob *job); - -/** * block_job_completed: * @job: The job being completed. * @ret: The status code.
include/qemu/job.h +52 -1
91 lines not on this page · raw message
block/backup.c +11 -11
@@ -207,25 +207,25 @@ static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret) } } -static void backup_commit (BlockJob *job) +static void backup_commit (Job *job) { -BackupBlockJob *s =container_of (job, BackupBlockJob, common); +BackupBlockJob *s =container_of (job, BackupBlockJob, common.job);if (s->sync_bitmap) {backup_cleanup_sync_bitmap (s,0 ); } } -static void backup_abort (BlockJob *job) +static void backup_abort (Job *job) { -BackupBlockJob *s =container_of (job, BackupBlockJob, common); +BackupBlockJob *s =container_of (job, BackupBlockJob, common.job);if (s->sync_bitmap) {backup_cleanup_sync_bitmap (s,-1 ); } } -static void backup_clean (BlockJob *job) +static void backup_clean (Job *job) { -BackupBlockJob *s =container_of (job, BackupBlockJob, common); +BackupBlockJob *s =container_of (job, BackupBlockJob, common.job);assert (s->target);blk_unref (s->target); s->target =NULL ;
@@ -530,10 +530,10 @@ static const BlockJobDriver backup_job_driver = { .free = block_job_free, .user_resume = block_job_user_resume, .start = backup_run, + .commit = backup_commit, + .abort = backup_abort, + .clean = backup_clean, }, - .commit = backup_commit, - .abort = backup_abort, - .clean = backup_clean, .attached_aio_context = backup_attached_aio_context, .drain = backup_drain, };
@@ -678,8 +678,8 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,bdrv_reclaim_dirty_bitmap (bs, sync_bitmap,NULL ); }if (job) { -backup_clean (&job->common); -block_job_early_fail (&job->common); +backup_clean (&job->common.job); +job_early_fail (&job->common.job); }return NULL ;
block/commit.c +1 -1
@@ -385,7 +385,7 @@ fail:if (commit_top_bs) {bdrv_replace_node (commit_top_bs, top, &error_abort); } -block_job_early_fail (&s->common); +job_early_fail (&s->common.job); }
block/mirror.c +1 -1
@@ -1257,7 +1257,7 @@ fail:g_free (s->replaces);blk_unref (s->target); -block_job_early_fail (&s->common); +job_early_fail (&s->common.job); } bdrv_child_try_set_perm(mirror_top_bs->backing, 0, BLK_PERM_ALL,
blockjob.c +25 -117
233 lines not on this page · raw message
job.c +98 -2
124 lines not on this page · raw message
qemu-img.c +1 -1
@@ -872,7 +872,7 @@ static void run_block_job(BlockJob *job, Error **errp)if (!job_is_completed (&job->job)) { ret =block_job_complete_sync (job, errp); }else { - ret = job->ret; + ret = job->job.ret; }job_unref (&job->job);aio_context_release (aio_context);
tests/test-blockjob.c +5 -5
@@ -128,11 +128,11 @@ static void test_job_ids(void) job[1 ] =do_test_id (blk[1 ],"id0" , false);/* But once job[0] finishes we can reuse its ID */ -block_job_early_fail (job[0 ]); +job_early_fail (&job[0 ]->job); job[1 ] =do_test_id (blk[1 ],"id0" , true);/* No job ID specified, defaults to the backend name ('drive1') */ -block_job_early_fail (job[1 ]); +job_early_fail (&job[1 ]->job); job[1 ] =do_test_id (blk[1 ],NULL , true);/* Duplicate job ID */
@@ -145,9 +145,9 @@ static void test_job_ids(void)/* This one is valid */ job[2 ] =do_test_id (blk[2 ],"id_2" , true); -block_job_early_fail (job[0 ]); -block_job_early_fail (job[1 ]); -block_job_early_fail (job[2 ]); +job_early_fail (&job[0 ]->job); +job_early_fail (&job[1 ]->job); +job_early_fail (&job[2 ]->job);destroy_blk (blk[0 ]);destroy_blk (blk[1 ]);
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH v2 20/40] job: Move single job finalisation to Job
Quoted 15 lines · Kevin Wolf
This moves the finalisation of a single job from BlockJob to Job.
Some part of this code depends on job transactions, and job transactions call this code, we introduce some temporary calls from Job functions to BlockJob ones. This will be fixed once transactions move to Job, too.
Signed-off-by Kevin Wolf <[email protected]>Reviewed-by Max Reitz <[email protected]>---
+++
b/job.cjob.c +0
-0@@ -449,6 +451,100 @@ void job_user_resume(Job *job, Error **errp)job_resume (job); }
job.c void job_user_resume(Job *job, Error **errp) · line 462
Quoted 10 lines
job.c +9
-0+void job_do_dismiss (Job *job) +{ +assert (job); + job->busy = false; + job->paused = false; + job->deferred_to_main_loop = true; + +/* TODO Don't assume it's a BlockJob */ +block_job_txn_del_job ((BlockJob *) job);
checkpatch flagged this for style, but it goes away later in the series. But more than just style, this hard-codes the assumption that BlockJob has a Job member at offset 0. So would it be better to use container_of() instead of a bare cast, even though it is transient?
Signature · 3 lines
Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
PATCH v2 21/40 job: Convert block_job_cancel_async() to Job
In reply to Kevin Wolfblock_job_cancel_async() did two things that were still block job specific:
* Setting job->force. This field makes sense on the Job level, so we can just move it. While at it, rename it to job->force_cancel to make its purpose more obvious.
* Resetting the I/O status. This can't be moved because generic Jobs don't have an I/O status. What the function really implements is a user resume, except without entering the coroutine. Consequently, it makes sense to call the .user_resume driver callback here which already resets the I/O status.
The old block_job_cancel_async() has two separate if statements that check job->iostatus != BLOCK_DEVICE_IO_STATUS_OK and job->user_paused. However, the former condition always implies the latter (as is asserted in block_job_iostatus_reset()), so changing the explicit call of block_job_iostatus_reset() on the former condition with the .user_resume callback on the latter condition is equivalent and doesn't need to access any BlockJob specific state.
--- include/block/blockjob.h | 6 ------ include/qemu/job.h | 6 ++++++ block/mirror.c | 4 ++-- blockjob.c | 25 +++++++++++++------------ 4 files changed, 21 insertions(+), 20 deletions(-)
include/block/blockjob.h +0 -6
@@ -51,12 +51,6 @@ typedef struct BlockJob {BlockBackend *blk;/** -* Set to true if the job should abort immediately without waiting -* for data to be in sync. -*/ -bool force; - -/** * Set to true when the job is ready to be completed. */ bool ready;
include/qemu/job.h +6 -0
@@ -97,6 +97,12 @@ typedef struct Job {*/ bool cancelled; +/** +* Set to true if the job should abort immediately without waiting +* for data to be in sync. +*/ +bool force_cancel; +/** Set to true when the job has deferred work to the main loop. */ bool deferred_to_main_loop;
block/mirror.c +2 -2
@@ -871,7 +871,7 @@ static void coroutine_fn mirror_run(void *opaque)trace_mirror_before_sleep (s, cnt, s->synced, delay_ns);job_sleep_ns (&s->common.job, delay_ns);if (job_is_cancelled (&s->common.job) && - (!s->synced || s->common.force)) + (!s->synced || s->common.job.force_cancel)) {break ; }
@@ -884,7 +884,7 @@ immediate_exit:* or it was cancelled prematurely so that we do not guarantee that * the target is a copy of the source. */ -assert (ret <0 || ((s->common.force || !s->synced) && +assert (ret <0 || ((s->common.job.force_cancel || !s->synced) &&job_is_cancelled (&s->common.job)));assert (need_drain);mirror_wait_for_all_io (s);
blockjob.c +13 -12
@@ -270,19 +270,20 @@ static int block_job_prepare(BlockJob *job)return job->job.ret; } -static void block_job_cancel_async (BlockJob *job,bool force) +static void job_cancel_async (Job *job,bool force) { -if (job->iostatus !=BLOCK_DEVICE_IO_STATUS_OK ) { -block_job_iostatus_reset (job); - } -if (job->job.user_paused) { -/* Do not call block_job_enter here, the caller will handle it. */ - job->job.user_paused = false; - job->job.pause_count--; +if (job->user_paused) { +/* Do not call job_enter here, the caller will handle it. */ + job->user_paused = false; +if (job->driver->user_resume) { + job->driver->user_resume (job); + } +assert (job->pause_count >0 ); + job->pause_count--; } - job->job.cancelled = true; + job->cancelled = true;/* To prevent 'force == false' overriding a previous 'force == true' */ - job->force |= force; + job->force_cancel |= force; }static int block_job_txn_apply (BlockJobTxn *txn,int fn (BlockJob *),bool lock)
@@ -367,7 +368,7 @@ static void block_job_completed_txn_abort(BlockJob *job)* on the caller, so leave it. */ QLIST_FOREACH(other_job, &txn->jobs, txn_list) {if (other_job != job) { -block_job_cancel_async (other_job, false); +job_cancel_async (&other_job->job, false); } }while (!QLIST_EMPTY (&txn->jobs)) {
@@ -527,7 +528,7 @@ void block_job_cancel(BlockJob *job, bool force)job_do_dismiss (&job->job);return ; } -block_job_cancel_async (job, force); +job_cancel_async (&job->job, force);if (!job_started (&job->job)) {block_job_completed (job, -ECANCELED ); }else if (job->job.deferred_to_main_loop) {
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH v2 21/40] job: Convert block_job_cancel_async() to Job
Quoted 10 lines · Kevin Wolf
block_job_cancel_async()did two things that were still block job specific:* Setting
job->force. This field makes sense on the Job level, so we can just move it. While at it, rename it tojob->force_cancelto make its purpose more obvious.* Resetting the I/O status. This can't be moved because generic Jobs don't have an I/O status. What the function really implements is a user resume, except without entering the coroutine. Consequently, it
You know, I had noticed this before but because this is called from contexts which are NOT user-initiated, I didn't want to share the callback.
... I guess it's fine because the job is about to not exist anymore, so it probably won't spook anything.
Quoted 10 lines
makes sense to call the .user_resume driver callback here which already resets the I/O status.
The old
block_job_cancel_async()has two separate if statements that checkjob->iostatus!=BLOCK_DEVICE_IO_STATUS_OKandjob->user_paused. However, the former condition always implies the latter (as is asserted inblock_job_iostatus_reset()), so changing the explicit call ofblock_job_iostatus_reset()on the former condition with the .user_resume callback on the latter condition is equivalent and doesn't need to access any BlockJob specific state.
Oh, cool! This is nice.
Quoted 32 lines
Signed-off-by Kevin Wolf <[email protected]>Reviewed-by Max Reitz <[email protected]>--- include/block/blockjob.h | 6 ------ include/qemu/job.h | 6 ++++++ block/mirror.c | 4 ++-- blockjob.c | 25 +++++++++++++------------ 4 files changed, 21 insertions(+), 20 deletions(-)include/block/blockjob.h +0
-6@@ -51,12 +51,6 @@ typedef struct BlockJob {BlockBackend *blk;/** -* Set to true if the job should abort immediately without waiting -* for data to be in sync. -*/ -bool force; - -/** * Set to true when the job is ready to be completed. */ bool ready;include/qemu/job.h +0
-0
include/qemu/job.h typedef struct Job { · line 105
Quoted 9 lines
include/qemu/job.h +6
-0@@ -97,6 +97,12 @@ typedef struct Job {*/ bool cancelled; +/** +* Set to true if the job should abort immediately without waiting +* for data to be in sync. +*/ +bool force_cancel; +
Does this comment need an update now, though?
Actually, in terms of "new jobs" API, it'd be really nice if cancel *always meant cancel*.
I think "cancel" should never be used to mean "successful completion, but different from the one we'd get if we used job_complete."
i.e., either we need a job setting:
job-set completion-mode=[pivot|no-pivot]
or optional parameters to pass to job-complete:
job-complete mode=[understood-by-job-type]
or some other mechanism that accomplishes the same type of behavior. It would be nice if it did not have to be determined at job creation time but instead could be determined later.
Quoted 79 lines
include/qemu/job.h +0
-0/** Set to true when the job has deferred work to the main loop. */ bool deferred_to_main_loop;block/mirror.c +2
-2@@ -871,7 +871,7 @@ static void coroutine_fn mirror_run(void *opaque)trace_mirror_before_sleep (s, cnt, s->synced, delay_ns);job_sleep_ns (&s->common.job, delay_ns);if (job_is_cancelled (&s->common.job) && - (!s->synced || s->common.force)) + (!s->synced || s->common.job.force_cancel)) {break ; }@@ -884,7 +884,7 @@ immediate_exit:* or it was cancelled prematurely so that we do not guarantee that * the target is a copy of the source. */ -assert (ret <0 || ((s->common.force || !s->synced) && +assert (ret <0 || ((s->common.job.force_cancel || !s->synced) &&job_is_cancelled (&s->common.job)));assert (need_drain);mirror_wait_for_all_io (s);blockjob.c +13
-12@@ -270,19 +270,20 @@ static int block_job_prepare(BlockJob *job)return job->job.ret; } -static void block_job_cancel_async (BlockJob *job,bool force) +static void job_cancel_async (Job *job,bool force) { -if (job->iostatus !=BLOCK_DEVICE_IO_STATUS_OK ) { -block_job_iostatus_reset (job); - } -if (job->job.user_paused) { -/* Do not call block_job_enter here, the caller will handle it. */ - job->job.user_paused = false; - job->job.pause_count--; +if (job->user_paused) { +/* Do not call job_enter here, the caller will handle it. */ + job->user_paused = false; +if (job->driver->user_resume) { + job->driver->user_resume (job); + } +assert (job->pause_count >0 ); + job->pause_count--; } - job->job.cancelled = true; + job->cancelled = true;/* To prevent 'force == false' overriding a previous 'force == true' */ - job->force |= force; + job->force_cancel |= force; }static int block_job_txn_apply (BlockJobTxn *txn,int fn (BlockJob *),bool lock)@@ -367,7 +368,7 @@ static void block_job_completed_txn_abort(BlockJob *job)* on the caller, so leave it. */ QLIST_FOREACH(other_job, &txn->jobs, txn_list) {if (other_job != job) { -block_job_cancel_async (other_job, false); +job_cancel_async (&other_job->job, false); } }while (!QLIST_EMPTY (&txn->jobs)) {@@ -527,7 +528,7 @@ void block_job_cancel(BlockJob *job, bool force)job_do_dismiss (&job->job);return ; } -block_job_cancel_async (job, force); +job_cancel_async (&job->job, force);if (!job_started (&job->job)) {block_job_completed (job, -ECANCELED ); }else if (job->job.deferred_to_main_loop) {
Re: [Qemu-devel] [PATCH v2 21/40] job: Convert block_job_cancel_async() to Job
Am 24.05.2018 um 01:18 hat John Snow geschrieben:
Quoted 27 lines
Quoted 5 lines
include/qemu/job.h +0
-0include/qemu/job.h typedef struct Job { · line 105
Quoted 9 lines
include/qemu/job.h +6
-0@@ -97,6 +97,12 @@ typedef struct Job {*/ bool cancelled; +/** +* Set to true if the job should abort immediately without waiting +* for data to be in sync. +*/ +bool force_cancel; +Does this comment need an update now, though?
Actually, in terms of "new jobs" API, it'd be really nice if cancel *always meant cancel*.
I think "cancel" should never be used to mean "successful completion, but different from the one we'd get if we used
job_complete."i.e., either we need a job setting:
job-set completion-mode=[pivot|no-pivot]
Quoted 7 lines
or optional parameters to pass to job-complete:
job-complete mode=[understood-by-job-type]
or some other mechanism that accomplishes the same type of behavior. It would be nice if it did not have to be determined at job creation time but instead could be determined later.
I agree. We already made sure that job-cancel really means cancel on the QAPI level, so we're free to do that. We just need to keep supporting block-job-cancel with the old semantics, so what I have is the easy conversion. We can change the internal implementation when we actually implement the selection of a completion mode.
Kevin
Re: [Qemu-devel] [PATCH v2 21/40] job: Convert block_job_cancel_async() to Job
Quoted 36 lines · Kevin Wolf
Am 24.05.2018 um 01:18 hat John Snow geschrieben:
Quoted 27 lines
Quoted 5 lines
include/qemu/job.h +0
-0include/qemu/job.h typedef struct Job { · line 105
Quoted 9 lines
include/qemu/job.h +6
-0@@ -97,6 +97,12 @@ typedef struct Job {*/ bool cancelled; +/** +* Set to true if the job should abort immediately without waiting +* for data to be in sync. +*/ +bool force_cancel; +Does this comment need an update now, though?
Actually, in terms of "new jobs" API, it'd be really nice if cancel *always meant cancel*.
I think "cancel" should never be used to mean "successful completion, but different from the one we'd get if we used
job_complete."i.e., either we need a job setting:
job-set completion-mode=[pivot|no-pivot]
Quoted 7 lines
or optional parameters to pass to job-complete:
job-complete mode=[understood-by-job-type]
or some other mechanism that accomplishes the same type of behavior. It would be nice if it did not have to be determined at job creation time but instead could be determined later.
Quoted 8 lines
I agree. We already made sure that job-cancel really means cancel on the QAPI level, so we're free to do that. We just need to keep supporting block-job-cancel with the old semantics, so what I have is the easy conversion. We can change the internal implementation when we actually implement the selection of a completion mode.
Kevin
We need this before 3.0 though, yeah? unless we make job-cancel x-job-cancel or some other warning that the way it works might change, yeah?
Or do I misunderstand our leeway to change this at a later point in time?
(can job-cancel apply to block jobs created with the legacy infrastructure? My reading was "yes.")
--js
Re: [Qemu-devel] [PATCH v2 21/40] job: Convert block_job_cancel_async() to Job
Am 24.05.2018 um 19:42 hat John Snow geschrieben:
Quoted 48 lines · Kevin Wolf
Quoted 36 lines
Am 24.05.2018 um 01:18 hat John Snow geschrieben:
Quoted 27 lines
Quoted 5 lines
include/qemu/job.h +0
-0include/qemu/job.h typedef struct Job { · line 105
Quoted 9 lines
include/qemu/job.h +6
-0@@ -97,6 +97,12 @@ typedef struct Job {*/ bool cancelled; +/** +* Set to true if the job should abort immediately without waiting +* for data to be in sync. +*/ +bool force_cancel; +Does this comment need an update now, though?
Actually, in terms of "new jobs" API, it'd be really nice if cancel *always meant cancel*.
I think "cancel" should never be used to mean "successful completion, but different from the one we'd get if we used
job_complete."i.e., either we need a job setting:
job-set completion-mode=[pivot|no-pivot]
Quoted 7 lines
or optional parameters to pass to job-complete:
job-complete mode=[understood-by-job-type]
or some other mechanism that accomplishes the same type of behavior. It would be nice if it did not have to be determined at job creation time but instead could be determined later.
Quoted 8 lines
I agree. We already made sure that job-cancel really means cancel on the QAPI level, so we're free to do that. We just need to keep supporting block-job-cancel with the old semantics, so what I have is the easy conversion. We can change the internal implementation when we actually implement the selection of a completion mode.
Kevin
Quoted 7 lines
We need this before 3.0 though, yeah? unless we make job-cancel x-job-cancel or some other warning that the way it works might change, yeah?
Or do I misunderstand our leeway to change this at a later point in time?
(can job-cancel apply to block jobs created with the legacy infrastructure? My reading was "yes.")
It can, and it already has its final semantics, so nothing has to change before 3.0. job-cancel is equivalent to block-job-cancel with fixed force=true. If you want the complete-by-cancel behaviour of mirror, you have to use block-job-cancel for now, because job-cancel doesn't provide that functionality.
So what we can change later is adding a way to initiate this secondary completion mode with a job-* command (probably with a new option for job-complete). But we wouldn't change the semantics of exisiting commands.
Kevin
Re: [Qemu-devel] [PATCH v2 21/40] job: Convert block_job_cancel_async() to Job
Quoted 63 lines · Kevin Wolf
Am 24.05.2018 um 19:42 hat John Snow geschrieben:
Quoted 48 lines · Kevin Wolf
Quoted 36 lines
Am 24.05.2018 um 01:18 hat John Snow geschrieben:
Quoted 27 lines
Quoted 5 lines
include/qemu/job.h +0
-0include/qemu/job.h typedef struct Job { · line 105
Quoted 9 lines
include/qemu/job.h +6
-0@@ -97,6 +97,12 @@ typedef struct Job {*/ bool cancelled; +/** +* Set to true if the job should abort immediately without waiting +* for data to be in sync. +*/ +bool force_cancel; +Does this comment need an update now, though?
Actually, in terms of "new jobs" API, it'd be really nice if cancel *always meant cancel*.
I think "cancel" should never be used to mean "successful completion, but different from the one we'd get if we used
job_complete."i.e., either we need a job setting:
job-set completion-mode=[pivot|no-pivot]
Quoted 7 lines
or optional parameters to pass to job-complete:
job-complete mode=[understood-by-job-type]
or some other mechanism that accomplishes the same type of behavior. It would be nice if it did not have to be determined at job creation time but instead could be determined later.
Quoted 8 lines
I agree. We already made sure that job-cancel really means cancel on the QAPI level, so we're free to do that. We just need to keep supporting block-job-cancel with the old semantics, so what I have is the easy conversion. We can change the internal implementation when we actually implement the selection of a completion mode.
Kevin
Quoted 7 lines
We need this before 3.0 though, yeah? unless we make job-cancel x-job-cancel or some other warning that the way it works might change, yeah?
Or do I misunderstand our leeway to change this at a later point in time?
(can job-cancel apply to block jobs created with the legacy infrastructure? My reading was "yes.")
It can, and it already has its final semantics, so nothing has to change before 3.0. job-cancel is equivalent to block-job-cancel with fixed force=true. If you want the complete-by-cancel behaviour of mirror, you have to use block-job-cancel for now, because job-cancel doesn't provide that functionality.
Quoted 7 lines
So what we can change later is adding a way to initiate this secondary completion mode with a job-* command (probably with a new option for job-complete). But we wouldn't change the semantics of exisiting commands.
Kevin
Oho, I _was_ missing something. I hadn't realized cancel takes on the newer semantics. Thanks.
Re: [Qemu-devel] [Qemu-block] [PATCH v2 21/40] job: Convert block_job_cancel_async() to Job
In reply to Kevin WolfQuoted 1 line · Kevin Wolf
Am 24.05.2018 um 19:42 hat John Snow geschrieben:
[...]
(Randomly chiming in for a small clarification.)
Quoted 18 lines
Quoted 18 lines
Quoted 4 lines
Quoted 3 lines
or some other mechanism that accomplishes the same type of behavior. It would be nice if it did not have to be determined at job creation time but instead could be determined later.
Quoted 8 lines
I agree. We already made sure that job-cancel really means cancel on the QAPI level, so we're free to do that. We just need to keep supporting block-job-cancel with the old semantics, so what I have is the easy conversion. We can change the internal implementation when we actually implement the selection of a completion mode.
Kevin
We need this before 3.0 though, yeah? unless we make job-cancel x-job-cancel or some other warning that the way it works might change, yeah?
Or do I misunderstand our leeway to change this at a later point in time?
Quoted 8 lines
Quoted 2 lines
(can job-cancel apply to block jobs created with the legacy infrastructure? My reading was "yes.")
It can, and it already has its final semantics, so nothing has to change before 3.0. job-cancel is equivalent to block-job-cancel with fixed force=true. If you want the complete-by-cancel behaviour of mirror, you have to use block-job-cancel for now, because job-cancel doesn't provide that functionality.
I think by "complete-by-cancel" you are referring to this[*] magic behaviour, mentioned in the last sentence here:
When you cancel an in-progress ‘mirror’ job before the source and target are synchronized, block-job-cancel will emit the event BLOCK_JOB_CANCELLED. However, note that if you cancel a ‘mirror’ job after it has indicated (via the event BLOCK_JOB_READY) that the source and target have reached synchronization, then the event emitted by block-job-cancel changes to BLOCK_JOB_COMPLETED.
[*] https://git.qemu.org/?p=qemu.git;a=blob;f=docs/interop/live-block-operations.rst#l515
Quoted 4 lines
So what we can change later is adding a way to initiate this secondary completion mode with a job-* command (probably with a new option for job-complete). But we wouldn't change the semantics of exisiting commands.
Ah, so if I'm understanding it correctly, the existing subtle magic of "complete-by-cancel" will be rectified by separting the two distinct
operations: 'job-cancel' and 'job-complete'.
Signature · 1 line
/kashyap
Re: [Qemu-devel] [Qemu-block] [PATCH v2 21/40] job: Convert block_job_cancel_async() to Job
Quoted 47 lines · Kashyap Chamarthy
Quoted 1 line · Kevin Wolf
Am 24.05.2018 um 19:42 hat John Snow geschrieben:
[...]
(Randomly chiming in for a small clarification.)
Quoted 18 lines
Quoted 18 lines
Quoted 4 lines
Quoted 3 lines
or some other mechanism that accomplishes the same type of behavior. It would be nice if it did not have to be determined at job creation time but instead could be determined later.
Quoted 8 lines
I agree. We already made sure that job-cancel really means cancel on the QAPI level, so we're free to do that. We just need to keep supporting block-job-cancel with the old semantics, so what I have is the easy conversion. We can change the internal implementation when we actually implement the selection of a completion mode.
Kevin
We need this before 3.0 though, yeah? unless we make job-cancel x-job-cancel or some other warning that the way it works might change, yeah?
Or do I misunderstand our leeway to change this at a later point in time?
Quoted 8 lines
Quoted 2 lines
(can job-cancel apply to block jobs created with the legacy infrastructure? My reading was "yes.")
It can, and it already has its final semantics, so nothing has to change before 3.0. job-cancel is equivalent to block-job-cancel with fixed force=true. If you want the complete-by-cancel behaviour of mirror, you have to use block-job-cancel for now, because job-cancel doesn't provide that functionality.
I think by "complete-by-cancel" you are referring to this[*] magic behaviour, mentioned in the last sentence here:
When you cancel an in-progress ‘mirror’ job before the source and target are synchronized, block-job-cancel will emit the event
BLOCK_JOB_CANCELLED. However, note that if you cancel a ‘mirror’ job after it has indicated (via the eventBLOCK_JOB_READY) that the source and target have reached synchronization, then the event emitted by block-job-cancel changes toBLOCK_JOB_COMPLETED.[*] https://git.qemu.org/?p=qemu.git;a=blob;f=docs/interop/live-block-operations.rst#l515
Quoted 8 lines
Quoted 4 lines
So what we can change later is adding a way to initiate this secondary completion mode with a job-* command (probably with a new option for job-complete). But we wouldn't change the semantics of exisiting commands.
Ah, so if I'm understanding it correctly, the existing subtle magic of "complete-by-cancel" will be rectified by separting the two distinct
operations: 'job-cancel' and 'job-complete'.
Not really, because we already had those two operations for block jobs. The thing is that block-job-complete (and thus job-complete) will pivot to the target disk, but block-job-cancel doesn't.
The special behavior is that you can use block-job-cancel after BLOCK_JOB_READY to complete the job, but not pivot to it. I don't think we have a real plan on how to represent that with the generic job commands, we just know that we don't want to use job-cancel.
(Maybe we can add a flag to job-complete (which to me does not sound like a good idea), or you could set flags on jobs while they are running, so you can set a do-not-pivot flag on the mirror job before you complete it.)
Max
Re: [Qemu-devel] [Qemu-block] [PATCH v2 21/40] job: Convert block_job_cancel_async() to Job
Quoted 2 lines · Max Reitz
Quoted 1 line · Kashyap Chamarthy
On Fri, May 25, 2018 at 10:00:35AM +0200, Kevin Wolf wrote:
[...]
Quoted 24 lines
Quoted 24 lines
Quoted 5 lines
It can, and it already has its final semantics, so nothing has to change before 3.0. job-cancel is equivalent to block-job-cancel with fixed force=true. If you want the complete-by-cancel behaviour of mirror, you have to use block-job-cancel for now, because job-cancel doesn't provide that functionality.
I think by "complete-by-cancel" you are referring to this[*] magic behaviour, mentioned in the last sentence here:
When you cancel an in-progress ‘mirror’ job before the source and target are synchronized, block-job-cancel will emit the event
BLOCK_JOB_CANCELLED. However, note that if you cancel a ‘mirror’ job after it has indicated (via the eventBLOCK_JOB_READY) that the source and target have reached synchronization, then the event emitted by block-job-cancel changes toBLOCK_JOB_COMPLETED.[*] https://git.qemu.org/?p=qemu.git;a=blob;f=docs/interop/live-block-operations.rst#l515
Quoted 4 lines
So what we can change later is adding a way to initiate this secondary completion mode with a job-* command (probably with a new option for job-complete). But we wouldn't change the semantics of exisiting commands.
Quoted 7 lines
Quoted 3 lines
Ah, so if I'm understanding it correctly, the existing subtle magic of "complete-by-cancel" will be rectified by separting the two distinct
operations: 'job-cancel' and 'job-complete'.Not really, because we already had those two operations for block jobs. The thing is that block-job-complete (and thus job-complete) will pivot to the target disk, but block-job-cancel doesn't.
Indeed; from the doc linked earlier: "Issuing the command ``block-job-complete`` (after it emits the event ``BLOCK_JOB_COMPLETED``) will adjust the guest device (i.e. live QEMU) to point to the target image, [E], causing all the new writes from this point on to happen there."
Quoted 4 lines
The special behavior is that you can use block-job-cancel after
BLOCK_JOB_READYto complete the job, but not pivot to it. I don't think we have a real plan on how to represent that with the generic job commands, we just know that we don't want to use job-cancel.
Ah, thanks for clarifying. Yes, what you say makes sense — not using 'job-cancel' to represent completion.
Quoted 4 lines
(Maybe we can add a flag to job-complete (which to me does not sound like a good idea), or you could set flags on jobs while they are running, so you can set a do-not-pivot flag on the mirror job before you complete it.)
Yeah, spelling that out, 'do-not-pivot' or something along those lines, as a flag makes it clearer. "Implicit is better than explicit".
Signature · 1 line
/kashyap
Re: [Qemu-devel] [Qemu-block] [PATCH v2 21/40] job: Convert block_job_cancel_async() to Job
Quoted 1 line · Kashyap Chamarthy
On Tue, May 29, 2018 at 02:30:47PM +0200, Max Reitz wrote:
[...]
Quoted 8 lines
Quoted 4 lines
The special behavior is that you can use block-job-cancel after
BLOCK_JOB_READYto complete the job, but not pivot to it. I don't think we have a real plan on how to represent that with the generic job commands, we just know that we don't want to use job-cancel.Ah, thanks for clarifying. Yes, what you say makes sense — not using 'job-cancel' to represent completion.
Quoted 7 lines
Quoted 4 lines
(Maybe we can add a flag to job-complete (which to me does not sound like a good idea), or you could set flags on jobs while they are running, so you can set a do-not-pivot flag on the mirror job before you complete it.)
Yeah, spelling that out, 'do-not-pivot' or something along those lines, as a flag makes it clearer. "Implicit is better than explicit".
Oops, I got the quote reverse :P. Correct: "Explicit is better than implicit". But you've auto-corrected in your head already...
Signature · 1 line
/kashyap
Re: [Qemu-devel] [Qemu-block] [PATCH v2 21/40] job: Convert block_job_cancel_async() to Job
In reply to Max ReitzQuoted 66 lines · Max Reitz
Quoted 47 lines · Kashyap Chamarthy
Quoted 1 line · Kevin Wolf
Am 24.05.2018 um 19:42 hat John Snow geschrieben:
[...]
(Randomly chiming in for a small clarification.)
Quoted 18 lines
Quoted 18 lines
Quoted 4 lines
Quoted 3 lines
or some other mechanism that accomplishes the same type of behavior. It would be nice if it did not have to be determined at job creation time but instead could be determined later.
Quoted 8 lines
I agree. We already made sure that job-cancel really means cancel on the QAPI level, so we're free to do that. We just need to keep supporting block-job-cancel with the old semantics, so what I have is the easy conversion. We can change the internal implementation when we actually implement the selection of a completion mode.
Kevin
We need this before 3.0 though, yeah? unless we make job-cancel x-job-cancel or some other warning that the way it works might change, yeah?
Or do I misunderstand our leeway to change this at a later point in time?
Quoted 8 lines
Quoted 2 lines
(can job-cancel apply to block jobs created with the legacy infrastructure? My reading was "yes.")
It can, and it already has its final semantics, so nothing has to change before 3.0. job-cancel is equivalent to block-job-cancel with fixed force=true. If you want the complete-by-cancel behaviour of mirror, you have to use block-job-cancel for now, because job-cancel doesn't provide that functionality.
I think by "complete-by-cancel" you are referring to this[*] magic behaviour, mentioned in the last sentence here:
When you cancel an in-progress ‘mirror’ job before the source and target are synchronized, block-job-cancel will emit the event
BLOCK_JOB_CANCELLED. However, note that if you cancel a ‘mirror’ job after it has indicated (via the eventBLOCK_JOB_READY) that the source and target have reached synchronization, then the event emitted by block-job-cancel changes toBLOCK_JOB_COMPLETED.[*] https://git.qemu.org/?p=qemu.git;a=blob;f=docs/interop/live-block-operations.rst#l515
Quoted 8 lines
Quoted 4 lines
So what we can change later is adding a way to initiate this secondary completion mode with a job-* command (probably with a new option for job-complete). But we wouldn't change the semantics of exisiting commands.
Ah, so if I'm understanding it correctly, the existing subtle magic of "complete-by-cancel" will be rectified by separting the two distinct
operations: 'job-cancel' and 'job-complete'.Not really, because we already had those two operations for block jobs. The thing is that block-job-complete (and thus job-complete) will pivot to the target disk, but block-job-cancel doesn't.
The special behavior is that you can use block-job-cancel after
BLOCK_JOB_READYto complete the job, but not pivot to it. I don't think we have a real plan on how to represent that with the generic job commands, we just know that we don't want to use job-cancel.
Quoted 7 lines
(Maybe we can add a flag to job-complete (which to me does not sound like a good idea), or you could set flags on jobs while they are running, so you can set a do-not-pivot flag on the mirror job before you complete it.)
Max
Adding the completion-mode as a "setting" to the job has always been my favorite way. Whether you set this setting at creation time or later during runtime is not important.
When we issue job-complete, the job knows based on its own settings the way it should do so, if there are multiple possibilities.
I only mentioned it as a flag to complete ... err, just in case it was easier that way somehow. *shrug*.
PATCH v2 22/40 job: Add job_drain()
In reply to Kevin Wolfblock_job_drain() contains a blk_drain() call which cannot be moved to Job, so add a new JobDriver callback JobDriver.drain which has a common implementation for all BlockJobs. In addition to this we keep the existing BlockJobDriver.drain callback that is called by the common drain implementation for all block jobs.
--- include/block/blockjob_int.h | 12 ++++++++++++ include/qemu/job.h | 13 +++++++++++++ block/backup.c | 1 + block/commit.c | 1 + block/mirror.c | 2 ++ block/stream.c | 1 + blockjob.c | 20 ++++++++++---------- job.c | 11 +++++++++++ tests/test-bdrv-drain.c | 1 + tests/test-blockjob-txn.c | 1 + tests/test-blockjob.c | 2 ++ 11 files changed, 55 insertions(+), 10 deletions(-)
include/block/blockjob_int.h +12 -0
@@ -65,6 +65,10 @@ struct BlockJobDriver {* If the callback is not NULL, it will be invoked when the job has to be * synchronously cancelled or completed; it should drain BlockDriverStates * as required to ensure progress. +* +* Block jobs must use the default implementation for job_driver.drain, +* which will in turn call this callback after doing generic block job +* stuff. */ void (*drain)(BlockJob *job); };
@@ -112,6 +116,14 @@ void block_job_free(Job *job);void block_job_user_resume (Job *job);/** +* block_job_drain: +* Callback to be used for JobDriver.drain in all block jobs. Drains the main +* block node associated with the block jobs and calls BlockJobDriver.drain for +* job-specific actions. +*/ +void block_job_drain (Job *job); + +/** * block_job_yield: * @job: The job that calls the function. *
include/qemu/job.h +13 -0
@@ -167,6 +167,13 @@ struct JobDriver {*/ void (*user_resume)(Job *job); +/* +* If the callback is not NULL, it will be invoked when the job has to be +* synchronously cancelled or completed; it should drain any activities +* as required to ensure progress. +*/ +void (*drain)(Job *job); +/** * If the callback is not NULL, it will be invoked when all the jobs * belonging to the same transaction complete; or upon this job's
@@ -325,6 +332,12 @@ bool job_user_paused(Job *job);*/ void job_user_resume (Job *job,Error **errp); +/* +* Drain any activities as required to ensure progress. This can be called in a +* loop to synchronously complete a job. +*/ +void job_drain (Job *job); +/** * Get the next element from the list of block jobs after @job, or the * first one if @job is %NULL.
block/backup.c +1 -0
@@ -529,6 +529,7 @@ static const BlockJobDriver backup_job_driver = { .job_type = JOB_TYPE_BACKUP, .free = block_job_free, .user_resume = block_job_user_resume, + .drain = block_job_drain, .start = backup_run, .commit = backup_commit, .abort = backup_abort,
block/commit.c +1 -0
@@ -221,6 +221,7 @@ static const BlockJobDriver commit_job_driver = { .job_type = JOB_TYPE_COMMIT, .free = block_job_free, .user_resume = block_job_user_resume, + .drain = block_job_drain, .start = commit_run, }, };
block/mirror.c +2 -0
@@ -992,6 +992,7 @@ static const BlockJobDriver mirror_job_driver = { .job_type = JOB_TYPE_MIRROR, .free = block_job_free, .user_resume = block_job_user_resume, + .drain = block_job_drain, .start = mirror_run, .pause = mirror_pause, },
@@ -1006,6 +1007,7 @@ static const BlockJobDriver commit_active_job_driver = { .job_type = JOB_TYPE_COMMIT, .free = block_job_free, .user_resume = block_job_user_resume, + .drain = block_job_drain, .start = mirror_run, .pause = mirror_pause, },
block/stream.c +1 -0
@@ -215,6 +215,7 @@ static const BlockJobDriver stream_job_driver = { .free = block_job_free, .start = stream_run, .user_resume = block_job_user_resume, + .drain = block_job_drain, }, };
blockjob.c +10 -10
@@ -169,14 +169,13 @@ static void block_job_attached_aio_context(AioContext *new_context,job_resume (&job->job); } -static void block_job_drain (BlockJob *job) +void block_job_drain (Job *job) { -/* If job is !job->job.busy this kicks it into the next pause point. */ -block_job_enter (job); +BlockJob *bjob =container_of (job, BlockJob, job); -blk_drain (job->blk); -if (job->driver->drain) { - job->driver->drain (job); +blk_drain (bjob->blk); +if (bjob->driver->drain) { + bjob->driver->drain (bjob); } }
@@ -190,7 +189,7 @@ static void block_job_detach_aio_context(void *opaque)job_pause (&job->job);while (!job->job.paused && !job_is_completed (&job->job)) { -block_job_drain (job); +job_drain (&job->job); } job->job.aio_context =NULL ;
@@ -327,11 +326,11 @@ static int block_job_finish_sync(BlockJob *job,job_unref (&job->job);return -EBUSY ; } -/* block_job_drain calls block_job_enter, and it should be enough to -* induce progress until the job completes or moves to the main thread. +/* job_drain calls job_enter, and it should be enough to induce progress +* until the job completes or moves to the main thread. */ while (!job->job.deferred_to_main_loop && !job_is_completed (&job->job)) { -block_job_drain (job); +job_drain (&job->job); }while (!job_is_completed (&job->job)) {aio_poll (qemu_get_aio_context (), true);
@@ -713,6 +712,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,assert (is_block_job (&job->job));assert (job->job.driver->free == &block_job_free);assert (job->job.driver->user_resume == &block_job_user_resume); +assert (job->job.driver->drain == &block_job_drain); job->driver = driver; job->blk = blk;
job.c +11 -0
@@ -367,6 +367,17 @@ void coroutine_fn job_sleep_ns(Job *job, int64_t ns)job_pause_point (job); } +void job_drain (Job *job) +{ +/* If job is !busy this kicks it into the next pause point. */ +job_enter (job); + +if (job->driver->drain) { + job->driver->drain (job); + } +} + +/** * All jobs must allow a pause point before entering their job proper. This * ensures that jobs can be paused prior to being started, then resumed later.
tests/test-bdrv-drain.c +1 -0
@@ -525,6 +525,7 @@ BlockJobDriver test_job_driver = { .instance_size = sizeof(TestBlockJob), .free = block_job_free, .user_resume = block_job_user_resume, + .drain = block_job_drain, .start = test_job_start, }, .complete = test_job_complete,
tests/test-blockjob-txn.c +1 -0
@@ -79,6 +79,7 @@ static const BlockJobDriver test_block_job_driver = { .instance_size = sizeof(TestBlockJob), .free = block_job_free, .user_resume = block_job_user_resume, + .drain = block_job_drain, .start = test_block_job_run, }, };
tests/test-blockjob.c +2 -0
@@ -21,6 +21,7 @@ static const BlockJobDriver test_block_job_driver = { .instance_size = sizeof(BlockJob), .free = block_job_free, .user_resume = block_job_user_resume, + .drain = block_job_drain, }, };
@@ -201,6 +202,7 @@ static const BlockJobDriver test_cancel_driver = { .instance_size = sizeof(CancelJob), .free = block_job_free, .user_resume = block_job_user_resume, + .drain = block_job_drain, .start = cancel_job_start, }, .complete = cancel_job_complete,
Signature · 1 line
2.13.6
PATCH v2 23/40 job: Move .complete callback to Job
In reply to Kevin WolfThis moves the .complete callback that tells a READY job to complete from BlockJobDriver to JobDriver. The wrapper function job_complete() doesn't require anything block job specific any more and can be moved to Job.
--- include/block/blockjob.h | 10 ---------- include/block/blockjob_int.h | 6 ------ include/qemu/job.h | 8 ++++++++ block/mirror.c | 10 +++++----- blockdev.c | 2 +- blockjob.c | 23 +++++------------------ job.c | 16 ++++++++++++++++ tests/test-bdrv-drain.c | 6 +++--- tests/test-blockjob.c | 10 +++++----- 9 files changed, 43 insertions(+), 48 deletions(-)
include/block/blockjob.h +0 -10
@@ -154,16 +154,6 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp);void block_job_cancel (BlockJob *job,bool force);/** -* block_job_complete: -* @job: The job to be completed. -* @errp: Error object. -* -* Asynchronously complete the specified job. -*/ -void block_job_complete (BlockJob *job,Error **errp); - - -/** * block_job_finalize: * @job: The job to fully commit and finish. * @errp: Error object.
include/block/blockjob_int.h +0 -6
@@ -39,12 +39,6 @@ struct BlockJobDriver {JobDriver job_driver;/** -* Optional callback for job types whose completion must be triggered -* manually. -*/ -void (*complete)(BlockJob *job,Error **errp); - -/** * If the callback is not NULL, prepare will be invoked when all the jobs * belonging to the same transaction complete; or upon this job's completion * if it is not in a transaction.
include/qemu/job.h +8 -0
@@ -167,6 +167,12 @@ struct JobDriver {*/ void (*user_resume)(Job *job); +/** +* Optional callback for job types whose completion must be triggered +* manually. +*/ +void (*complete)(Job *job,Error **errp); +/* * If the callback is not NULL, it will be invoked when the job has to be * synchronously cancelled or completed; it should drain any activities
@@ -363,6 +369,8 @@ int job_apply_verb(Job *job, JobVerb verb, Error **errp);/** The @job could not be started, free it. */ void job_early_fail (Job *job); +/** Asynchronously complete the specified @job. */ +void job_complete (Job *job,Error **errp);;typedef void JobDeferToMainLoopFn (Job *job,void *opaque);
block/mirror.c +5 -5
@@ -905,16 +905,16 @@ immediate_exit:job_defer_to_main_loop (&s->common.job, mirror_exit, data); } -static void mirror_complete (BlockJob *job,Error **errp) +static void mirror_complete (Job *job,Error **errp) { -MirrorBlockJob *s =container_of (job, MirrorBlockJob, common); +MirrorBlockJob *s =container_of (job, MirrorBlockJob, common.job);BlockDriverState *target; target =blk_bs (s->target);if (!s->synced) {error_setg (errp,"The active block job '%s' cannot be completed" , - job->job.id); + job->id);return ; }
@@ -995,8 +995,8 @@ static const BlockJobDriver mirror_job_driver = { .drain = block_job_drain, .start = mirror_run, .pause = mirror_pause, + .complete = mirror_complete, }, - .complete = mirror_complete, .attached_aio_context = mirror_attached_aio_context, .drain = mirror_drain, };
@@ -1010,8 +1010,8 @@ static const BlockJobDriver commit_active_job_driver = { .drain = block_job_drain, .start = mirror_run, .pause = mirror_pause, + .complete = mirror_complete, }, - .complete = mirror_complete, .attached_aio_context = mirror_attached_aio_context, .drain = mirror_drain, };
blockdev.c +1 -1
@@ -3894,7 +3894,7 @@ void qmp_block_job_complete(const char *device, Error **errp) }trace_qmp_block_job_complete (job); -block_job_complete (job, errp); +job_complete (&job->job, errp);aio_context_release (aio_context); }
blockjob.c +5 -18
@@ -481,24 +481,6 @@ int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n)return ratelimit_calculate_delay (&job->limit, n); } -void block_job_complete (BlockJob *job,Error **errp) -{ -/* Should not be reachable via external interface for internal jobs */ -assert (job->job.id); -if (job_apply_verb (&job->job,JOB_VERB_COMPLETE , errp)) { -return ; - } -if (job->job.pause_count ||job_is_cancelled (&job->job) || - !job->driver->complete) - { -error_setg (errp,"The active block job '%s' cannot be completed" , - job->job.id); -return ; - } - - job->driver->complete (job, errp); -} -void block_job_finalize (BlockJob *job,Error **errp) {assert (job && job->job.id);
@@ -571,6 +553,11 @@ void block_job_cancel_sync_all(void) } } +static void block_job_complete (BlockJob *job,Error **errp) +{ +job_complete (&job->job, errp); +} +int block_job_complete_sync (BlockJob *job,Error **errp) {return block_job_finish_sync (job, &block_job_complete, errp);
job.c +16 -0
@@ -556,6 +556,22 @@ int job_finalize_single(Job *job)return 0 ; } +void job_complete (Job *job,Error **errp) +{ +/* Should not be reachable via external interface for internal jobs */ +assert (job->id); +if (job_apply_verb (job,JOB_VERB_COMPLETE , errp)) { +return ; + } +if (job->pause_count ||job_is_cancelled (job) || !job->driver->complete) { +error_setg (errp,"The active block job '%s' cannot be completed" , + job->id); +return ; + } + + job->driver->complete (job, errp); +} + typedef struct { Job *job;
tests/test-bdrv-drain.c +3 -3
@@ -514,9 +514,9 @@ static void coroutine_fn test_job_start(void *opaque)job_defer_to_main_loop (&s->common.job, test_job_completed,NULL ); } -static void test_job_complete (BlockJob *job,Error **errp) +static void test_job_complete (Job *job,Error **errp) { -TestBlockJob *s =container_of (job, TestBlockJob, common); +TestBlockJob *s =container_of (job, TestBlockJob, common.job); s->should_complete = true; }
@@ -527,8 +527,8 @@ BlockJobDriver test_job_driver = { .user_resume = block_job_user_resume, .drain = block_job_drain, .start = test_job_start, + .complete = test_job_complete, }, - .complete = test_job_complete, };static void test_blockjob_common (enum drain_type drain_type)
tests/test-blockjob.c +5 -5
@@ -171,9 +171,9 @@ static void cancel_job_completed(Job *job, void *opaque)block_job_completed (bjob,0 ); } -static void cancel_job_complete (BlockJob *job,Error **errp) +static void cancel_job_complete (Job *job,Error **errp) { -CancelJob *s =container_of (job, CancelJob, common); +CancelJob *s =container_of (job, CancelJob, common.job); s->should_complete = true; }
@@ -204,8 +204,8 @@ static const BlockJobDriver test_cancel_driver = { .user_resume = block_job_user_resume, .drain = block_job_drain, .start = cancel_job_start, + .complete = cancel_job_complete, }, - .complete = cancel_job_complete, };static CancelJob *create_common (BlockJob **pjob)
@@ -333,7 +333,7 @@ static void test_cancel_pending(void)block_job_enter (job);assert (job->job.status ==JOB_STATUS_READY ); -block_job_complete (job, &error_abort); +job_complete (&job->job, &error_abort);block_job_enter (job);while (!s->completed) {aio_poll (qemu_get_aio_context (), true);
@@ -357,7 +357,7 @@ static void test_cancel_concluded(void)block_job_enter (job);assert (job->job.status ==JOB_STATUS_READY ); -block_job_complete (job, &error_abort); +job_complete (&job->job, &error_abort);block_job_enter (job);while (!s->completed) {aio_poll (qemu_get_aio_context (), true);
Signature · 1 line
2.13.6
PATCH v2 24/40 job: Move job_finish_sync() to Job
In reply to Kevin Wolfblock_job_finish_sync() doesn't contain anything block job specific any more, so it can be moved to Job.
--- include/qemu/job.h | 9 +++++++++ block/commit.c | 6 +++--- blockjob.c | 55 +++++++++--------------------------------------------- job.c | 28 +++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 49 deletions(-)
include/qemu/job.h +9 -0
@@ -389,6 +389,15 @@ typedef void JobDeferToMainLoopFn(Job *job, void *opaque);*/ void job_defer_to_main_loop (Job *job,JobDeferToMainLoopFn *fn,void *opaque); +/** +* Synchronously finishes the given @job. If @finish is given, it is called to +* trigger completion or cancellation of the job. +* +* Returns 0 if the job is successfully completed, -ECANCELED if the job was +* cancelled before completing, and -errno in other error cases. +*/ +int job_finish_sync (Job *job,void (*finish)(Job *,Error **errp),Error **errp); +/* TODO To be removed from the public interface */ void job_state_transition (Job *job,JobStatus s1); void coroutine_fn job_do_yield(Job *job, uint64_t ns);
block/commit.c +3 -3
@@ -112,9 +112,9 @@ static void commit_complete(Job *job, void *opaque)blk_unref (s->top);/* If there is more than one reference to the job (e.g. if called from -* block_job_finish_sync()), block_job_completed() won't free it and -* therefore the blockers on the intermediate nodes remain. This would -* cause bdrv_set_backing_hd() to fail. */ +* job_finish_sync()), block_job_completed() won't free it and therefore +* the blockers on the intermediate nodes remain. This would cause +* bdrv_set_backing_hd() to fail. */ block_job_remove_all_bdrv (bjob);block_job_completed (&s->common, ret);
blockjob.c +9 -46
86 lines not on this page · raw message
job.c +28 -0
@@ -603,3 +603,31 @@ void job_defer_to_main_loop(Job *job, JobDeferToMainLoopFn *fn, void *opaque)aio_bh_schedule_oneshot (qemu_get_aio_context (), job_defer_to_main_loop_bh, data); } + +int job_finish_sync (Job *job,void (*finish)(Job *,Error **errp),Error **errp) +{ +Error *local_err =NULL ; +int ret; + +job_ref (job); + +if (finish) { +finish (job, &local_err); + } +if (local_err) { +error_propagate (errp, local_err); +job_unref (job); +return -EBUSY ; + } +/* job_drain calls job_enter, and it should be enough to induce progress +* until the job completes or moves to the main thread. */ +while (!job->deferred_to_main_loop && !job_is_completed (job)) { +job_drain (job); + } +while (!job_is_completed (job)) { +aio_poll (qemu_get_aio_context (), true); + } + ret = (job_is_cancelled (job) && job->ret ==0 ) ? -ECANCELED : job->ret; +job_unref (job); +return ret; +}
Signature · 1 line
2.13.6
PATCH v2 25/40 job: Switch transactions to JobTxn
In reply to Kevin WolfThis doesn't actually move any transaction code to Job yet, but it renames the type for transactions from BlockJobTxn to JobTxn and makes them contain Jobs rather than BlockJobs
--- include/block/block_int.h | 2 +- include/block/blockjob.h | 11 ++++---- include/block/blockjob_int.h | 2 +- include/qemu/job.h | 3 +++ block/backup.c | 2 +- blockdev.c | 14 +++++------ blockjob.c | 60 +++++++++++++++++++++++--------------------- tests/test-blockjob-txn.c | 8 +++--- 8 files changed, 54 insertions(+), 48 deletions(-)
include/block/block_int.h +1 -1
@@ -1029,7 +1029,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,BlockdevOnError on_target_error, int creation_flags, BlockCompletionFunc *cb, void *opaque, - BlockJobTxn *txn, Error **errp); + JobTxn *txn, Error **errp);void hmp_drive_add_node (Monitor *mon,const char *optstr);
include/block/blockjob.h +5 -6
@@ -33,7 +33,7 @@#define BLOCK_JOB_SLICE_TIME 100000000ULL/* ns */ typedef struct BlockJobDriver BlockJobDriver ; -typedef struct BlockJobTxn BlockJobTxn ; +typedef struct JobTxn JobTxn ;/** * BlockJob:
@@ -85,8 +85,7 @@ typedef struct BlockJob {/** BlockDriverStates that are involved in this block job */ GSList *nodes; -BlockJobTxn *txn; -QLIST_ENTRY (BlockJob) txn_list; +JobTxn *txn; } BlockJob;/**
@@ -273,7 +272,7 @@ void block_job_iostatus_reset(BlockJob *job);* group. Jobs wait for each other before completing. Cancelling one job * cancels all jobs in the transaction. */ -BlockJobTxn *block_job_txn_new (void ); +JobTxn *block_job_txn_new (void );/** * block_job_txn_unref:
@@ -282,7 +281,7 @@ BlockJobTxn *block_job_txn_new(void);* or block_job_txn_new. If it's the last reference to the object, it will be * freed. */ -void block_job_txn_unref (BlockJobTxn *txn); +void block_job_txn_unref (JobTxn *txn);/** * block_job_txn_add_job:
@@ -293,7 +292,7 @@ void block_job_txn_unref(BlockJobTxn *txn);* The caller must call either block_job_txn_unref() or block_job_completed() * to release the reference that is automatically grabbed here. */ -void block_job_txn_add_job (BlockJobTxn *txn,BlockJob *job); +void block_job_txn_add_job (JobTxn *txn,BlockJob *job);/** * block_job_is_internal:
include/block/blockjob_int.h +1 -1
@@ -91,7 +91,7 @@ struct BlockJobDriver {* called from a wrapper that is specific to the job type. */ void *block_job_create (const char *job_id,const BlockJobDriver *driver, -BlockJobTxn *txn,BlockDriverState *bs,uint64_t perm, +JobTxn *txn,BlockDriverState *bs,uint64_t perm,uint64_t shared_perm,int64_t speed,int flags,BlockCompletionFunc *cb,void *opaque,Error **errp);
include/qemu/job.h +3 -0
@@ -132,6 +132,9 @@ typedef struct Job {/** Element of the list of jobs */ QLIST_ENTRY (Job) job_list; + +/** Element of the list of jobs in a job transaction */ +QLIST_ENTRY (Job) txn_list; } Job;/**
block/backup.c +1 -1
@@ -547,7 +547,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,BlockdevOnError on_target_error, int creation_flags, BlockCompletionFunc *cb, void *opaque, - BlockJobTxn *txn, Error **errp) + JobTxn *txn, Error **errp) { int64_t len;BlockDriverInfo bdi;
blockdev.c +7 -7
@@ -1446,7 +1446,7 @@ typedef struct BlkActionOps {struct BlkActionState {TransactionAction *action;const BlkActionOps *ops; -BlockJobTxn *block_job_txn; +JobTxn *block_job_txn;TransactionProperties *txn_props;QSIMPLEQ_ENTRY (BlkActionState) entry; };
@@ -1864,7 +1864,7 @@ typedef struct DriveBackupState {BlockJob *job; } DriveBackupState; -static BlockJob *do_drive_backup (DriveBackup *backup,BlockJobTxn *txn, +static BlockJob *do_drive_backup (DriveBackup *backup,JobTxn *txn,Error **errp);static void drive_backup_prepare (BlkActionState *common,Error **errp)
@@ -1954,7 +1954,7 @@ typedef struct BlockdevBackupState {BlockJob *job; } BlockdevBackupState; -static BlockJob *do_blockdev_backup (BlockdevBackup *backup,BlockJobTxn *txn, +static BlockJob *do_blockdev_backup (BlockdevBackup *backup,JobTxn *txn,Error **errp);static void blockdev_backup_prepare (BlkActionState *common,Error **errp)
@@ -2243,7 +2243,7 @@ void qmp_transaction(TransactionActionList *dev_list, Error **errp) {TransactionActionList *dev_entry = dev_list; -BlockJobTxn *block_job_txn =NULL ; +JobTxn *block_job_txn =NULL ;BlkActionState *state, *next;Error *local_err =NULL ;
@@ -2251,7 +2251,7 @@ void qmp_transaction(TransactionActionList *dev_list,QSIMPLEQ_INIT (&snap_bdrv_states);/* Does this transaction get canceled as a group on failure? -* If not, we don't really need to make a BlockJobTxn. +* If not, we don't really need to make a JobTxn. */ props =get_transaction_properties (props);if (props->completion_mode !=ACTION_COMPLETION_MODE_INDIVIDUAL ) {
@@ -3264,7 +3264,7 @@ out:aio_context_release (aio_context); } -static BlockJob *do_drive_backup (DriveBackup *backup,BlockJobTxn *txn, +static BlockJob *do_drive_backup (DriveBackup *backup,JobTxn *txn,Error **errp) {BlockDriverState *bs;
@@ -3434,7 +3434,7 @@ BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp)return bdrv_named_nodes_list (errp); } -BlockJob *do_blockdev_backup (BlockdevBackup *backup,BlockJobTxn *txn, +BlockJob *do_blockdev_backup (BlockdevBackup *backup,JobTxn *txn,Error **errp) {BlockDriverState *bs;
blockjob.c +32 -28
162 lines not on this page · raw message
tests/test-blockjob-txn.c +4 -4
@@ -93,7 +93,7 @@ static const BlockJobDriver test_block_job_driver = {*/ static BlockJob *test_block_job_start (unsigned int iterations,bool use_timer, -int rc,int *result,BlockJobTxn *txn) +int rc,int *result,JobTxn *txn) {BlockDriverState *bs;TestBlockJob *s;
@@ -122,7 +122,7 @@ static BlockJob *test_block_job_start(unsigned int iterations,static void test_single_job (int expected) {BlockJob *job; -BlockJobTxn *txn; +JobTxn *txn;int result = -EINPROGRESS ; txn =block_job_txn_new ();
@@ -160,7 +160,7 @@ static void test_pair_jobs(int expected1, int expected2) {BlockJob *job1;BlockJob *job2; -BlockJobTxn *txn; +JobTxn *txn;int result1 = -EINPROGRESS ;int result2 = -EINPROGRESS ;
@@ -222,7 +222,7 @@ static void test_pair_jobs_fail_cancel_race(void) {BlockJob *job1;BlockJob *job2; -BlockJobTxn *txn; +JobTxn *txn;int result1 = -EINPROGRESS ;int result2 = -EINPROGRESS ;
Signature · 1 line
2.13.6
PATCH v2 26/40 job: Move transactions to Job
In reply to Kevin WolfThis moves the logic that implements job transactions from BlockJob to Job.
--- include/block/blockjob.h | 54 ---------- include/block/blockjob_int.h | 10 -- include/qemu/job.h | 71 +++++++++++-- blockdev.c | 6 +- blockjob.c | 238 +------------------------------------------ job.c | 234 ++++++++++++++++++++++++++++++++++++++++-- tests/test-blockjob-txn.c | 12 +-- tests/test-blockjob.c | 2 +- 8 files changed, 303 insertions(+), 324 deletions(-)
include/block/blockjob.h +0 -54
@@ -33,7 +33,6 @@#define BLOCK_JOB_SLICE_TIME 100000000ULL/* ns */ typedef struct BlockJobDriver BlockJobDriver ; -typedef struct JobTxn JobTxn ;/** * BlockJob:
@@ -84,8 +83,6 @@ typedef struct BlockJob {/** BlockDriverStates that are involved in this block job */ GSList *nodes; - -JobTxn *txn; } BlockJob;/**
@@ -153,22 +150,6 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp);void block_job_cancel (BlockJob *job,bool force);/** -* block_job_finalize: -* @job: The job to fully commit and finish. -* @errp: Error object. -* -* For jobs that have finished their work and are pending -* awaiting explicit acknowledgement to commit their work, -* This will commit that work. -* -* FIXME: Make the below statement universally true: -* For jobs that support the manual workflow mode, all graph -* changes that occur as a result will occur after this command -* and before a successful reply. -*/ -void block_job_finalize (BlockJob *job,Error **errp); - -/** * block_job_dismiss: * @job: The job to be dismissed. * @errp: Error object.
@@ -260,41 +241,6 @@ int block_job_complete_sync(BlockJob *job, Error **errp);void block_job_iostatus_reset (BlockJob *job);/** -* block_job_txn_new: -* -* Allocate and return a new block job transaction. Jobs can be added to the -* transaction using block_job_txn_add_job(). -* -* The transaction is automatically freed when the last job completes or is -* cancelled. -* -* All jobs in the transaction either complete successfully or fail/cancel as a -* group. Jobs wait for each other before completing. Cancelling one job -* cancels all jobs in the transaction. -*/ -JobTxn *block_job_txn_new (void ); - -/** -* block_job_txn_unref: -* -* Release a reference that was previously acquired with block_job_txn_add_job -* or block_job_txn_new. If it's the last reference to the object, it will be -* freed. -*/ -void block_job_txn_unref (JobTxn *txn); - -/** -* block_job_txn_add_job: -* @txn: The transaction (may be NULL) -* @job: Job to add to the transaction -* -* Add @job to the transaction. The @job must not already be in a transaction. -* The caller must call either block_job_txn_unref() or block_job_completed() -* to release the reference that is automatically grabbed here. -*/ -void block_job_txn_add_job (JobTxn *txn,BlockJob *job); - -/** * block_job_is_internal: * @job: The job to determine if it is user-visible or not. *
include/block/blockjob_int.h +0 -10
@@ -38,16 +38,6 @@ struct BlockJobDriver {/** Generic JobDriver callbacks and settings */ JobDriver job_driver; -/** -* If the callback is not NULL, prepare will be invoked when all the jobs -* belonging to the same transaction complete; or upon this job's completion -* if it is not in a transaction. -* -* This callback will not be invoked if the job has already failed. -* If it fails, abort and then clean will be called. -*/ -int (*prepare)(BlockJob *job); -/* * If the callback is not NULL, it will be invoked before the job is * resumed in a new AioContext. This is the place to move any resources
include/qemu/job.h +62 -9
125 lines not on this page · raw message
blockdev.c +3 -3
@@ -2255,7 +2255,7 @@ void qmp_transaction(TransactionActionList *dev_list,*/ props =get_transaction_properties (props);if (props->completion_mode !=ACTION_COMPLETION_MODE_INDIVIDUAL ) { - block_job_txn =block_job_txn_new (); + block_job_txn =job_txn_new (); }/* drain all i/o before any operations */
@@ -2314,7 +2314,7 @@ exit:if (!has_props) {qapi_free_TransactionProperties (props); } -block_job_txn_unref (block_job_txn); +job_txn_unref (block_job_txn); } void qmp_eject(bool has_device, const char *device,
@@ -3908,7 +3908,7 @@ void qmp_block_job_finalize(const char *id, Error **errp) }trace_qmp_block_job_finalize (job); -block_job_finalize (job, errp); +job_finalize (&job->job, errp);aio_context_release (aio_context); }
blockjob.c +5 -233
297 lines not on this page · raw message
job.c +226 -8
292 lines not on this page · raw message
tests/test-blockjob-txn.c +6 -6
@@ -125,7 +125,7 @@ static void test_single_job(int expected)JobTxn *txn;int result = -EINPROGRESS ; - txn =block_job_txn_new (); + txn =job_txn_new (); job =test_block_job_start (1 , true, expected, &result, txn);job_start (&job->job);
@@ -138,7 +138,7 @@ static void test_single_job(int expected) } g_assert_cmpint(result, ==, expected); -block_job_txn_unref (txn); +job_txn_unref (txn); }static void test_single_job_success (void )
@@ -164,7 +164,7 @@ static void test_pair_jobs(int expected1, int expected2)int result1 = -EINPROGRESS ;int result2 = -EINPROGRESS ; - txn =block_job_txn_new (); + txn =job_txn_new (); job1 =test_block_job_start (1 , true, expected1, &result1, txn); job2 =test_block_job_start (2 , true, expected2, &result2, txn);job_start (&job1->job);
@@ -173,7 +173,7 @@ static void test_pair_jobs(int expected1, int expected2)/* Release our reference now to trigger as many nice * use-after-free bugs as possible. */ -block_job_txn_unref (txn); +job_txn_unref (txn);if (expected1 == -ECANCELED ) {block_job_cancel (job1, false);
@@ -226,7 +226,7 @@ static void test_pair_jobs_fail_cancel_race(void)int result1 = -EINPROGRESS ;int result2 = -EINPROGRESS ; - txn =block_job_txn_new (); + txn =job_txn_new (); job1 =test_block_job_start (1 , true, -ECANCELED , &result1, txn); job2 =test_block_job_start (2 , false,0 , &result2, txn);job_start (&job1->job);
@@ -247,7 +247,7 @@ static void test_pair_jobs_fail_cancel_race(void) g_assert_cmpint(result1, ==, -ECANCELED); g_assert_cmpint(result2, ==, -ECANCELED); -block_job_txn_unref (txn); +job_txn_unref (txn); }int main (int argc,char **argv)
tests/test-blockjob.c +1 -1
@@ -364,7 +364,7 @@ static void test_cancel_concluded(void) }assert (job->job.status ==JOB_STATUS_PENDING ); -block_job_finalize (job, &error_abort); +job_finalize (&job->job, &error_abort);assert (job->job.status ==JOB_STATUS_CONCLUDED );cancel_common (s);
Signature · 1 line
2.13.6
PATCH v2 27/40 job: Move completion and cancellation to Job
In reply to Kevin WolfThis moves the top-level job completion and cancellation functions from BlockJob to Job.
--- include/block/blockjob.h | 55 ----------------------------- include/block/blockjob_int.h | 18 ---------- include/qemu/job.h | 68 +++++++++++++++++++++++++++++------ block.c | 4 ++- block/backup.c | 3 +- block/commit.c | 6 ++-- block/mirror.c | 6 ++-- block/replication.c | 4 +-- block/stream.c | 2 +- blockdev.c | 8 ++--- blockjob.c | 76 --------------------------------------- job.c | 84 +++++++++++++++++++++++++++++++++++++++----- qemu-img.c | 2 +- tests/test-bdrv-drain.c | 5 ++- tests/test-blockjob-txn.c | 14 ++++---- tests/test-blockjob.c | 21 ++++++----- block/trace-events | 3 -- trace-events | 1 + 18 files changed, 171 insertions(+), 209 deletions(-)
include/block/blockjob.h +0 -55
@@ -141,15 +141,6 @@ void block_job_remove_all_bdrv(BlockJob *job);void block_job_set_speed (BlockJob *job,int64_t speed,Error **errp);/** -* block_job_cancel: -* @job: The job to be canceled. -* @force: Quit a job without waiting for data to be in sync. -* -* Asynchronously cancel the specified job. -*/ -void block_job_cancel (BlockJob *job,bool force); - -/** * block_job_dismiss: * @job: The job to be dismissed. * @errp: Error object.
@@ -186,52 +177,6 @@ void block_job_progress_set_remaining(BlockJob *job, uint64_t remaining);BlockJobInfo *block_job_query (BlockJob *job,Error **errp);/** -* block_job_user_cancel: -* @job: The job to be cancelled. -* @force: Quit a job without waiting for data to be in sync. -* -* Cancels the specified job, but may refuse to do so if the -* operation isn't currently meaningful. -*/ -void block_job_user_cancel (BlockJob *job,bool force,Error **errp); - -/** -* block_job_cancel_sync: -* @job: The job to be canceled. -* -* Synchronously cancel the job. The completion callback is called -* before the function returns. The job may actually complete -* instead of canceling itself; the circumstances under which this -* happens depend on the kind of job that is active. -* -* Returns the return value from the job if the job actually completed -* during the call, or -ECANCELED if it was canceled. -*/ -int block_job_cancel_sync (BlockJob *job); - -/** -* block_job_cancel_sync_all: -* -* Synchronously cancels all jobs using block_job_cancel_sync(). -*/ -void block_job_cancel_sync_all (void ); - -/** -* block_job_complete_sync: -* @job: The job to be completed. -* @errp: Error object which may be set by block_job_complete(); this is not -* necessarily set on every error, the job return value has to be -* checked as well. -* -* Synchronously complete the job. The completion callback is called before the -* function returns, unless it is NULL (which is permissible when using this -* function). -* -* Returns the return value from the job. -*/ -int block_job_complete_sync (BlockJob *job,Error **errp); - -/** * block_job_iostatus_reset: * @job: The job whose I/O status should be reset. *
include/block/blockjob_int.h +0 -18
@@ -124,24 +124,6 @@ void block_job_yield(BlockJob *job);int64_t block_job_ratelimit_get_delay (BlockJob *job,uint64_t n);/** -* block_job_completed: -* @job: The job being completed. -* @ret: The status code. -* -* Call the completion function that was registered at creation time, and -* free @job. -*/ -void block_job_completed (BlockJob *job,int ret); - -/** -* block_job_enter: -* @job: The job to enter. -* -* Continue the specified job by entering the coroutine. -*/ -void block_job_enter (BlockJob *job); - -/** * block_job_event_ready: * @job: The job which is now ready to be completed. *
include/qemu/job.h +57 -11
99 lines not on this page · raw message
block.c +3 -1
@@ -3362,7 +3362,9 @@ static void bdrv_close(BlockDriverState *bs)void bdrv_close_all (void ) { -block_job_cancel_sync_all (); +/* TODO We do want to cancel all jobs instead of just block jobs on +* shutdown, but bdrv_close_all() isn't the right place any more. */ +job_cancel_sync_all ();nbd_export_close_all ();/* Drop references from requests still in flight, such as canceled block
block/backup.c +1 -2
@@ -319,10 +319,9 @@ typedef struct {static void backup_complete (Job *job,void *opaque) { -BlockJob *bjob =container_of (job, BlockJob, job);BackupCompleteData *data = opaque; -block_job_completed (bjob, data->ret); +job_completed (job, data->ret);g_free (data); }
block/commit.c +3 -3
@@ -112,12 +112,12 @@ static void commit_complete(Job *job, void *opaque)blk_unref (s->top);/* If there is more than one reference to the job (e.g. if called from -* job_finish_sync()), block_job_completed() won't free it and therefore -* the blockers on the intermediate nodes remain. This would cause +* job_finish_sync()), job_completed() won't free it and therefore the +* blockers on the intermediate nodes remain. This would cause * bdrv_set_backing_hd() to fail. */ block_job_remove_all_bdrv (bjob); -block_job_completed (&s->common, ret); +job_completed (job, ret);g_free (data);/* If bdrv_drop_intermediate() didn't already do that, remove the commit
block/mirror.c +3 -3
@@ -498,7 +498,7 @@ static void mirror_exit(Job *job, void *opaque)bdrv_release_dirty_bitmap (src, s->dirty_bitmap);/* Make sure that the source BDS doesn't go away before we called -* block_job_completed(). */ +* job_completed(). */ bdrv_ref (src);bdrv_ref (mirror_top_bs);bdrv_ref (target_bs);
@@ -581,7 +581,7 @@ static void mirror_exit(Job *job, void *opaque)blk_set_perm (bjob->blk,0 ,BLK_PERM_ALL , &error_abort);blk_insert_bs (bjob->blk, mirror_top_bs, &error_abort); -block_job_completed (&s->common, data->ret); +job_completed (job, data->ret);g_free (data);bdrv_drained_end (src);
@@ -954,7 +954,7 @@ static void mirror_complete(Job *job, Error **errp) } s->should_complete = true; -block_job_enter (&s->common); +job_enter (job); }static void mirror_pause (Job *job)
block/replication.c +2 -2
@@ -145,7 +145,7 @@ static void replication_close(BlockDriverState *bs)replication_stop (s->rs, false,NULL ); }if (s->stage ==BLOCK_REPLICATION_FAILOVER ) { -block_job_cancel_sync (s->active_disk->bs->job); +job_cancel_sync (&s->active_disk->bs->job->job); }if (s->mode ==REPLICATION_MODE_SECONDARY ) {
@@ -681,7 +681,7 @@ static void replication_stop(ReplicationState *rs, bool failover, Error **errp)* disk, secondary disk in backup_job_completed(). */ if (s->secondary_disk->bs->job) { -block_job_cancel_sync (s->secondary_disk->bs->job); +job_cancel_sync (&s->secondary_disk->bs->job->job); }if (!failover) {
block/stream.c +1 -1
@@ -93,7 +93,7 @@ out: }g_free (s->backing_file_str); -block_job_completed (&s->common, data->ret); +job_completed (job, data->ret);g_free (data); }
blockdev.c +4 -4
@@ -150,7 +150,7 @@ void blockdev_mark_auto_del(BlockBackend *blk)aio_context_acquire (aio_context);if (bs->job) { -block_job_cancel (bs->job, false); +job_cancel (&bs->job->job, false); }aio_context_release (aio_context);
@@ -1925,7 +1925,7 @@ static void drive_backup_abort(BlkActionState *common) aio_context =bdrv_get_aio_context (state->bs);aio_context_acquire (aio_context); -block_job_cancel_sync (state->job); +job_cancel_sync (&state->job->job);aio_context_release (aio_context); }
@@ -2023,7 +2023,7 @@ static void blockdev_backup_abort(BlkActionState *common) aio_context =bdrv_get_aio_context (state->bs);aio_context_acquire (aio_context); -block_job_cancel_sync (state->job); +job_cancel_sync (&state->job->job);aio_context_release (aio_context); }
@@ -3851,7 +3851,7 @@ void qmp_block_job_cancel(const char *device, }trace_qmp_block_job_cancel (job); -block_job_user_cancel (job, force, errp); +job_user_cancel (&job->job, force, errp); out:aio_context_release (aio_context); }
blockjob.c +0 -76
88 lines not on this page · raw message
job.c +76 -8
127 lines not on this page · raw message
qemu-img.c +1 -1
@@ -870,7 +870,7 @@ static void run_block_job(BlockJob *job, Error **errp) }while (!job->ready && !job_is_completed (&job->job));if (!job_is_completed (&job->job)) { - ret =block_job_complete_sync (job, errp); + ret =job_complete_sync (&job->job, errp); }else { ret = job->job.ret; }
tests/test-bdrv-drain.c +2 -3
@@ -498,8 +498,7 @@ typedef struct TestBlockJob {static void test_job_completed (Job *job,void *opaque) { -BlockJob *bjob =container_of (job, BlockJob, job); -block_job_completed (bjob,0 ); +job_completed (job,0 ); } static void coroutine_fn test_job_start(void *opaque)
@@ -593,7 +592,7 @@ static void test_blockjob_common(enum drain_type drain_type)g_assert_false (job->job.paused);g_assert_false (job->job.busy);/* We're in job_sleep_ns() */ - ret =block_job_complete_sync (job, &error_abort); + ret =job_complete_sync (&job->job, &error_abort); g_assert_cmpint(ret, ==, 0);blk_unref (blk_src);
tests/test-blockjob-txn.c +7 -7
@@ -34,7 +34,7 @@ static void test_block_job_complete(Job *job, void *opaque) rc = -ECANCELED ; } -block_job_completed (bjob, rc); +job_completed (job, rc);bdrv_unref (bs); }
@@ -130,7 +130,7 @@ static void test_single_job(int expected)job_start (&job->job);if (expected == -ECANCELED ) { -block_job_cancel (job, false); +job_cancel (&job->job, false); }while (result == -EINPROGRESS ) {
@@ -176,10 +176,10 @@ static void test_pair_jobs(int expected1, int expected2)job_txn_unref (txn);if (expected1 == -ECANCELED ) { -block_job_cancel (job1, false); +job_cancel (&job1->job, false); }if (expected2 == -ECANCELED ) { -block_job_cancel (job2, false); +job_cancel (&job2->job, false); }while (result1 == -EINPROGRESS || result2 == -EINPROGRESS ) {
@@ -232,13 +232,13 @@ static void test_pair_jobs_fail_cancel_race(void)job_start (&job1->job);job_start (&job2->job); -block_job_cancel (job1, false); +job_cancel (&job1->job, false);/* Now make job2 finish before the main loop kicks jobs. This simulates * the race between a pending kick and another job completing. */ -block_job_enter (job2); -block_job_enter (job2); +job_enter (&job2->job); +job_enter (&job2->job);while (result1 == -EINPROGRESS || result2 == -EINPROGRESS ) {aio_poll (qemu_get_aio_context (), true);
tests/test-blockjob.c +10 -11
@@ -165,10 +165,9 @@ typedef struct CancelJob {static void cancel_job_completed (Job *job,void *opaque) { -BlockJob *bjob =container_of (job, BlockJob, job);CancelJob *s = opaque; s->completed = true; -block_job_completed (bjob,0 ); +job_completed (job,0 ); }static void cancel_job_complete (Job *job,Error **errp)
@@ -232,7 +231,7 @@ static void cancel_common(CancelJob *s)BlockBackend *blk = s->blk;JobStatus sts = job->job.status; -block_job_cancel_sync (job); +job_cancel_sync (&job->job);if (sts !=JOB_STATUS_CREATED && sts !=JOB_STATUS_CONCLUDED ) {BlockJob *dummy = job;block_job_dismiss (&dummy, &error_abort);
@@ -275,7 +274,7 @@ static void test_cancel_paused(void)assert (job->job.status ==JOB_STATUS_RUNNING );job_user_pause (&job->job, &error_abort); -block_job_enter (job); +job_enter (&job->job);assert (job->job.status ==JOB_STATUS_PAUSED );cancel_common (s);
@@ -292,7 +291,7 @@ static void test_cancel_ready(void)assert (job->job.status ==JOB_STATUS_RUNNING ); s->should_converge = true; -block_job_enter (job); +job_enter (&job->job);assert (job->job.status ==JOB_STATUS_READY );cancel_common (s);
@@ -309,11 +308,11 @@ static void test_cancel_standby(void)assert (job->job.status ==JOB_STATUS_RUNNING ); s->should_converge = true; -block_job_enter (job); +job_enter (&job->job);assert (job->job.status ==JOB_STATUS_READY );job_user_pause (&job->job, &error_abort); -block_job_enter (job); +job_enter (&job->job);assert (job->job.status ==JOB_STATUS_STANDBY );cancel_common (s);
@@ -330,11 +329,11 @@ static void test_cancel_pending(void)assert (job->job.status ==JOB_STATUS_RUNNING ); s->should_converge = true; -block_job_enter (job); +job_enter (&job->job);assert (job->job.status ==JOB_STATUS_READY );job_complete (&job->job, &error_abort); -block_job_enter (job); +job_enter (&job->job);while (!s->completed) {aio_poll (qemu_get_aio_context (), true); }
@@ -354,11 +353,11 @@ static void test_cancel_concluded(void)assert (job->job.status ==JOB_STATUS_RUNNING ); s->should_converge = true; -block_job_enter (job); +job_enter (&job->job);assert (job->job.status ==JOB_STATUS_READY );job_complete (&job->job, &error_abort); -block_job_enter (job); +job_enter (&job->job);while (!s->completed) {aio_poll (qemu_get_aio_context (), true); }
block/trace-events +0 -3
@@ -4,9 +4,6 @@ bdrv_open_common(void *bs, const char *filename, int flags, const char *format_name) "bs %p filename \"%s\" flags 0x%x format_name \"%s\"" bdrv_lock_medium(void *bs, bool locked) "bs %p locked %d" -# blockjob.c -block_job_completed(void *job, int ret, int jret) "job %p ret %d corrected ret %d" - # block/block-backend.c blk_co_preadv(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags) "blk %p bs %p offset %"PRId64" bytes %u flags 0x%x" blk_co_pwritev(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags) "blk %p bs %p offset %"PRId64" bytes %u flags 0x%x"
trace-events +1 -0
@@ -107,6 +107,7 @@ gdbstub_err_checksum_incorrect(uint8_t expected, uint8_t got) "got command packe # job.c job_state_transition(void *job, int ret, const char *legal, const char *s0, const char *s1) "job %p (ret: %d) attempting %s transition (%s-->%s)" job_apply_verb(void *job, const char *state, const char *verb, const char *legal) "job %p in state %s; applying verb %s (%s)" +job_completed(void *job, int ret, int jret) "job %p ret %d corrected ret %d" ### Guest events, keep at bottom
Signature · 1 line
2.13.6
PATCH v2 28/40 block: Cancel job in bdrv_close_all() callers
In reply to Kevin WolfNow that we cancel all jobs and not only block jobs on shutdown, doing that in bdrv_close_all() isn't really appropriate any more. Move the job_cancel_sync_all() call to the callers, and only assert that there are no job running in bdrv_close_all().
--- block.c | 4 +--- qemu-nbd.c | 8 +++++++- vl.c | 1 + 3 files changed, 9 insertions(+), 4 deletions(-)
block.c +1 -3
@@ -3362,9 +3362,7 @@ static void bdrv_close(BlockDriverState *bs)void bdrv_close_all (void ) { -/* TODO We do want to cancel all jobs instead of just block jobs on -* shutdown, but bdrv_close_all() isn't the right place any more. */ -job_cancel_sync_all (); +assert (job_next (NULL ) ==NULL );nbd_export_close_all ();/* Drop references from requests still in flight, such as canceled block
qemu-nbd.c +7 -1
@@ -482,6 +482,12 @@ static const char *socket_activation_validate_opts(const char *device,return NULL ; } +static void qemu_nbd_shutdown (void ) +{ +job_cancel_sync_all (); +bdrv_close_all (); +} +int main (int argc,char **argv) {BlockBackend *blk;
@@ -928,7 +934,7 @@ int main(int argc, char **argv)exit (EXIT_FAILURE ); }bdrv_init (); -atexit (bdrv_close_all); +atexit (qemu_nbd_shutdown); srcpath = argv[optind];if (imageOpts) {
vl.c +1 -0
@@ -4679,6 +4679,7 @@ int main(int argc, char **argv, char **envp)/* No more vcpu or device emulation activity beyond this point */ vm_shutdown (); +job_cancel_sync_all ();bdrv_close_all ();res_free ();
Signature · 1 line
2.13.6
PATCH v2 29/40 job: Add job_yield()
In reply to Kevin WolfThis moves block_job_yield() to the Job layer.
--- include/block/blockjob_int.h | 8 -------- include/qemu/job.h | 9 +++++++-- block/backup.c | 2 +- block/mirror.c | 2 +- blockjob.c | 16 ---------------- job.c | 20 ++++++++++++++++++-- tests/test-blockjob-txn.c | 2 +- 7 files changed, 28 insertions(+), 31 deletions(-)
include/block/blockjob_int.h +0 -8
@@ -108,14 +108,6 @@ void block_job_user_resume(Job *job);void block_job_drain (Job *job);/** -* block_job_yield: -* @job: The job that calls the function. -* -* Yield the block job coroutine. -*/ -void block_job_yield (BlockJob *job); - -/** * block_job_ratelimit_get_delay: * * Calculate and return delay for the next request in ns. See the documentation
include/qemu/job.h +7 -2
@@ -339,6 +339,13 @@ void coroutine_fn job_pause_point(Job *job);/** * @job: The job that calls the function. +* +* Yield the job coroutine. +*/ +void job_yield (Job *job); + +/** +* @job: The job that calls the function. * @ns: How many nanoseconds to stop for. * * Put the job to sleep (assuming that it wasn't canceled) for @ns
@@ -508,8 +515,6 @@ int job_finish_sync(Job *job, void (*finish)(Job *, Error **errp), Error **errp)/* TODO To be removed from the public interface */ void job_state_transition (Job *job,JobStatus s1); -void coroutine_fn job_do_yield(Job *job, uint64_t ns); -bool job_should_pause (Job *job);void job_do_dismiss (Job *job);#endif
block/backup.c +1 -1
@@ -444,7 +444,7 @@ static void coroutine_fn backup_run(void *opaque)while (!job_is_cancelled (&job->common.job)) {/* Yield until the job is cancelled. We just let our before_write * notify callback service CoW requests. */ -block_job_yield (&job->common); +job_yield (&job->common.job); } }else if (job->sync_mode ==MIRROR_SYNC_MODE_INCREMENTAL ) { ret =backup_run_incremental (job);
block/mirror.c +1 -1
@@ -731,7 +731,7 @@ static void coroutine_fn mirror_run(void *opaque)block_job_event_ready (&s->common); s->synced = true;while (!job_is_cancelled (&s->common.job) && !s->should_complete) { -block_job_yield (&s->common); +job_yield (&s->common.job); } s->common.job.cancelled = false; goto immediate_exit;
blockjob.c +0 -16
@@ -431,22 +431,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,return job; } -void block_job_yield (BlockJob *job) -{ -assert (job->job.busy); - -/* Check cancellation *before* setting busy = false, too! */ -if (job_is_cancelled (&job->job)) { -return ; - } - -if (!job_should_pause (&job->job)) { -job_do_yield (&job->job,-1 ); - } - -job_pause_point (&job->job); -} -void block_job_iostatus_reset (BlockJob *job) {if (job->iostatus ==BLOCK_DEVICE_IO_STATUS_OK ) {
job.c +18 -2
@@ -226,7 +226,7 @@ static bool job_started(Job *job)return job->co; } -bool job_should_pause (Job *job) +static bool job_should_pause (Job *job) {return job->pause_count >0 ; }
@@ -396,7 +396,7 @@ void job_enter(Job *job)* * If @ns is (uint64_t) -1, no timer is scheduled and job_enter() must be * called explicitly. */ -void coroutine_fn job_do_yield(Job *job, uint64_t ns) +static void coroutine_fn job_do_yield(Job *job, uint64_t ns) {job_lock ();if (ns !=-1 ) {
@@ -441,6 +441,22 @@ void coroutine_fn job_pause_point(Job *job) } } +void job_yield (Job *job) +{ +assert (job->busy); + +/* Check cancellation *before* setting busy = false, too! */ +if (job_is_cancelled (job)) { +return ; + } + +if (!job_should_pause (job)) { +job_do_yield (job,-1 ); + } + +job_pause_point (job); +} + void coroutine_fn job_sleep_ns(Job *job, int64_t ns) {assert (job->busy);
tests/test-blockjob-txn.c +1 -1
@@ -47,7 +47,7 @@ static void coroutine_fn test_block_job_run(void *opaque)if (s->use_timer) {job_sleep_ns (&job->job,0 ); }else { -block_job_yield (job); +job_yield (&job->job); }if (job_is_cancelled (&job->job)) {
Signature · 1 line
2.13.6
PATCH v2 30/40 job: Add job_dismiss()
In reply to Kevin WolfThis moves block_job_dismiss() to the Job layer.
--- include/block/blockjob.h | 9 --------- include/qemu/job.h | 7 ++++++- blockdev.c | 10 ++++++---- blockjob.c | 13 ------------- job.c | 15 ++++++++++++++- tests/test-blockjob.c | 4 ++-- 6 files changed, 28 insertions(+), 30 deletions(-)
include/block/blockjob.h +0 -9
@@ -141,15 +141,6 @@ void block_job_remove_all_bdrv(BlockJob *job);void block_job_set_speed (BlockJob *job,int64_t speed,Error **errp);/** -* block_job_dismiss: -* @job: The job to be dismissed. -* @errp: Error object. -* -* Remove a concluded job from the query list. -*/ -void block_job_dismiss (BlockJob **job,Error **errp); - -/** * block_job_progress_update: * @job: The job that has made progress * @done: How much progress the job made
include/qemu/job.h +6 -1
@@ -487,6 +487,12 @@ int job_complete_sync(Job *job, Error **errp);*/ void job_finalize (Job *job,Error **errp); +/** +* Remove the concluded @job from the query list and resets the passed pointer +* to %NULL. Returns an error if the job is not actually concluded. +*/ +void job_dismiss (Job **job,Error **errp); +typedef void JobDeferToMainLoopFn (Job *job,void *opaque);/**
@@ -515,6 +521,5 @@ int job_finish_sync(Job *job, void (*finish)(Job *, Error **errp), Error **errp)/* TODO To be removed from the public interface */ void job_state_transition (Job *job,JobStatus s1); -void job_do_dismiss (Job *job);#endif
blockdev.c +6 -4
@@ -3915,14 +3915,16 @@ void qmp_block_job_finalize(const char *id, Error **errp)void qmp_block_job_dismiss (const char *id,Error **errp) {AioContext *aio_context; -BlockJob *job =find_block_job (id, &aio_context, errp); +BlockJob *bjob =find_block_job (id, &aio_context, errp); +Job *job; -if (!job) { +if (!bjob) {return ; } -trace_qmp_block_job_dismiss (job); -block_job_dismiss (&job, errp); +trace_qmp_block_job_dismiss (bjob); + job = &bjob->job; +job_dismiss (&job, errp);aio_context_release (aio_context); }
blockjob.c +0 -13
@@ -242,19 +242,6 @@ int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n)return ratelimit_calculate_delay (&job->limit, n); } -void block_job_dismiss (BlockJob **jobptr,Error **errp) -{ -BlockJob *job = *jobptr; -/* similarly to _complete, this is QMP-interface only. */ -assert (job->job.id); -if (job_apply_verb (&job->job,JOB_VERB_DISMISS , errp)) { -return ; - } - -job_do_dismiss (&job->job); - *jobptr =NULL ; -} -void block_job_progress_update (BlockJob *job,uint64_t done) { job->offset += done;
job.c +14 -1
@@ -568,7 +568,7 @@ void job_user_resume(Job *job, Error **errp)job_resume (job); } -void job_do_dismiss (Job *job) +static void job_do_dismiss (Job *job) {assert (job); job->busy = false;
@@ -581,6 +581,19 @@ void job_do_dismiss(Job *job)job_unref (job); } +void job_dismiss (Job **jobptr,Error **errp) +{ +Job *job = *jobptr; +/* similarly to _complete, this is QMP-interface only. */ +assert (job->id); +if (job_apply_verb (job,JOB_VERB_DISMISS , errp)) { +return ; + } + +job_do_dismiss (job); + *jobptr =NULL ; +} +void job_early_fail (Job *job) {assert (job->status ==JOB_STATUS_CREATED );
tests/test-blockjob.c +2 -2
@@ -233,8 +233,8 @@ static void cancel_common(CancelJob *s)job_cancel_sync (&job->job);if (sts !=JOB_STATUS_CREATED && sts !=JOB_STATUS_CONCLUDED ) { -BlockJob *dummy = job; -block_job_dismiss (&dummy, &error_abort); +Job *dummy = &job->job; +job_dismiss (&dummy, &error_abort); }assert (job->job.status ==JOB_STATUS_NULL );job_unref (&job->job);
Signature · 1 line
2.13.6
PATCH v2 31/40 job: Add job_is_ready()
In reply to Kevin WolfInstead of having a 'bool ready' in BlockJob, add a function that derives its value from the job status.
At the same time, this fixes the behaviour to match what the QAPI documentation promises for query-block-job: 'true if the job may be completed'. When the ready flag was introduced in commit ef6dbf1e46e, the flag never had to be reset to match the description because after being ready, the jobs would immediately complete and disappear.
Job transactions and manual job finalisation were introduced only later. With these changes, jobs may stay around even after having completed (and they are not ready to be completed a second time), however their patches forgot to reset the ready flag.
--- include/block/blockjob.h | 5 ----- include/qemu/job.h | 3 +++ blockjob.c | 3 +-- job.c | 22 ++++++++++++++++++++++ qemu-img.c | 2 +- tests/test-blockjob.c | 2 +- 6 files changed, 28 insertions(+), 9 deletions(-)
include/block/blockjob.h +0 -5
@@ -49,11 +49,6 @@ typedef struct BlockJob {/** The block device on which the job is operating. */ BlockBackend *blk; -/** -* Set to true when the job is ready to be completed. -*/ -bool ready; -/** Status that is published by the query-block-jobs QMP API */ BlockDeviceIoStatus iostatus;
include/qemu/job.h +3 -0
@@ -367,6 +367,9 @@ bool job_is_cancelled(Job *job);/** Returns whether the job is in a completed state. */ bool job_is_completed (Job *job); +/** Returns whether the job is ready to be completed. */ +bool job_is_ready (Job *job); +/** * Request @job to pause at the next pause point. Must be paired with * job_resume(). If the job is supposed to be resumed by user action, call
blockjob.c +1 -2
@@ -269,7 +269,7 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp) info->offset = job->offset; info->speed = job->speed; info->io_status = job->iostatus; - info->ready = job->ready; + info->ready =job_is_ready (&job->job), info->status = job->job.status; info->auto_finalize = job->job.auto_finalize; info->auto_dismiss = job->job.auto_dismiss;
@@ -436,7 +436,6 @@ void block_job_user_resume(Job *job)void block_job_event_ready (BlockJob *job) {job_state_transition (&job->job,JOB_STATUS_READY ); - job->ready = true;if (block_job_is_internal (job)) {return ;
job.c +22 -0
@@ -199,6 +199,28 @@ bool job_is_cancelled(Job *job)return job->cancelled; } +bool job_is_ready (Job *job) +{ +switch (job->status) { +case JOB_STATUS_UNDEFINED : +case JOB_STATUS_CREATED : +case JOB_STATUS_RUNNING : +case JOB_STATUS_PAUSED : +case JOB_STATUS_WAITING : +case JOB_STATUS_PENDING : +case JOB_STATUS_ABORTING : +case JOB_STATUS_CONCLUDED : +case JOB_STATUS_NULL : +return false; +case JOB_STATUS_READY : +case JOB_STATUS_STANDBY : +return true; +default : +g_assert_not_reached (); + } +return false; +} +bool job_is_completed (Job *job) {switch (job->status) {
qemu-img.c +1 -1
@@ -867,7 +867,7 @@ static void run_block_job(BlockJob *job, Error **errp)aio_poll (aio_context, true);qemu_progress_print (job->len ? ((float )job->offset / job->len *100.f ) :0.0f ,0 ); - }while (!job->ready && !job_is_completed (&job->job)); + }while (!job_is_ready (&job->job) && !job_is_completed (&job->job));if (!job_is_completed (&job->job)) { ret =job_complete_sync (&job->job, errp);
tests/test-blockjob.c +1 -1
@@ -185,7 +185,7 @@ static void coroutine_fn cancel_job_start(void *opaque) goto defer; } -if (!s->common.ready && s->should_converge) { +if (!job_is_ready (&s->common.job) && s->should_converge) {block_job_event_ready (&s->common); }
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH v2 31/40] job: Add job_is_ready()
Quoted 96 lines · Kevin Wolf
Instead of having a 'bool ready' in BlockJob, add a function that derives its value from the job status.
At the same time, this fixes the behaviour to match what the QAPI documentation promises for query-block-job: 'true if the job may be completed'. When the ready flag was introduced in commit
ef6dbf1e46e, the flag never had to be reset to match the description because after being ready, the jobs would immediately complete and disappear.Job transactions and manual job finalisation were introduced only later. With these changes, jobs may stay around even after having completed (and they are not ready to be completed a second time), however their patches forgot to reset the ready flag.
Signed-off-by Kevin Wolf <[email protected]>Reviewed-by Max Reitz <[email protected]>--- include/block/blockjob.h | 5 ----- include/qemu/job.h | 3 +++ blockjob.c | 3 +-- job.c | 22 ++++++++++++++++++++++ qemu-img.c | 2 +- tests/test-blockjob.c | 2 +- 6 files changed, 28 insertions(+), 9 deletions(-)include/block/blockjob.h +0
-5@@ -49,11 +49,6 @@ typedef struct BlockJob {/** The block device on which the job is operating. */ BlockBackend *blk; -/** -* Set to true when the job is ready to be completed. -*/ -bool ready; -/** Status that is published by the query-block-jobs QMP API */ BlockDeviceIoStatus iostatus;include/qemu/job.h +3
-0@@ -367,6 +367,9 @@ bool job_is_cancelled(Job *job);/** Returns whether the job is in a completed state. */ bool job_is_completed (Job *job); +/** Returns whether the job is ready to be completed. */ +bool job_is_ready (Job *job); +/** * Request @job to pause at the next pause point. Must be paired with * job_resume(). If the job is supposed to be resumed by user action, call blockjob.c +1
-2@@ -269,7 +269,7 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp) info->offset = job->offset; info->speed = job->speed; info->io_status = job->iostatus; - info->ready = job->ready; + info->ready =job_is_ready (&job->job), info->status = job->job.status; info->auto_finalize = job->job.auto_finalize; info->auto_dismiss = job->job.auto_dismiss;@@ -436,7 +436,6 @@ void block_job_user_resume(Job *job)void block_job_event_ready (BlockJob *job) {job_state_transition (&job->job,JOB_STATUS_READY ); - job->ready = true;if (block_job_is_internal (job)) {return ;job.c +12
-0@@ -199,6 +199,28 @@ bool job_is_cancelled(Job *job)return job->cancelled; } +bool job_is_ready (Job *job) +{ +switch (job->status) { +case JOB_STATUS_UNDEFINED : +case JOB_STATUS_CREATED : +case JOB_STATUS_RUNNING : +case JOB_STATUS_PAUSED : +case JOB_STATUS_WAITING : +case JOB_STATUS_PENDING : +case JOB_STATUS_ABORTING : +case JOB_STATUS_CONCLUDED : +case JOB_STATUS_NULL :
job.c bool job_is_cancelled(Job *job) · line 223
Quoted 10 lines
job.c +10
-0+return false; +case JOB_STATUS_READY : +case JOB_STATUS_STANDBY : +return true; +default : +g_assert_not_reached (); + } +return false; +} +
What's the benefit to a switch statement with a default clause here, over the shorter:
if (job->status == READY || job->status == STANDBY) {
return true;
}
return false;
(Yes, I realize you already merged this code, but I'm still curious and I need to read the series anyway to see what's changed...)
Quoted 8 lines
job.c +0
-0bool job_is_completed (Job *job) {switch (job->status) {qemu-img.c +0
-0
qemu-img.c static void run_block_job(BlockJob *job, Error **errp) · line 871
Quoted 6 lines
qemu-img.c +1
-1@@ -867,7 +867,7 @@ static void run_block_job(BlockJob *job, Error **errp)aio_poll (aio_context, true);qemu_progress_print (job->len ? ((float )job->offset / job->len *100.f ) :0.0f ,0 ); - }while (!job->ready && !job_is_completed (&job->job)); + }while (!job_is_ready (&job->job) && !job_is_completed (&job->job));
Semantic change in this variable, but due to the way this function is written it doesn't matter.
Quoted 16 lines
qemu-img.c +0
-0if (!job_is_completed (&job->job)) { ret =job_complete_sync (&job->job, errp);tests/test-blockjob.c +1
-1@@ -185,7 +185,7 @@ static void coroutine_fn cancel_job_start(void *opaque) goto defer; } -if (!s->common.ready && s->should_converge) { +if (!job_is_ready (&s->common.job) && s->should_converge) {block_job_event_ready (&s->common); }
Re: [Qemu-devel] [PATCH v2 31/40] job: Add job_is_ready()
Am 24.05.2018 um 01:42 hat John Snow geschrieben:
Quoted 111 lines · Kevin Wolf
Quoted 96 lines
Instead of having a 'bool ready' in BlockJob, add a function that derives its value from the job status.
At the same time, this fixes the behaviour to match what the QAPI documentation promises for query-block-job: 'true if the job may be completed'. When the ready flag was introduced in commit
ef6dbf1e46e, the flag never had to be reset to match the description because after being ready, the jobs would immediately complete and disappear.Job transactions and manual job finalisation were introduced only later. With these changes, jobs may stay around even after having completed (and they are not ready to be completed a second time), however their patches forgot to reset the ready flag.
Signed-off-by Kevin Wolf <[email protected]>Reviewed-by Max Reitz <[email protected]>--- include/block/blockjob.h | 5 ----- include/qemu/job.h | 3 +++ blockjob.c | 3 +-- job.c | 22 ++++++++++++++++++++++ qemu-img.c | 2 +- tests/test-blockjob.c | 2 +- 6 files changed, 28 insertions(+), 9 deletions(-)include/block/blockjob.h +0
-5@@ -49,11 +49,6 @@ typedef struct BlockJob {/** The block device on which the job is operating. */ BlockBackend *blk; -/** -* Set to true when the job is ready to be completed. -*/ -bool ready; -/** Status that is published by the query-block-jobs QMP API */ BlockDeviceIoStatus iostatus;include/qemu/job.h +3
-0@@ -367,6 +367,9 @@ bool job_is_cancelled(Job *job);/** Returns whether the job is in a completed state. */ bool job_is_completed (Job *job); +/** Returns whether the job is ready to be completed. */ +bool job_is_ready (Job *job); +/** * Request @job to pause at the next pause point. Must be paired with * job_resume(). If the job is supposed to be resumed by user action, call blockjob.c +1
-2@@ -269,7 +269,7 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp) info->offset = job->offset; info->speed = job->speed; info->io_status = job->iostatus; - info->ready = job->ready; + info->ready =job_is_ready (&job->job), info->status = job->job.status; info->auto_finalize = job->job.auto_finalize; info->auto_dismiss = job->job.auto_dismiss;@@ -436,7 +436,6 @@ void block_job_user_resume(Job *job)void block_job_event_ready (BlockJob *job) {job_state_transition (&job->job,JOB_STATUS_READY ); - job->ready = true;if (block_job_is_internal (job)) {return ;job.c +12
-0@@ -199,6 +199,28 @@ bool job_is_cancelled(Job *job)return job->cancelled; } +bool job_is_ready (Job *job) +{ +switch (job->status) { +case JOB_STATUS_UNDEFINED : +case JOB_STATUS_CREATED : +case JOB_STATUS_RUNNING : +case JOB_STATUS_PAUSED : +case JOB_STATUS_WAITING : +case JOB_STATUS_PENDING : +case JOB_STATUS_ABORTING : +case JOB_STATUS_CONCLUDED : +case JOB_STATUS_NULL :job.c bool job_is_cancelled(Job *job) · line 223
Quoted 10 lines
job.c +10
-0+return false; +case JOB_STATUS_READY : +case JOB_STATUS_STANDBY : +return true; +default : +g_assert_not_reached (); + } +return false; +} +What's the benefit to a switch statement with a default clause here, over the shorter:
Quoted 7 lines
if (
job->status== READY ||job->status== STANDBY) {
return true;
}
return false;(Yes, I realize you already merged this code, but I'm still curious and I need to read the series anyway to see what's changed...)
That it's easy to copy and paste from job_is_completed()? :-P
I guess you could argue that the switch ensures that we don't forget to explicitly handle every state if we ever add a new one, but the real reason is more like, job_is_completed() was already there and I didn't see a reason to do something different here.
(And actually, I seem to remember that I initially had everything after READY return true as well, but then I noticed that you can reach those states through ABORTED as well, which should not result in job_is_ready() returning true.)
Kevin
Re: [Qemu-devel] [PATCH v2 31/40] job: Add job_is_ready()
Quoted 117 lines · Kevin Wolf
Am 24.05.2018 um 01:42 hat John Snow geschrieben:
Quoted 116 lines · Kevin Wolf
Quoted 96 lines
Instead of having a 'bool ready' in BlockJob, add a function that derives its value from the job status.
At the same time, this fixes the behaviour to match what the QAPI documentation promises for query-block-job: 'true if the job may be completed'. When the ready flag was introduced in commit
ef6dbf1e46e, the flag never had to be reset to match the description because after being ready, the jobs would immediately complete and disappear.Job transactions and manual job finalisation were introduced only later. With these changes, jobs may stay around even after having completed (and they are not ready to be completed a second time), however their patches forgot to reset the ready flag.
Signed-off-by Kevin Wolf <[email protected]>Reviewed-by Max Reitz <[email protected]>--- include/block/blockjob.h | 5 ----- include/qemu/job.h | 3 +++ blockjob.c | 3 +-- job.c | 22 ++++++++++++++++++++++ qemu-img.c | 2 +- tests/test-blockjob.c | 2 +- 6 files changed, 28 insertions(+), 9 deletions(-)include/block/blockjob.h +0
-5@@ -49,11 +49,6 @@ typedef struct BlockJob {/** The block device on which the job is operating. */ BlockBackend *blk; -/** -* Set to true when the job is ready to be completed. -*/ -bool ready; -/** Status that is published by the query-block-jobs QMP API */ BlockDeviceIoStatus iostatus;include/qemu/job.h +3
-0@@ -367,6 +367,9 @@ bool job_is_cancelled(Job *job);/** Returns whether the job is in a completed state. */ bool job_is_completed (Job *job); +/** Returns whether the job is ready to be completed. */ +bool job_is_ready (Job *job); +/** * Request @job to pause at the next pause point. Must be paired with * job_resume(). If the job is supposed to be resumed by user action, call blockjob.c +1
-2@@ -269,7 +269,7 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp) info->offset = job->offset; info->speed = job->speed; info->io_status = job->iostatus; - info->ready = job->ready; + info->ready =job_is_ready (&job->job), info->status = job->job.status; info->auto_finalize = job->job.auto_finalize; info->auto_dismiss = job->job.auto_dismiss;@@ -436,7 +436,6 @@ void block_job_user_resume(Job *job)void block_job_event_ready (BlockJob *job) {job_state_transition (&job->job,JOB_STATUS_READY ); - job->ready = true;if (block_job_is_internal (job)) {return ;job.c +12
-0@@ -199,6 +199,28 @@ bool job_is_cancelled(Job *job)return job->cancelled; } +bool job_is_ready (Job *job) +{ +switch (job->status) { +case JOB_STATUS_UNDEFINED : +case JOB_STATUS_CREATED : +case JOB_STATUS_RUNNING : +case JOB_STATUS_PAUSED : +case JOB_STATUS_WAITING : +case JOB_STATUS_PENDING : +case JOB_STATUS_ABORTING : +case JOB_STATUS_CONCLUDED : +case JOB_STATUS_NULL :job.c bool job_is_cancelled(Job *job) · line 223
Quoted 10 lines
job.c +10
-0+return false; +case JOB_STATUS_READY : +case JOB_STATUS_STANDBY : +return true; +default : +g_assert_not_reached (); + } +return false; +} +What's the benefit to a switch statement with a default clause here, over the shorter:
if (
job->status== READY ||job->status== STANDBY) {
return true;
}
return false;
Quoted 5 lines
Quoted 2 lines
(Yes, I realize you already merged this code, but I'm still curious and I need to read the series anyway to see what's changed...)
That it's easy to copy and paste from
job_is_completed()? :-P
Haha! Sure!
Quoted 5 lines
I guess you could argue that the switch ensures that we don't forget to explicitly handle every state if we ever add a new one, but the real reason is more like,
job_is_completed()was already there and I didn't see a reason to do something different here.
I think the "default" case removes that benefit somewhat; it's nicer when the compiler yelps at you for forgetting. The cases that might cause an assertion could be harder to hit.
No need to change anything though, I was really just curious about your style preferences.
(I often like to write switch/case statements without a default clause hoping that the compiler will catch when I don't check all possible enumerations, but this doesn't work well with QAPI values because of the __MAX enumeration we like to add at the end. You can fix it by adding a case MY_ENUM__MAX: g_assert_not_reached();, but I'm often afraid this looks too hacky and silly to check in. I wish C had slightly nicer enumerations as first class citizens a lot. I also wish I had a yacht sometimes.)
Quoted 5 lines
(And actually, I seem to remember that I initially had everything after READY return true as well, but then I noticed that you can reach those states through ABORTED as well, which should not result in
job_is_ready()returning true.)
Makes much more sense.
Thanks!
Quoted 2 lines
Kevin
Re: [Qemu-devel] [PATCH v2 31/40] job: Add job_is_ready()
Am 24.05.2018 um 19:25 hat John Snow geschrieben:
Quoted 53 lines
Quoted 39 lines
Quoted 39 lines
Quoted 20 lines
job.c +12
-0@@ -199,6 +199,28 @@ bool job_is_cancelled(Job *job)return job->cancelled; } +bool job_is_ready (Job *job) +{ +switch (job->status) { +case JOB_STATUS_UNDEFINED : +case JOB_STATUS_CREATED : +case JOB_STATUS_RUNNING : +case JOB_STATUS_PAUSED : +case JOB_STATUS_WAITING : +case JOB_STATUS_PENDING : +case JOB_STATUS_ABORTING : +case JOB_STATUS_CONCLUDED : +case JOB_STATUS_NULL :job.c bool job_is_cancelled(Job *job) · line 223
Quoted 10 lines
job.c +10
-0+return false; +case JOB_STATUS_READY : +case JOB_STATUS_STANDBY : +return true; +default : +g_assert_not_reached (); + } +return false; +} +What's the benefit to a switch statement with a default clause here, over the shorter:
if (
job->status== READY ||job->status== STANDBY) {
return true;
}
return false;Quoted 5 lines
Quoted 2 lines
(Yes, I realize you already merged this code, but I'm still curious and I need to read the series anyway to see what's changed...)
That it's easy to copy and paste from
job_is_completed()? :-PHaha! Sure!
Quoted 5 lines
I guess you could argue that the switch ensures that we don't forget to explicitly handle every state if we ever add a new one, but the real reason is more like,
job_is_completed()was already there and I didn't see a reason to do something different here.
Quoted 3 lines
I think the "default" case removes that benefit somewhat; it's nicer when the compiler yelps at you for forgetting. The cases that might cause an assertion could be harder to hit.
Good point. I'm not sure which compilers would actually warn here, while asserting makes sure to crash some tests, but in this context I think we could actually have both: All enum values that are handled return inside the switch statement. So if we move the g_assert_not_reached() just after the switch block, we should get both the compiler warning and the crash.
Would you like to send a patch?
Kevin
PATCH v2 32/40 job: Add job_transition_to_ready()
In reply to Kevin WolfThe transition to the READY state was still performed in the BlockJob layer, in the same function that sent the BLOCK_JOB_READY QMP event.
This patch brings the state transition to the Job layer and implements the QMP event using a notifier called from the Job layer, like we already do for other events related to state transitions.
--- include/block/blockjob.h | 3 +++ include/block/blockjob_int.h | 8 -------- include/qemu/job.h | 9 ++++++--- block/mirror.c | 6 +++--- blockjob.c | 33 ++++++++++++++++++--------------- job.c | 16 +++++++++++++--- tests/test-bdrv-drain.c | 2 +- tests/test-blockjob.c | 2 +- 8 files changed, 45 insertions(+), 34 deletions(-)
include/block/blockjob.h +3 -0
@@ -76,6 +76,9 @@ typedef struct BlockJob {/** Called when the job transitions to PENDING */ Notifier pending_notifier; +/** Called when the job transitions to READY */ +Notifier ready_notifier; +/** BlockDriverStates that are involved in this block job */ GSList *nodes; } BlockJob;
include/block/blockjob_int.h +0 -8
@@ -116,14 +116,6 @@ void block_job_drain(Job *job);int64_t block_job_ratelimit_get_delay (BlockJob *job,uint64_t n);/** -* block_job_event_ready: -* @job: The job which is now ready to be completed. -* -* Send a BLOCK_JOB_READY event for the specified job. -*/ -void block_job_event_ready (BlockJob *job); - -/** * block_job_error_action: * @job: The job to signal an error for. * @on_err: The error action setting.
include/qemu/job.h +6 -3
@@ -132,6 +132,9 @@ typedef struct Job {/** Notifiers called when the job transitions to PENDING */ NotifierList on_pending; +/** Notifiers called when the job transitions to READY */ +NotifierList on_ready; +/** Element of the list of jobs */ QLIST_ENTRY (Job) job_list;
@@ -426,6 +429,9 @@ int job_apply_verb(Job *job, JobVerb verb, Error **errp);/** The @job could not be started, free it. */ void job_early_fail (Job *job); +/** Moves the @job from RUNNING to READY */ +void job_transition_to_ready (Job *job); +/** * @job: The job being completed. * @ret: The status code.
@@ -522,7 +528,4 @@ void job_defer_to_main_loop(Job *job, JobDeferToMainLoopFn *fn, void *opaque);*/ int job_finish_sync (Job *job,void (*finish)(Job *,Error **errp),Error **errp); -/* TODO To be removed from the public interface */ -void job_state_transition (Job *job,JobStatus s1); -#endif
block/mirror.c +3 -3
@@ -727,8 +727,8 @@ static void coroutine_fn mirror_run(void *opaque) }if (s->bdev_length ==0 ) { -/* Report BLOCK_JOB_READY and wait for complete. */ -block_job_event_ready (&s->common); +/* Transition to the READY state and wait for complete. */ +job_transition_to_ready (&s->common.job); s->synced = true;while (!job_is_cancelled (&s->common.job) && !s->should_complete) {job_yield (&s->common.job);
@@ -824,7 +824,7 @@ static void coroutine_fn mirror_run(void *opaque)* report completion. This way, block-job-cancel will leave * the target in a consistent state. */ -block_job_event_ready (&s->common); +job_transition_to_ready (&s->common.job); s->synced = true; }
blockjob.c +18 -15
@@ -338,6 +338,22 @@ static void block_job_event_pending(Notifier *n, void *opaque) &error_abort); } +static void block_job_event_ready (Notifier *n,void *opaque) +{ +BlockJob *job = opaque; + +if (block_job_is_internal (job)) { +return ; + } + +qapi_event_send_block_job_ready (job_type (&job->job), + job->job.id, + job->len, + job->offset, + job->speed, &error_abort); +} + +/* * API for block job drivers and the block layer. These functions are * declared in blockjob_int.h.
@@ -386,12 +402,14 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, job->finalize_cancelled_notifier.notify = block_job_event_cancelled; job->finalize_completed_notifier.notify = block_job_event_completed; job->pending_notifier.notify = block_job_event_pending; + job->ready_notifier.notify = block_job_event_ready;notifier_list_add (&job->job.on_finalize_cancelled, &job->finalize_cancelled_notifier);notifier_list_add (&job->job.on_finalize_completed, &job->finalize_completed_notifier);notifier_list_add (&job->job.on_pending, &job->pending_notifier); +notifier_list_add (&job->job.on_ready, &job->ready_notifier);error_setg (&job->blocker,"block device is in use by block job: %s" ,job_type_str (&job->job));
@@ -433,21 +451,6 @@ void block_job_user_resume(Job *job)block_job_iostatus_reset (bjob); } -void block_job_event_ready (BlockJob *job) -{ -job_state_transition (&job->job,JOB_STATUS_READY ); - -if (block_job_is_internal (job)) { -return ; - } - -qapi_event_send_block_job_ready (job_type (&job->job), - job->job.id, - job->len, - job->offset, - job->speed, &error_abort); -} -BlockErrorAction block_job_error_action (BlockJob *job,BlockdevOnError on_err,int is_read,int error) {
job.c +13 -3
@@ -157,9 +157,7 @@ static int job_txn_apply(JobTxn *txn, int fn(Job *), bool lock)return rc; } - -/* TODO Make static once the whole state machine is in job.c */ -void job_state_transition (Job *job,JobStatus s1) +static void job_state_transition (Job *job,JobStatus s1) {JobStatus s0 = job->status;assert (s1 >=0 && s1 <=JOB_STATUS__MAX );
@@ -321,6 +319,7 @@ void *job_create(const char *job_id, const JobDriver *driver, JobTxn *txn,notifier_list_init (&job->on_finalize_cancelled);notifier_list_init (&job->on_finalize_completed);notifier_list_init (&job->on_pending); +notifier_list_init (&job->on_ready);job_state_transition (job,JOB_STATUS_CREATED ); aio_timer_init(qemu_get_aio_context(), &job->sleep_timer,
@@ -380,6 +379,11 @@ static void job_event_pending(Job *job)notifier_list_notify (&job->on_pending, job); } +static void job_event_ready (Job *job) +{ +notifier_list_notify (&job->on_ready, job); +} +void job_enter_cond (Job *job,bool (*fn)(Job *job)) {if (!job_started (job)) {
@@ -799,6 +803,12 @@ static int job_transition_to_pending(Job *job)return 0 ; } +void job_transition_to_ready (Job *job) +{ +job_state_transition (job,JOB_STATUS_READY ); +job_event_ready (job); +} +static void job_completed_txn_success (Job *job) {JobTxn *txn = job->txn;
tests/test-bdrv-drain.c +1 -1
@@ -505,7 +505,7 @@ static void coroutine_fn test_job_start(void *opaque) {TestBlockJob *s = opaque; -block_job_event_ready (&s->common); +job_transition_to_ready (&s->common.job);while (!s->should_complete) {job_sleep_ns (&s->common.job,100000 ); }
tests/test-blockjob.c +1 -1
@@ -186,7 +186,7 @@ static void coroutine_fn cancel_job_start(void *opaque) }if (!job_is_ready (&s->common.job) && s->should_converge) { -block_job_event_ready (&s->common); +job_transition_to_ready (&s->common.job); }job_sleep_ns (&s->common.job,100000 );
Signature · 1 line
2.13.6
PATCH v2 33/40 job: Move progress fields to Job
In reply to Kevin WolfBlockJob has fields .offset and .len, which are actually misnomers today because they are no longer tied to block device sizes, but just progress counters. As such they make a lot of sense in generic Jobs.
This patch moves the fields to Job and renames them to .progress_current and .progress_total to describe their function better.
--- include/block/blockjob.h | 25 ------------------------- include/qemu/job.h | 28 ++++++++++++++++++++++++++++ block/backup.c | 8 ++++---- block/commit.c | 4 ++-- block/mirror.c | 4 ++-- block/stream.c | 4 ++-- blockjob.c | 26 ++++++++------------------ job.c | 10 ++++++++++ qemu-img.c | 8 ++++++-- 9 files changed, 62 insertions(+), 55 deletions(-)
include/block/blockjob.h +0 -25
@@ -52,12 +52,6 @@ typedef struct BlockJob {/** Status that is published by the query-block-jobs QMP API */ BlockDeviceIoStatus iostatus; -/** Offset that is published by the query-block-jobs QMP API */ -int64_t offset; - -/** Length that is published by the query-block-jobs QMP API */ -int64_t len; -/** Speed that was set with @block_job_set_speed. */ int64_t speed;
@@ -139,25 +133,6 @@ void block_job_remove_all_bdrv(BlockJob *job);void block_job_set_speed (BlockJob *job,int64_t speed,Error **errp);/** -* block_job_progress_update: -* @job: The job that has made progress -* @done: How much progress the job made -* -* Updates the progress counter of the job. -*/ -void block_job_progress_update (BlockJob *job,uint64_t done); - -/** -* block_job_progress_set_remaining: -* @job: The job whose expected progress end value is set -* @remaining: Expected end value of the progress counter of the job -* -* Sets the expected end value of the progress counter of a job so that a -* completion percentage can be calculated when the progress is updated. -*/ -void block_job_progress_set_remaining (BlockJob *job,uint64_t remaining); - -/** * block_job_query: * @job: The job to get information about. *
include/qemu/job.h +28 -0
@@ -114,6 +114,16 @@ typedef struct Job {/** True if this job should automatically dismiss itself */ bool auto_dismiss; +/** +* Current progress. The unit is arbitrary as long as the ratio between +* progress_current and progress_total represents the estimated percentage +* of work already done. +*/ +int64_t progress_current; + +/** Estimated progress_current value at the completion of the job */ +int64_t progress_total; +/** ret code passed to job_completed. */ int ret;
@@ -304,6 +314,24 @@ void job_ref(Job *job);*/ void job_unref (Job *job); +/** +* @job: The job that has made progress +* @done: How much progress the job made since the last call +* +* Updates the progress counter of the job. +*/ +void job_progress_update (Job *job,uint64_t done); + +/** +* @job: The job whose expected progress end value is set +* @remaining: Missing progress (on top of the current progress counter value) +* until the new expected end value is reached +* +* Sets the expected end value of the progress counter of a job so that a +* completion percentage can be calculated when the progress is updated. +*/ +void job_progress_set_remaining (Job *job,uint64_t remaining); +/** To be called when a cancelled job is finalised. */ void job_event_cancelled (Job *job);
block/backup.c +4 -4
@@ -160,7 +160,7 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job,* offset field is an opaque progress value, it is not a disk offset. */ job->bytes_read += n; -block_job_progress_update (&job->common, n); +job_progress_update (&job->common.job, n); } out:
@@ -406,8 +406,8 @@ static void backup_incremental_init_copy_bitmap(BackupBlockJob *job)bdrv_set_dirty_iter (dbi, next_cluster * job->cluster_size); } -/* TODO block_job_progress_set_remaining() would make more sense */ -block_job_progress_update (&job->common, +/* TODO job_progress_set_remaining() would make more sense */ +job_progress_update (&job->common.job, job->len -hbitmap_count (job->copy_bitmap) * job->cluster_size);bdrv_dirty_iter_free (dbi);
@@ -425,7 +425,7 @@ static void coroutine_fn backup_run(void *opaque)qemu_co_rwlock_init (&job->flush_rwlock); nb_clusters =DIV_ROUND_UP (job->len, job->cluster_size); -block_job_progress_set_remaining (&job->common, job->len); +job_progress_set_remaining (&job->common.job, job->len); job->copy_bitmap =hbitmap_alloc (nb_clusters,0 );if (job->sync_mode ==MIRROR_SYNC_MODE_INCREMENTAL ) {
block/commit.c +2 -2
@@ -150,7 +150,7 @@ static void coroutine_fn commit_run(void *opaque)if (len <0 ) { goto out; } -block_job_progress_set_remaining (&s->common, len); +job_progress_set_remaining (&s->common.job, len); ret = base_len =blk_getlength (s->base);if (base_len <0 ) {
@@ -196,7 +196,7 @@ static void coroutine_fn commit_run(void *opaque) } }/* Publish progress */ -block_job_progress_update (&s->common, n); +job_progress_update (&s->common.job, n);if (copy) { delay_ns =block_job_ratelimit_get_delay (&s->common, n);
block/mirror.c +2 -2
@@ -119,7 +119,7 @@ static void mirror_iteration_done(MirrorOp *op, int ret)bitmap_set (s->cow_bitmap, chunk_num, nb_chunks); }if (!s->initial_zeroing_ongoing) { -block_job_progress_update (&s->common, op->bytes); +job_progress_update (&s->common.job, op->bytes); } }qemu_iovec_destroy (&op->qiov);
@@ -792,7 +792,7 @@ static void coroutine_fn mirror_run(void *opaque)/* cnt is the number of dirty bytes remaining and s->bytes_in_flight is * the number of bytes currently being processed; together those are * the current remaining operation length */ -block_job_progress_set_remaining (&s->common, s->bytes_in_flight + cnt); +job_progress_set_remaining (&s->common.job, s->bytes_in_flight + cnt);/* Note that even when no rate limit is applied we need to yield * periodically with no pending I/O so that bdrv_drain_all() returns.
block/stream.c +2 -2
@@ -121,7 +121,7 @@ static void coroutine_fn stream_run(void *opaque) ret = len; goto out; } -block_job_progress_set_remaining (&s->common, len); +job_progress_set_remaining (&s->common.job, len); buf =qemu_blockalign (bs,STREAM_BUFFER_SIZE );
@@ -184,7 +184,7 @@ static void coroutine_fn stream_run(void *opaque) ret =0 ;/* Publish progress */ -block_job_progress_update (&s->common, n); +job_progress_update (&s->common.job, n);if (copy) { delay_ns =block_job_ratelimit_get_delay (&s->common, n); }else {
blockjob.c +8 -18
@@ -242,16 +242,6 @@ int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n)return ratelimit_calculate_delay (&job->limit, n); } -void block_job_progress_update (BlockJob *job,uint64_t done) -{ - job->offset += done; -} - -void block_job_progress_set_remaining (BlockJob *job,uint64_t remaining) -{ - job->len = job->offset + remaining; -} -BlockJobInfo *block_job_query (BlockJob *job,Error **errp) {BlockJobInfo *info;
@@ -263,10 +253,10 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp) info =g_new0 (BlockJobInfo,1 ); info->type =g_strdup (job_type_str (&job->job)); info->device =g_strdup (job->job.id); - info->len = job->len; info->busy =atomic_read (&job->job.busy); info->paused = job->job.pause_count >0 ; - info->offset = job->offset; + info->offset = job->job.progress_current; + info->len = job->job.progress_total; info->speed = job->speed; info->io_status = job->iostatus; info->ready = job_is_ready(&job->job),
@@ -296,8 +286,8 @@ static void block_job_event_cancelled(Notifier *n, void *opaque)qapi_event_send_block_job_cancelled (job_type (&job->job), job->job.id, - job->len, - job->offset, + job->job.progress_total, + job->job.progress_current, job->speed, &error_abort); }
@@ -317,8 +307,8 @@ static void block_job_event_completed(Notifier *n, void *opaque) qapi_event_send_block_job_completed(job_type(&job->job), job->job.id, - job->len, - job->offset, + job->job.progress_total, + job->job.progress_current, job->speed, !!msg, msg,
@@ -348,8 +338,8 @@ static void block_job_event_ready(Notifier *n, void *opaque)qapi_event_send_block_job_ready (job_type (&job->job), job->job.id, - job->len, - job->offset, + job->job.progress_total, + job->job.progress_current, job->speed, &error_abort); }
job.c +10 -0
@@ -364,6 +364,16 @@ void job_unref(Job *job) } } +void job_progress_update (Job *job,uint64_t done) +{ + job->progress_current += done; +} + +void job_progress_set_remaining (Job *job,uint64_t remaining) +{ + job->progress_total = job->progress_current + remaining; +} +void job_event_cancelled (Job *job) {notifier_list_notify (&job->on_finalize_cancelled, job);
qemu-img.c +6 -2
@@ -864,9 +864,13 @@ static void run_block_job(BlockJob *job, Error **errp)aio_context_acquire (aio_context);job_ref (&job->job);do { +float progress =0.0f ;aio_poll (aio_context, true); -qemu_progress_print (job->len ? - ((float )job->offset / job->len *100.f ) :0.0f ,0 ); +if (job->job.progress_total) { + progress = (float )job->job.progress_current / + job->job.progress_total *100.f ; + } +qemu_progress_print (progress,0 ); }while (!job_is_ready (&job->job) && !job_is_completed (&job->job));if (!job_is_completed (&job->job)) {
Signature · 1 line
2.13.6
PATCH v2 34/40 job: Introduce qapi/job.json
In reply to Kevin WolfThis adds a separate schema file for all job-related definitions that aren't tied to the block layer.
For a start, move the enums JobType, JobStatus and JobVerb.
--- qapi/block-core.json | 90 +----------------------------------------------- qapi/job.json | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++ qapi/qapi-schema.json | 1 + Makefile | 9 +++++ Makefile.objs | 4 +++ 5 files changed, 109 insertions(+), 89 deletions(-) create mode 100644 qapi/job.json
qapi/block-core.json +1 -89
@@ -6,6 +6,7 @@ { 'include': 'common.json' } { 'include': 'crypto.json' } +{ 'include': 'job.json' } { 'include': 'sockets.json' } ##
@@ -1050,95 +1051,6 @@ 'data': ['top', 'full', 'none', 'incremental'] } ## -# @JobType: -# -# Type of a background job. -# -# @commit: block commit job type, see "block-commit" -# -# @stream: block stream job type, see "block-stream" -# -# @mirror: drive mirror job type, see "drive-mirror" -# -# @backup: drive backup job type, see "drive-backup" -# -# Since: 1.7 -## -{ 'enum': 'JobType', - 'data': ['commit', 'stream', 'mirror', 'backup'] } - -## -# @JobVerb: -# -# Represents command verbs that can be applied to a job. -# -# @cancel: see @block-job-cancel -# -# @pause: see @block-job-pause -# -# @resume: see @block-job-resume -# -# @set-speed: see @block-job-set-speed -# -# @complete: see @block-job-complete -# -# @dismiss: see @block-job-dismiss -# -# @finalize: see @block-job-finalize -# -# Since: 2.12 -## -{ 'enum': 'JobVerb', - 'data': ['cancel', 'pause', 'resume', 'set-speed', 'complete', 'dismiss', - 'finalize' ] } - -## -# @JobStatus: -# -# Indicates the present state of a given job in its lifetime. -# -# @undefined: Erroneous, default state. Should not ever be visible. -# -# @created: The job has been created, but not yet started. -# -# @running: The job is currently running. -# -# @paused: The job is running, but paused. The pause may be requested by -# either the QMP user or by internal processes. -# -# @ready: The job is running, but is ready for the user to signal completion. -# This is used for long-running jobs like mirror that are designed to -# run indefinitely. -# -# @standby: The job is ready, but paused. This is nearly identical to @paused. -# The job may return to @ready or otherwise be canceled. -# -# @waiting: The job is waiting for other jobs in the transaction to converge -# to the waiting state. This status will likely not be visible for -# the last job in a transaction. -# -# @pending: The job has finished its work, but has finalization steps that it -# needs to make prior to completing. These changes may require -# manual intervention by the management process if manual was set -# to true. These changes may still fail. -# -# @aborting: The job is in the process of being aborted, and will finish with -# an error. The job will afterwards report that it is @concluded. -# This status may not be visible to the management process. -# -# @concluded: The job has finished all work. If manual was set to true, the job -# will remain in the query list until it is dismissed. -# -# @null: The job is in the process of being dismantled. This state should not -# ever be visible externally. -# -# Since: 2.12 -## -{ 'enum': 'JobStatus', - 'data': ['undefined', 'created', 'running', 'paused', 'ready', 'standby', - 'waiting', 'pending', 'aborting', 'concluded', 'null' ] } - -## # @BlockJobInfo: # # Information about a long-running block device operation.
qapi/job.json new file +94 -0
94 lines not on this page · raw message
qapi/qapi-schema.json +1 -0
@@ -84,6 +84,7 @@ { 'include': 'crypto.json' } { 'include': 'block.json' } { 'include': 'char.json' } +{ 'include': 'job.json' } { 'include': 'net.json' } { 'include': 'rocker.json' } { 'include': 'tpm.json' }
Makefile +9 -0
@@ -98,6 +98,7 @@ GENERATED_FILES += qapi/qapi-types-char.h qapi/qapi-types-char.c GENERATED_FILES += qapi/qapi-types-common.h qapi/qapi-types-common.c GENERATED_FILES += qapi/qapi-types-crypto.h qapi/qapi-types-crypto.c GENERATED_FILES += qapi/qapi-types-introspect.h qapi/qapi-types-introspect.c +GENERATED_FILES += qapi/qapi-types-job.h qapi/qapi-types-job.c GENERATED_FILES += qapi/qapi-types-migration.h qapi/qapi-types-migration.c GENERATED_FILES += qapi/qapi-types-misc.h qapi/qapi-types-misc.c GENERATED_FILES += qapi/qapi-types-net.h qapi/qapi-types-net.c
@@ -116,6 +117,7 @@ GENERATED_FILES += qapi/qapi-visit-char.h qapi/qapi-visit-char.c GENERATED_FILES += qapi/qapi-visit-common.h qapi/qapi-visit-common.c GENERATED_FILES += qapi/qapi-visit-crypto.h qapi/qapi-visit-crypto.c GENERATED_FILES += qapi/qapi-visit-introspect.h qapi/qapi-visit-introspect.c +GENERATED_FILES += qapi/qapi-visit-job.h qapi/qapi-visit-job.c GENERATED_FILES += qapi/qapi-visit-migration.h qapi/qapi-visit-migration.c GENERATED_FILES += qapi/qapi-visit-misc.h qapi/qapi-visit-misc.c GENERATED_FILES += qapi/qapi-visit-net.h qapi/qapi-visit-net.c
@@ -133,6 +135,7 @@ GENERATED_FILES += qapi/qapi-commands-char.h qapi/qapi-commands-char.c GENERATED_FILES += qapi/qapi-commands-common.h qapi/qapi-commands-common.c GENERATED_FILES += qapi/qapi-commands-crypto.h qapi/qapi-commands-crypto.c GENERATED_FILES += qapi/qapi-commands-introspect.h qapi/qapi-commands-introspect.c +GENERATED_FILES += qapi/qapi-commands-job.h qapi/qapi-commands-job.c GENERATED_FILES += qapi/qapi-commands-migration.h qapi/qapi-commands-migration.c GENERATED_FILES += qapi/qapi-commands-misc.h qapi/qapi-commands-misc.c GENERATED_FILES += qapi/qapi-commands-net.h qapi/qapi-commands-net.c
@@ -150,6 +153,7 @@ GENERATED_FILES += qapi/qapi-events-char.h qapi/qapi-events-char.c GENERATED_FILES += qapi/qapi-events-common.h qapi/qapi-events-common.c GENERATED_FILES += qapi/qapi-events-crypto.h qapi/qapi-events-crypto.c GENERATED_FILES += qapi/qapi-events-introspect.h qapi/qapi-events-introspect.c +GENERATED_FILES += qapi/qapi-events-job.h qapi/qapi-events-job.c GENERATED_FILES += qapi/qapi-events-migration.h qapi/qapi-events-migration.c GENERATED_FILES += qapi/qapi-events-misc.h qapi/qapi-events-misc.c GENERATED_FILES += qapi/qapi-events-net.h qapi/qapi-events-net.c
@@ -582,6 +586,7 @@ qapi-modules = $(SRC_PATH)/qapi/qapi-schema.json $(SRC_PATH)/qapi/common.json \ $(SRC_PATH)/qapi/char.json \ $(SRC_PATH)/qapi/crypto.json \ $(SRC_PATH)/qapi/introspect.json \ + $(SRC_PATH)/qapi/job.json \ $(SRC_PATH)/qapi/migration.json \ $(SRC_PATH)/qapi/misc.json \ $(SRC_PATH)/qapi/net.json \
@@ -601,6 +606,7 @@ qapi/qapi-types-char.c qapi/qapi-types-char.h \ qapi/qapi-types-common.c qapi/qapi-types-common.h \ qapi/qapi-types-crypto.c qapi/qapi-types-crypto.h \ qapi/qapi-types-introspect.c qapi/qapi-types-introspect.h \ +qapi/qapi-types-job.c qapi/qapi-types-job.h \ qapi/qapi-types-migration.c qapi/qapi-types-migration.h \ qapi/qapi-types-misc.c qapi/qapi-types-misc.h \ qapi/qapi-types-net.c qapi/qapi-types-net.h \
@@ -619,6 +625,7 @@ qapi/qapi-visit-char.c qapi/qapi-visit-char.h \ qapi/qapi-visit-common.c qapi/qapi-visit-common.h \ qapi/qapi-visit-crypto.c qapi/qapi-visit-crypto.h \ qapi/qapi-visit-introspect.c qapi/qapi-visit-introspect.h \ +qapi/qapi-visit-job.c qapi/qapi-visit-job.h \ qapi/qapi-visit-migration.c qapi/qapi-visit-migration.h \ qapi/qapi-visit-misc.c qapi/qapi-visit-misc.h \ qapi/qapi-visit-net.c qapi/qapi-visit-net.h \
@@ -636,6 +643,7 @@ qapi/qapi-commands-char.c qapi/qapi-commands-char.h \ qapi/qapi-commands-common.c qapi/qapi-commands-common.h \ qapi/qapi-commands-crypto.c qapi/qapi-commands-crypto.h \ qapi/qapi-commands-introspect.c qapi/qapi-commands-introspect.h \ +qapi/qapi-commands-job.c qapi/qapi-commands-job.h \ qapi/qapi-commands-migration.c qapi/qapi-commands-migration.h \ qapi/qapi-commands-misc.c qapi/qapi-commands-misc.h \ qapi/qapi-commands-net.c qapi/qapi-commands-net.h \
@@ -653,6 +661,7 @@ qapi/qapi-events-char.c qapi/qapi-events-char.h \ qapi/qapi-events-common.c qapi/qapi-events-common.h \ qapi/qapi-events-crypto.c qapi/qapi-events-crypto.h \ qapi/qapi-events-introspect.c qapi/qapi-events-introspect.h \ +qapi/qapi-events-job.c qapi/qapi-events-job.h \ qapi/qapi-events-migration.c qapi/qapi-events-migration.h \ qapi/qapi-events-misc.c qapi/qapi-events-misc.h \ qapi/qapi-events-net.c qapi/qapi-events-net.h \
Makefile.objs +4 -0
@@ -10,6 +10,7 @@ util-obj-y += qapi/qapi-types-char.o util-obj-y += qapi/qapi-types-common.o util-obj-y += qapi/qapi-types-crypto.o util-obj-y += qapi/qapi-types-introspect.o +util-obj-y += qapi/qapi-types-job.o util-obj-y += qapi/qapi-types-migration.o util-obj-y += qapi/qapi-types-misc.o util-obj-y += qapi/qapi-types-net.o
@@ -28,6 +29,7 @@ util-obj-y += qapi/qapi-visit-char.o util-obj-y += qapi/qapi-visit-common.o util-obj-y += qapi/qapi-visit-crypto.o util-obj-y += qapi/qapi-visit-introspect.o +util-obj-y += qapi/qapi-visit-job.o util-obj-y += qapi/qapi-visit-migration.o util-obj-y += qapi/qapi-visit-misc.o util-obj-y += qapi/qapi-visit-net.o
@@ -45,6 +47,7 @@ util-obj-y += qapi/qapi-events-char.o util-obj-y += qapi/qapi-events-common.o util-obj-y += qapi/qapi-events-crypto.o util-obj-y += qapi/qapi-events-introspect.o +util-obj-y += qapi/qapi-events-job.o util-obj-y += qapi/qapi-events-migration.o util-obj-y += qapi/qapi-events-misc.o util-obj-y += qapi/qapi-events-net.o
@@ -140,6 +143,7 @@ common-obj-y += qapi/qapi-commands-char.o common-obj-y += qapi/qapi-commands-common.o common-obj-y += qapi/qapi-commands-crypto.o common-obj-y += qapi/qapi-commands-introspect.o +common-obj-y += qapi/qapi-commands-job.o common-obj-y += qapi/qapi-commands-migration.o common-obj-y += qapi/qapi-commands-misc.o common-obj-y += qapi/qapi-commands-net.o
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH v2 34/40] job: Introduce qapi/job.json
Quoted 6 lines · Kevin Wolf
This adds a separate schema file for all job-related definitions that aren't tied to the block layer.
For a start, move the enums JobType, JobStatus and JobVerb.
Signed-off-by Kevin Wolf <[email protected]>
Quoted 8 lines
--- qapi/block-core.json | 90 +----------------------------------------------- qapi/job.json | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++ qapi/qapi-schema.json | 1 + Makefile | 9 +++++ Makefile.objs | 4 +++ 5 files changed, 109 insertions(+), 89 deletions(-)create mode 100644
qapi/job.json
Do you want to add a MAINTAINERS entry under a block section, so that the block list is cc'd on changes to the new file, in addition to the fact that it is already covered by normal QAPI reviewers? (If we do that, block-core.json could also use such additional coverage.)
Makefile GENERATED_FILES += qapi/qapi-types-char.h qapi/qapi-types-char.c · line 104
Quoted 9 lines
+++ b/Makefile
Makefile +1
-0@@ -98,6 +98,7 @@ GENERATED_FILES += qapi/qapi-types-char.h qapi/qapi-types-char.c GENERATED_FILES += qapi/qapi-types-common.h qapi/qapi-types-common.c GENERATED_FILES += qapi/qapi-types-crypto.h qapi/qapi-types-crypto.c GENERATED_FILES += qapi/qapi-types-introspect.h qapi/qapi-types-introspect.c +GENERATED_FILES += qapi/qapi-types-job.h qapi/qapi-types-job.c GENERATED_FILES += qapi/qapi-types-migration.h qapi/qapi-types-migration.c GENERATED_FILES += qapi/qapi-types-misc.h qapi/qapi-types-misc.c GENERATED_FILES += qapi/qapi-types-net.h qapi/qapi-types-net.c
I've threatened to post a refactoring patch to make it so that adding a new schema file doesn't require so many edits in the Makefile. Maybe I should make good on that threat :)
But in the meantime, this looks good.
Signature · 3 lines
Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
Re: [Qemu-devel] [PATCH v2 34/40] job: Introduce qapi/job.json
In reply to Kevin WolfQuoted 6 lines · Kevin Wolf
This adds a separate schema file for all job-related definitions that aren't tied to the block layer.
For a start, move the enums JobType, JobStatus and JobVerb.
Signed-off-by Kevin Wolf <[email protected]>
Quoted 8 lines
--- qapi/block-core.json | 90 +----------------------------------------------- qapi/job.json | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++ qapi/qapi-schema.json | 1 + Makefile | 9 +++++ Makefile.objs | 4 +++ 5 files changed, 109 insertions(+), 89 deletions(-)create mode 100644
qapi/job.json
This commit now results in a dirty 'git status' for in-tree builds:
qapi/qapi-commands-job.c
qapi/qapi-commands-job.h
qapi/qapi-events-job.c
qapi/qapi-events-job.h
qapi/qapi-types-job.c
qapi/qapi-types-job.h
qapi/qapi-visit-job.c
qapi/qapi-visit-job.h
I'll propose a cleanup patch.
Signature · 3 lines
Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
PATCH v2 35/40 job: Add JOB_STATUS_CHANGE QMP event
In reply to Kevin WolfThis adds a QMP event that is emitted whenever a job transitions from one status to another.
--- qapi/job.json | 14 ++++ job.c | 10 +++ tests/qemu-iotests/030 | 17 +++- tests/qemu-iotests/040 | 2 + tests/qemu-iotests/041 | 17 +++- tests/qemu-iotests/094.out | 7 ++ tests/qemu-iotests/095 | 2 +- tests/qemu-iotests/095.out | 6 ++ tests/qemu-iotests/109 | 2 +- tests/qemu-iotests/109.out | 178 ++++++++++++++++++++++++++++++++++++------ tests/qemu-iotests/124 | 8 ++ tests/qemu-iotests/127.out | 7 ++ tests/qemu-iotests/141 | 13 ++- tests/qemu-iotests/141.out | 29 +++++++ tests/qemu-iotests/144 | 2 +- tests/qemu-iotests/144.out | 7 ++ tests/qemu-iotests/156 | 2 +- tests/qemu-iotests/156.out | 7 ++ tests/qemu-iotests/185 | 12 +-- tests/qemu-iotests/185.out | 10 +++ tests/qemu-iotests/191 | 4 +- tests/qemu-iotests/191.out | 132 +++++++++++++++++++++++++++++++ tests/qemu-iotests/iotests.py | 5 ++ 23 files changed, 449 insertions(+), 44 deletions(-)
qapi/job.json +14 -0
@@ -92,3 +92,17 @@ { 'enum': 'JobVerb', 'data': ['cancel', 'pause', 'resume', 'set-speed', 'complete', 'dismiss', 'finalize' ] } + +## +# @JOB_STATUS_CHANGE: +# +# Emitted when a job transitions to a different status. +# +# @id: The job identifier +# @status: The new job status +# +# Since: 2.13 +## +{ 'event': 'JOB_STATUS_CHANGE', + 'data': { 'id': 'str', + 'status': 'JobStatus' } }
job.c +10 -0
@@ -30,6 +30,7 @@#include "qemu/id.h" #include "qemu/main-loop.h" #include "trace-root.h" +#include "qapi/qapi-events-job.h" static QLIST_HEAD(, Job) jobs = QLIST_HEAD_INITIALIZER(jobs);
@@ -157,6 +158,11 @@ static int job_txn_apply(JobTxn *txn, int fn(Job *), bool lock)return rc; } +static bool job_is_internal (Job *job) +{ +return (job->id ==NULL ); +} +static void job_state_transition (Job *job,JobStatus s1) {JobStatus s0 = job->status;
@@ -166,6 +172,10 @@ static void job_state_transition(Job *job, JobStatus s1) JobStatus_str(s0), JobStatus_str(s1));assert (JobSTT[s0][s1]); job->status = s1; + +if (!job_is_internal (job) && s1 != s0) { +qapi_event_send_job_status_change (job->id, job->status, &error_abort); + } }int job_apply_verb (Job *job,JobVerb verb,Error **errp)
tests/qemu-iotests/030 +14 -3
@@ -304,8 +304,7 @@ class TestParallelOps(iotests.QMPTestCase): result = self.vm.qmp('block-stream', device='node5', base=self.imgs[3], job_id='stream-node6') self.assert_qmp(result, 'error/class', 'GenericError') - event = self.vm.get_qmp_event(wait=True) - self.assertEqual(event['event'], 'BLOCK_JOB_READY') + event = self.vm.event_wait(name='BLOCK_JOB_READY') self.assert_qmp(event, 'data/device', 'commit-drive0') self.assert_qmp(event, 'data/type', 'commit') self.assert_qmp_absent(event, 'data/error')
@@ -565,6 +564,8 @@ class TestEIO(TestErrors): self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE) self.assert_qmp(event, 'data/len', self.image_len) completed = True + elif event['event'] == 'JOB_STATUS_CHANGE': + self.assert_qmp(event, 'data/id', 'drive0') self.assert_no_active_block_jobs() self.vm.shutdown()
@@ -596,6 +597,8 @@ class TestEIO(TestErrors): self.assert_qmp(event, 'data/offset', self.image_len) self.assert_qmp(event, 'data/len', self.image_len) completed = True + elif event['event'] == 'JOB_STATUS_CHANGE': + self.assert_qmp(event, 'data/id', 'drive0') self.assert_no_active_block_jobs() self.vm.shutdown()
@@ -637,6 +640,8 @@ class TestEIO(TestErrors): self.assert_qmp(event, 'data/offset', self.image_len) self.assert_qmp(event, 'data/len', self.image_len) completed = True + elif event['event'] == 'JOB_STATUS_CHANGE': + self.assert_qmp(event, 'data/id', 'drive0') self.assert_no_active_block_jobs() self.vm.shutdown()
@@ -663,6 +668,8 @@ class TestEIO(TestErrors): self.assert_qmp(event, 'data/offset', self.STREAM_BUFFER_SIZE) self.assert_qmp(event, 'data/len', self.image_len) completed = True + elif event['event'] == 'JOB_STATUS_CHANGE': + self.assert_qmp(event, 'data/id', 'drive0') self.assert_no_active_block_jobs() self.vm.shutdown()
@@ -722,6 +729,8 @@ class TestENOSPC(TestErrors): self.assert_qmp(event, 'data/offset', self.image_len) self.assert_qmp(event, 'data/len', self.image_len) completed = True + elif event['event'] == 'JOB_STATUS_CHANGE': + self.assert_qmp(event, 'data/id', 'drive0') self.assert_no_active_block_jobs() self.vm.shutdown()
@@ -751,7 +760,9 @@ class TestStreamStop(iotests.QMPTestCase): time.sleep(0.1) events = self.vm.get_qmp_events(wait=False) - self.assertEqual(events, [], 'unexpected QMP event: %s' % events) + for e in events: + self.assert_qmp(e, 'event', 'JOB_STATUS_CHANGE') + self.assert_qmp(e, 'data/id', 'drive0') self.cancel_and_wait(resume=True)
tests/qemu-iotests/040 +2 -0
@@ -162,6 +162,8 @@ class TestSingleDrive(ImageCommitTestCase): elif event['event'] == 'BLOCK_JOB_CANCELLED': self.assert_qmp(event, 'data/device', 'drive0') cancelled = True + elif event['event'] == 'JOB_STATUS_CHANGE': + self.assert_qmp(event, 'data/id', 'drive0') else: self.fail("Unexpected event %s" % (event['event']))
tests/qemu-iotests/041 +16 -1
@@ -445,6 +445,8 @@ new_state = "1" self.assert_qmp(event, 'data/device', 'drive0') self.assert_qmp(event, 'data/error', 'Input/output error') completed = True + elif event['event'] == 'JOB_STATUS_CHANGE': + self.assert_qmp(event, 'data/id', 'drive0') self.assert_no_active_block_jobs() self.vm.shutdown()
@@ -457,6 +459,10 @@ new_state = "1" self.assert_qmp(result, 'return', {}) event = self.vm.get_qmp_event(wait=True) + while event['event'] == 'JOB_STATUS_CHANGE': + self.assert_qmp(event, 'data/id', 'drive0') + event = self.vm.get_qmp_event(wait=True) + self.assertEquals(event['event'], 'BLOCK_JOB_ERROR') self.assert_qmp(event, 'data/device', 'drive0') self.assert_qmp(event, 'data/operation', 'read')
@@ -478,6 +484,10 @@ new_state = "1" self.assert_qmp(result, 'return', {}) event = self.vm.get_qmp_event(wait=True) + while event['event'] == 'JOB_STATUS_CHANGE': + self.assert_qmp(event, 'data/id', 'drive0') + event = self.vm.get_qmp_event(wait=True) + self.assertEquals(event['event'], 'BLOCK_JOB_ERROR') self.assert_qmp(event, 'data/device', 'drive0') self.assert_qmp(event, 'data/operation', 'read')
@@ -608,7 +618,7 @@ new_state = "1" on_target_error='ignore') self.assert_qmp(result, 'return', {}) - event = self.vm.get_qmp_event(wait=True) + event = self.vm.event_wait(name='BLOCK_JOB_ERROR') self.assertEquals(event['event'], 'BLOCK_JOB_ERROR') self.assert_qmp(event, 'data/device', 'drive0') self.assert_qmp(event, 'data/operation', 'write')
@@ -784,7 +794,12 @@ class TestGranularity(iotests.QMPTestCase): sync='full', target=target_img, mode='absolute-paths', granularity=8192) self.assert_qmp(result, 'return', {}) + event = self.vm.get_qmp_event(wait=60.0) + while event['event'] == 'JOB_STATUS_CHANGE': + self.assert_qmp(event, 'data/id', 'drive0') + event = self.vm.get_qmp_event(wait=60.0) + # Failures will manifest as COMPLETED/ERROR. self.assert_qmp(event, 'event', 'BLOCK_JOB_READY') self.complete_and_wait(drive='drive0', wait_ready=False)
tests/qemu-iotests/094.out +7 -0
@@ -2,10 +2,17 @@ QA output created by 094 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 Formatting 'TEST_DIR/source.IMGFMT', fmt=IMGFMT size=67108864 {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "src"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "src"}} {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "src"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}} {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "src"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "src"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} *** done
tests/qemu-iotests/095 +1 -1
@@ -72,7 +72,7 @@ _send_qemu_cmd $h "{ 'execute': 'qmp_capabilities' }" "return" _send_qemu_cmd $h "{ 'execute': 'block-commit', 'arguments': { 'device': 'test', - 'top': '"${TEST_IMG}.snp1"' } }" "BLOCK_JOB_COMPLETED" + 'top': '"${TEST_IMG}.snp1"' } }" '"status": "null"' _cleanup_qemu
tests/qemu-iotests/095.out +6 -0
@@ -11,8 +11,14 @@ virtual size: 5.0M (5242880 bytes) === Running QEMU Live Commit Test === {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "test"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "test"}} {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "test"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "test"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "test", "len": 104857600, "offset": 104857600, "speed": 0, "type": "commit"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "test"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "test"}} === Base image info after commit and resize === image: TEST_DIR/t.IMGFMT.base
tests/qemu-iotests/109 +1 -1
@@ -64,7 +64,7 @@ function run_qemu() _send_qemu_cmd $QEMU_HANDLE '' "$qmp_event" if test "$qmp_event" = BLOCK_JOB_ERROR; then - _send_qemu_cmd $QEMU_HANDLE '' "BLOCK_JOB_COMPLETED" + _send_qemu_cmd $QEMU_HANDLE '' '"status": "null"' fi _send_qemu_cmd $QEMU_HANDLE '{"execute":"query-block-jobs"}' "return" _send_qemu_cmd $QEMU_HANDLE '{"execute":"quit"}' "return"
tests/qemu-iotests/109.out +156 -22
409 lines not on this page · raw message
tests/qemu-iotests/124 +8 -0
@@ -151,10 +151,17 @@ class TestIncrementalBackupBase(iotests.QMPTestCase): return self.wait_qmp_backup(kwargs['device'], error) + def ignore_job_status_change_events(self): + while True: + e = self.vm.event_wait(name="JOB_STATUS_CHANGE") + if e['data']['status'] == 'null': + break + def wait_qmp_backup(self, device, error='Input/output error'): event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED", match={'data': {'device': device}}) self.assertNotEqual(event, None) + self.ignore_job_status_change_events() try: failure = self.dictpath(event, 'data/error')
@@ -172,6 +179,7 @@ class TestIncrementalBackupBase(iotests.QMPTestCase): event = self.vm.event_wait(name='BLOCK_JOB_CANCELLED', match={'data': {'device': device}}) self.assertNotEqual(event, None) + self.ignore_job_status_change_events() def create_anchor_backup(self, drive=None):
tests/qemu-iotests/127.out +7 -0
@@ -5,10 +5,17 @@ Formatting 'TEST_DIR/t.IMGFMT.overlay1', fmt=IMGFMT size=65536 backing_file=TEST wrote 42/42 bytes at offset 0 42 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "mirror"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "mirror"}} {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "mirror"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "mirror", "len": 65536, "offset": 65536, "speed": 0, "type": "mirror"}} {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "mirror"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "mirror"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "mirror", "len": 65536, "offset": 65536, "speed": 0, "type": "mirror"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "mirror"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "mirror"}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} *** done
tests/qemu-iotests/141 +6 -7
@@ -107,7 +107,7 @@ test_blockjob \ 'format': '$IMGFMT', 'sync': 'none'}}" \ 'return' \ - 'BLOCK_JOB_CANCELLED' + '"status": "null"' echo echo '=== Testing drive-mirror ==='
@@ -124,7 +124,7 @@ test_blockjob \ 'format': '$IMGFMT', 'sync': 'none'}}" \ 'BLOCK_JOB_READY' \ - 'BLOCK_JOB_COMPLETED' + '"status": "null"' echo echo '=== Testing active block-commit ==='
@@ -138,7 +138,7 @@ test_blockjob \ "{'execute': 'block-commit', 'arguments': {'job-id': 'job0', 'device': 'drv0'}}" \ 'BLOCK_JOB_READY' \ - 'BLOCK_JOB_COMPLETED' + '"status": "null"' echo echo '=== Testing non-active block-commit ==='
@@ -157,7 +157,7 @@ test_blockjob \ 'top': '$TEST_DIR/m.$IMGFMT', 'speed': 1}}" \ 'return' \ - 'BLOCK_JOB_CANCELLED' + '"status": "null"' echo echo '=== Testing block-stream ==='
@@ -170,8 +170,7 @@ echo $QEMU_IO -c 'write 0 1M' "$TEST_DIR/b.$IMGFMT" | _filter_qemu_io # With some data to stream (and @speed set to 1), block-stream will not complete -# until we send the block-job-cancel command. Therefore, no event other than -# BLOCK_JOB_CANCELLED will be emitted. +# until we send the block-job-cancel command. test_blockjob \ "{'execute': 'block-stream',
@@ -179,7 +178,7 @@ test_blockjob \ 'device': 'drv0', 'speed': 1}}" \ 'return' \ - 'BLOCK_JOB_CANCELLED' + '"status": "null"' _cleanup_qemu
tests/qemu-iotests/141.out +29 -0
@@ -8,31 +8,50 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/m. {"return": {}} Formatting 'TEST_DIR/o.IMGFMT', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/t.IMGFMT backing_fmt=IMGFMT +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job0"}} {"return": {}} {"error": {"class": "GenericError", "desc": "Node drv0 is in use"}} {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "job0"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_CANCELLED", "data": {"device": "job0", "len": 1048576, "offset": 0, "speed": 0, "type": "backup"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job0"}} {"return": {}} === Testing drive-mirror === {"return": {}} Formatting 'TEST_DIR/o.IMGFMT', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/t.IMGFMT backing_fmt=IMGFMT +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "job0"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "job0", "len": 0, "offset": 0, "speed": 0, "type": "mirror"}} {"return": {}} {"error": {"class": "GenericError", "desc": "Node 'drv0' is busy: node is used as backing hd of 'NODE_NAME'"}} {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job0"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "job0", "len": 0, "offset": 0, "speed": 0, "type": "mirror"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job0"}} {"return": {}} === Testing active block-commit === {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "job0"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "job0", "len": 0, "offset": 0, "speed": 0, "type": "commit"}} {"return": {}} {"error": {"class": "GenericError", "desc": "Node 'drv0' is busy: node is used as backing hd of 'NODE_NAME'"}} {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job0"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "job0", "len": 0, "offset": 0, "speed": 0, "type": "commit"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job0"}} {"return": {}} === Testing non-active block-commit ===
@@ -40,10 +59,15 @@ Formatting 'TEST_DIR/o.IMGFMT', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/t. wrote 1048576/1048576 bytes at offset 0 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job0"}} {"return": {}} {"error": {"class": "GenericError", "desc": "Node drv0 is in use"}} {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "job0"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_CANCELLED", "data": {"device": "job0", "len": 1048576, "offset": 524288, "speed": 1, "type": "commit"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job0"}} {"return": {}} === Testing block-stream ===
@@ -51,9 +75,14 @@ wrote 1048576/1048576 bytes at offset 0 wrote 1048576/1048576 bytes at offset 0 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job0"}} {"return": {}} {"error": {"class": "GenericError", "desc": "Node drv0 is in use"}} {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "aborting", "id": "job0"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_CANCELLED", "data": {"device": "job0", "len": 1048576, "offset": 524288, "speed": 1, "type": "stream"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job0"}} {"return": {}} *** done
tests/qemu-iotests/144 +1 -1
@@ -93,7 +93,7 @@ _send_qemu_cmd $h "{ 'execute': 'block-job-complete', 'arguments': { 'device': 'virtio0' } - }" "COMPLETED" + }" '"status": "null"' echo echo === Performing Live Snapshot 2 ===
tests/qemu-iotests/144.out +7 -0
@@ -12,10 +12,17 @@ Formatting 'TEST_DIR/tmp.qcow2', fmt=qcow2 size=536870912 backing_file=TEST_DIR/ === Performing block-commit on active layer === +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "virtio0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "virtio0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "virtio0"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "virtio0", "len": 0, "offset": 0, "speed": 0, "type": "commit"}} {"return": {}} {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "virtio0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "virtio0"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "virtio0", "len": 0, "offset": 0, "speed": 0, "type": "commit"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "virtio0"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "virtio0"}} === Performing Live Snapshot 2 ===
tests/qemu-iotests/156 +1 -1
@@ -119,7 +119,7 @@ _send_qemu_cmd $QEMU_HANDLE \ _send_qemu_cmd $QEMU_HANDLE \ '' \ - 'BLOCK_JOB_COMPLETED' + '"status": "null"' # Remove the source images rm -f "$TEST_IMG{,.backing,.overlay}"
tests/qemu-iotests/156.out +7 -0
@@ -12,13 +12,20 @@ wrote 131072/131072 bytes at offset 131072 128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) {"return": ""} Formatting 'TEST_DIR/t.IMGFMT.target.overlay', fmt=IMGFMT size=1048576 backing_file=TEST_DIR/t.IMGFMT.target +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "source"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "source"}} {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "source"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "source", "len": 131072, "offset": 131072, "speed": 0, "type": "mirror"}} wrote 65536/65536 bytes at offset 196608 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) {"return": ""} {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "source"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "source"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "source", "len": 196608, "offset": 196608, "speed": 0, "type": "mirror"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "source"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "source"}} read 65536/65536 bytes at offset 0 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
tests/qemu-iotests/185 +7 -5
@@ -118,8 +118,10 @@ _send_qemu_cmd $h \ # If we don't sleep here 'quit' command races with disk I/O sleep 0.5 +# Ignore the JOB_STATUS_CHANGE events while shutting down the VM. Depending on +# the timing, jobs may or may not transition through a paused state. _send_qemu_cmd $h "{ 'execute': 'quit' }" "return" -wait=1 _cleanup_qemu +wait=1 _cleanup_qemu | grep -v 'JOB_STATUS_CHANGE' echo echo === Start active commit job and exit qemu ===
@@ -141,7 +143,7 @@ _send_qemu_cmd $h \ sleep 0.5 _send_qemu_cmd $h "{ 'execute': 'quit' }" "return" -wait=1 _cleanup_qemu +wait=1 _cleanup_qemu | grep -v 'JOB_STATUS_CHANGE' echo echo === Start mirror job and exit qemu ===
@@ -166,7 +168,7 @@ _send_qemu_cmd $h \ sleep 0.5 _send_qemu_cmd $h "{ 'execute': 'quit' }" "return" -wait=1 _cleanup_qemu +wait=1 _cleanup_qemu | grep -v 'JOB_STATUS_CHANGE' echo echo === Start backup job and exit qemu ===
@@ -190,7 +192,7 @@ _send_qemu_cmd $h \ sleep 0.5 _send_qemu_cmd $h "{ 'execute': 'quit' }" "return" -wait=1 _cleanup_qemu +wait=1 _cleanup_qemu | grep -v 'JOB_STATUS_CHANGE' echo echo === Start streaming job and exit qemu ===
@@ -211,7 +213,7 @@ _send_qemu_cmd $h \ sleep 0.5 _send_qemu_cmd $h "{ 'execute': 'quit' }" "return" -wait=1 _cleanup_qemu +wait=1 _cleanup_qemu | grep -v 'JOB_STATUS_CHANGE' _check_test_img
tests/qemu-iotests/185.out +10 -0
@@ -17,6 +17,8 @@ Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 backing_file=TEST_DIR/t.q === Start commit job and exit qemu === +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "disk"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "disk"}} {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
@@ -25,6 +27,8 @@ Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 backing_file=TEST_DIR/t.q === Start active commit job and exit qemu === {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "disk"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "disk"}} {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
@@ -34,6 +38,8 @@ Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 size=67108864 backing_file=TEST_DIR/t.q {"return": {}} Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=67108864 cluster_size=65536 lazy_refcounts=off refcount_bits=16 +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "disk"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "disk"}} {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
@@ -43,6 +49,8 @@ Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=67108864 cluster_size=65536 l {"return": {}} Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=67108864 cluster_size=65536 lazy_refcounts=off refcount_bits=16 +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "disk"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "disk"}} {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
@@ -51,6 +59,8 @@ Formatting 'TEST_DIR/t.qcow2.copy', fmt=qcow2 size=67108864 cluster_size=65536 l === Start streaming job and exit qemu === {"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "disk"}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "disk"}} {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}}
tests/qemu-iotests/191 +2 -2
@@ -83,7 +83,7 @@ _send_qemu_cmd $h \ 'device': 'top', 'base':'$TEST_IMG.base', 'top': '$TEST_IMG.mid' } }" \ - "BLOCK_JOB_COMPLETED" + '"status": "null"' _send_qemu_cmd $h "" "^}" echo
@@ -131,7 +131,7 @@ _send_qemu_cmd $h \ 'device': 'top', 'base':'$TEST_IMG.base', 'top': '$TEST_IMG.mid' } }" \ - "BLOCK_JOB_COMPLETED" + '"status": "null"' _send_qemu_cmd $h "" "^}" echo
tests/qemu-iotests/191.out +132 -0
168 lines not on this page · raw message
tests/qemu-iotests/iotests.py +5 -0
@@ -464,6 +464,9 @@ class QMPTestCase(unittest.TestCase): self.assert_qmp(event, 'data/device', drive) result = event cancelled = True + elif event['event'] == 'JOB_STATUS_CHANGE': + self.assert_qmp(event, 'data/id', drive) + self.assert_no_active_block_jobs() return result
@@ -479,6 +482,8 @@ class QMPTestCase(unittest.TestCase): self.assert_qmp(event, 'data/offset', event['data']['len']) self.assert_no_active_block_jobs() return event + elif event['event'] == 'JOB_STATUS_CHANGE': + self.assert_qmp(event, 'data/id', drive) def wait_ready(self, drive='drive0'): '''Wait until a block job BLOCK_JOB_READY event'''
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH v2 35/40] job: Add JOB_STATUS_CHANGE QMP event
Quoted 9 lines · Kevin Wolf
This adds a QMP event that is emitted whenever a job transitions from one status to another.
Signed-off-by Kevin Wolf <[email protected]>--- qapi/job.json | 14 ++++ job.c | 10 +++
job.c static int job_txn_apply(JobTxn *txn, int fn(Job *), bool lock) · line 163
Quoted 6 lines
job.c +3
-0@@ -157,6 +158,11 @@ static int job_txn_apply(JobTxn *txn, int fn(Job *), bool lock)return rc; } +static bool job_is_internal (Job *job) +{ +return (job->id ==NULL );
The () are not necessary. In fact, you could use !!job->id for less typing, or even rely on the compiler to auto-convert a pointer into bool by just 'return job->id' (although the latter feels a bit too terse to me).
Style is minor, so
Signature · 3 lines
Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
Re: [Qemu-devel] [PATCH v2 35/40] job: Add JOB_STATUS_CHANGE QMP event
In reply to Kevin WolfQuoted 4 lines · Kevin Wolf
This adds a QMP event that is emitted whenever a job transitions from one status to another.
Signed-off-by Kevin Wolf <[email protected]>
That's a lot of events, and a lot are redundant to what we already transmitted under block jobs; it also has the effect of making internal state changes explicit behavior that we're responsible for maintaining for external clients.
Is that what we want here?
(I mean, the answer is probably "Yes" because you're here writing the patch, but I was hoping to find your motivation.)
Re: [Qemu-devel] [PATCH v2 35/40] job: Add JOB_STATUS_CHANGE QMP event
Am 24.05.2018 um 02:02 hat John Snow geschrieben:
Quoted 13 lines · Kevin Wolf
Quoted 4 lines
This adds a QMP event that is emitted whenever a job transitions from one status to another.
Signed-off-by Kevin Wolf <[email protected]>That's a lot of events, and a lot are redundant to what we already transmitted under block jobs; it also has the effect of making internal state changes explicit behavior that we're responsible for maintaining for external clients.
Quoted 4 lines
Is that what we want here?
(I mean, the answer is probably "Yes" because you're here writing the patch, but I was hoping to find your motivation.)
The state change is visible in query-jobs anyway, so allowing the client to get events rather than polling query-jobs doesn't change the amount of information that is exposed.
You're right that some of the events are redundant with existing block job events, but that unavoidable because we can't send BLOCK_JOB_* events for non-block jobs. And not sending JOB_* for block jobs would be inconsistent.
The question that I was asking myself was just whether I'd literally duplicate the existing events, just with s/BLOCK_JOB_/JOB_/, or whether a single event with a status field can do. I think the latter is more elegant (also because it can be implemented in a single place), even if it is emitted a bit more often than strictly necessary.
Kevin
Re: [Qemu-devel] [PATCH v2 35/40] job: Add JOB_STATUS_CHANGE QMP event
Quoted 14 lines · Kevin Wolf
Am 24.05.2018 um 02:02 hat John Snow geschrieben:
Quoted 13 lines · Kevin Wolf
Quoted 4 lines
This adds a QMP event that is emitted whenever a job transitions from one status to another.
Signed-off-by Kevin Wolf <[email protected]>That's a lot of events, and a lot are redundant to what we already transmitted under block jobs; it also has the effect of making internal state changes explicit behavior that we're responsible for maintaining for external clients.
Quoted 9 lines
Quoted 4 lines
Is that what we want here?
(I mean, the answer is probably "Yes" because you're here writing the patch, but I was hoping to find your motivation.)
The state change is visible in query-jobs anyway, so allowing the client to get events rather than polling query-jobs doesn't change the amount of information that is exposed.
With differing amounts of guarantees, though; for querying you have to so happen to catch it and in practice you may never observe many of the status transitions. It's not something that an external client could rely on.
With events, we're guaranteeing you will be able to listen to all possible state transitions. People may begin depending on them in ways we hadn't anticipated just yet.
...Well, as long as the client is connected to the stream, which we suggest may not always be possible with the presence of the concluded state.
Quoted 11 lines
You're right that some of the events are redundant with existing block job events, but that unavoidable because we can't send BLOCK_JOB_* events for non-block jobs. And not sending JOB_* for block jobs would be inconsistent.
The question that I was asking myself was just whether I'd literally duplicate the existing events, just with s/BLOCK_JOB_/JOB_/, or whether a single event with a status field can do. I think the latter is more elegant (also because it can be implemented in a single place), even if it is emitted a bit more often than strictly necessary.
Code-wise I agree that this is more elegant; just wondering if the implications of the additional API guarantees were considered. This means we have to be a bit more careful about how we restructure the state machine in the future, I think.
It also makes the "NULL" state visible, which I didn't really intend... It's probably fine, just a little quirky.
Quoted 2 lines
Kevin
Re: [Qemu-devel] [PATCH v2 35/40] job: Add JOB_STATUS_CHANGE QMP event
Quoted 14 lines · John Snow
Quoted 4 lines
Quoted 4 lines · Kevin Wolf
Quoted 3 lines
This adds a QMP event that is emitted whenever a job transitions from one status to another.
Quoted 9 lines
Quoted 1 line
Quoted 1 line
Signed-off-by Kevin Wolf <[email protected]>The question that I was asking myself was just whether I'd literally duplicate the existing events, just with s/BLOCK_JOB_/JOB_/, or whether a single event with a status field can do. I think the latter is more elegant (also because it can be implemented in a single place), even if it is emitted a bit more often than strictly necessary.
Quoted 7 lines
Code-wise I agree that this is more elegant; just wondering if the implications of the additional API guarantees were considered. This means we have to be a bit more careful about how we restructure the state machine in the future, I think.
It also makes the "NULL" state visible, which I didn't really intend... It's probably fine, just a little quirky.
Is it worth a special case to avoid emitting the event on transition to the NULL state?
Signature · 3 lines
Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
Re: [Qemu-devel] [PATCH v2 35/40] job: Add JOB_STATUS_CHANGE QMP event
Quoted 21 lines · Eric Blake
Quoted 19 lines · John Snow
Quoted 4 lines
Quoted 4 lines · Kevin Wolf
Quoted 3 lines
This adds a QMP event that is emitted whenever a job transitions from one status to another.
Quoted 9 lines
Quoted 1 line
Quoted 1 line
Signed-off-by Kevin Wolf <[email protected]>The question that I was asking myself was just whether I'd literally duplicate the existing events, just with s/BLOCK_JOB_/JOB_/, or whether a single event with a status field can do. I think the latter is more elegant (also because it can be implemented in a single place), even if it is emitted a bit more often than strictly necessary.
Code-wise I agree that this is more elegant; just wondering if the implications of the additional API guarantees were considered. This means we have to be a bit more careful about how we restructure the state machine in the future, I think.
Quoted 6 lines
Quoted 2 lines
It also makes the "NULL" state visible, which I didn't really intend... It's probably fine, just a little quirky.
Is it worth a special case to avoid emitting the event on transition to the NULL state?
I would have done it, but it also doesn't necessarily hurt anything to have it in here either.
Maybe it provides a benefit: I suppose it would definitely indicate to a client that -- regardless of how they configured the job (with auto-dismiss or not) that it serves as final record that the job has *definitely absolutely gone* and can no longer be addressed.
It's just a strange state name for that purpose; I simply didn't *intend* to expose it.
...OTOH, for early failures it seems like an obviously spurious message that we don't need or want.
Well, glad I can give you precisely conflicting feelings on it and arrive at no opinion. Good job everyone, see you tomorrow.
--js
PATCH v2 36/40 job: Add lifecycle QMP commands
In reply to Kevin WolfThis adds QMP commands that control the transition between states of the job lifecycle.
--- qapi/job.json | 99 +++++++++++++++++++++++++++++++++++++++++++ job-qmp.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ MAINTAINERS | 1 + Makefile.objs | 1 + trace-events | 9 ++++ 5 files changed, 244 insertions(+) create mode 100644 job-qmp.c
qapi/job.json +99 -0
@@ -106,3 +106,102 @@ { 'event': 'JOB_STATUS_CHANGE', 'data': { 'id': 'str', 'status': 'JobStatus' } } + +## +# @job-pause: +# +# Pause an active job. +# +# This command returns immediately after marking the active job for pausing. +# Pausing an already paused job is an error. +# +# The job will pause as soon as possible, which means transitioning into the +# PAUSED state if it was RUNNING, or into STANDBY if it was READY. The +# corresponding JOB_STATUS_CHANGE event will be emitted. +# +# Cancelling a paused job automatically resumes it. +# +# @id: The job identifier. +# +# Since: 2.13 +## +{ 'command': 'job-pause', 'data': { 'id': 'str' } } + +## +# @job-resume: +# +# Resume a paused job. +# +# This command returns immediately after resuming a paused job. Resuming an +# already running job is an error. +# +# @id : The job identifier. +# +# Since: 2.13 +## +{ 'command': 'job-resume', 'data': { 'id': 'str' } } + +## +# @job-cancel: +# +# Instruct an active background job to cancel at the next opportunity. +# This command returns immediately after marking the active job for +# cancellation. +# +# The job will cancel as soon as possible and then emit a JOB_STATUS_CHANGE +# event. Usually, the status will change to ABORTING, but it is possible that +# a job successfully completes (e.g. because it was almost done and there was +# no opportunity to cancel earlier than completing the job) and transitions to +# PENDING instead. +# +# @id: The job identifier. +# +# Since: 2.13 +## +{ 'command': 'job-cancel', 'data': { 'id': 'str' } } + + +## +# @job-complete: +# +# Manually trigger completion of an active job in the READY state. +# +# @id: The job identifier. +# +# Since: 2.13 +## +{ 'command': 'job-complete', 'data': { 'id': 'str' } } + +## +# @job-dismiss: +# +# Deletes a job that is in the CONCLUDED state. This command only needs to be +# run explicitly for jobs that don't have automatic dismiss enabled. +# +# This command will refuse to operate on any job that has not yet reached its +# terminal state, JOB_STATUS_CONCLUDED. For jobs that make use of JOB_READY +# event, job-cancel or job-complete will still need to be used as appropriate. +# +# @id: The job identifier. +# +# Since: 2.13 +## +{ 'command': 'job-dismiss', 'data': { 'id': 'str' } } + +## +# @job-finalize: +# +# Instructs all jobs in a transaction (or a single job if it is not part of any +# transaction) to finalize any graph changes and do any necessary cleanup. This +# command requires that all involved jobs are in the PENDING state. +# +# For jobs in a transaction, instructing one job to finalize will force +# ALL jobs in the transaction to finalize, so it is only necessary to instruct +# a single member job to finalize. +# +# @id: The identifier of any job in the transaction, or of a job that is not +# part of any transaction. +# +# Since: 2.13 +## +{ 'command': 'job-finalize', 'data': { 'id': 'str' } }
job-qmp.c new file +134 -0
134 lines not on this page · raw message
MAINTAINERS +1 -0
@@ -1370,6 +1370,7 @@ S: Supported F: blockjob.c F: include/block/blockjob.h F: job.c +F: job-qmp.c F: include/block/job.h F: block/backup.c F: block/commit.c
Makefile.objs +1 -0
@@ -97,6 +97,7 @@ io-obj-y = io/ ifeq ($(CONFIG_SOFTMMU),y) common-obj-y = blockdev.o blockdev-nbd.o block/ common-obj-y += bootdevice.o iothread.o +common-obj-y += job-qmp.o common-obj-y += net/ common-obj-y += qdev-monitor.o device-hotplug.o common-obj-$(CONFIG_WIN32) += os-win32.o
trace-events +9 -0
@@ -109,6 +109,15 @@ job_state_transition(void *job, int ret, const char *legal, const char *s0, con job_apply_verb(void *job, const char *state, const char *verb, const char *legal) "job %p in state %s; applying verb %s (%s)" job_completed(void *job, int ret, int jret) "job %p ret %d corrected ret %d" +# job-qmp.c +qmp_job_cancel(void *job) "job %p" +qmp_job_pause(void *job) "job %p" +qmp_job_resume(void *job) "job %p" +qmp_job_complete(void *job) "job %p" +qmp_job_finalize(void *job) "job %p" +qmp_job_dismiss(void *job) "job %p" + + ### Guest events, keep at bottom
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH v2 36/40] job: Add lifecycle QMP commands
Quoted 13 lines · Kevin Wolf
This adds QMP commands that control the transition between states of the job lifecycle.
Signed-off-by Kevin Wolf <[email protected]>--- qapi/job.json | 99 +++++++++++++++++++++++++++++++++++++++++++ job-qmp.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ MAINTAINERS | 1 + Makefile.objs | 1 + trace-events | 9 ++++ 5 files changed, 244 insertions(+)create mode 100644
job-qmp.c
qapi/job.json line 179
Quoted 5 lines
qapi/job.json +5
-0+## +# @job-dismiss: +# +# Deletes a job that is in the CONCLUDED state. This command only needs to be +# run explicitly for jobs that don't have automatic dismiss enabled.
Did we decide whether it is valid to expect a job with automatic dismiss enabled (old-style block jobs) to use the new job control commands? Or would it be reasonable to require that 'job-dismiss' is an error on jobs with auto-dismiss enabled (as in, if you're going to use new style jobs, you are guaranteed to also have auto-dismiss false, because we don't expose a way to change that flag in new-style jobs; and if you use old style jobs, all management of the job should be done through the old interfaces).
Quoted 10 lines
qapi/job.json +10
-0+# This command will refuse to operate on any job that has not yet reached its +# terminal state, JOB_STATUS_CONCLUDED. For jobs that make use of JOB_READY +# event, job-cancel or job-complete will still need to be used as appropriate. +# +# @id: The job identifier. +# +# Since: 2.13 +## +{ 'command': 'job-dismiss', 'data': { 'id': 'str' } } +
Signature · 3 lines
Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
Re: [Qemu-devel] [PATCH v2 36/40] job: Add lifecycle QMP commands
Am 18.05.2018 um 20:12 hat Eric Blake geschrieben:
Quoted 14 lines · Kevin Wolf
Quoted 13 lines
This adds QMP commands that control the transition between states of the job lifecycle.
Signed-off-by Kevin Wolf <[email protected]>--- qapi/job.json | 99 +++++++++++++++++++++++++++++++++++++++++++ job-qmp.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ MAINTAINERS | 1 + Makefile.objs | 1 + trace-events | 9 ++++ 5 files changed, 244 insertions(+)create mode 100644
job-qmp.c
Quoted 8 lines
qapi/job.json line 179
Quoted 5 lines
qapi/job.json +5
-0+## +# @job-dismiss: +# +# Deletes a job that is in the CONCLUDED state. This command only needs to be +# run explicitly for jobs that don't have automatic dismiss enabled.Did we decide whether it is valid to expect a job with automatic dismiss enabled (old-style block jobs) to use the new job control commands?
"job control commands" is a pretty broad term, so I'd say yes, it's expected that you can use job-* commands on any job. For example, job-pause/resume make perfect sense on a job with auto-dismiss=true.
Quoted 2 lines
Or would it be reasonable to require that 'job-dismiss' is an error on jobs with auto-dismiss enabled
This, too, but only because it implicitly follows from the condition specified above: You'll never catch a job in the CONCLUDED state when you have auto-dismiss=true because it will immediately move on to NULL.
Quoted 4 lines
(as in, if you're going to use new style jobs, you are guaranteed to also have auto-dismiss false, because we don't expose a way to change that flag in new-style jobs; and if you use old style jobs, all management of the job should be done through the old interfaces).
I'm not completely convinced that auto-dismiss=false is the only "correct" setting. That might be the case for libvirt, but possibly not for simple ad-hoc scripts with lower requirements.
We don't have any new jobs yet, so whether we expose the auto-* flags there is a decision yet to be made.
Kevin
Re: [Qemu-devel] [PATCH v2 36/40] job: Add lifecycle QMP commands
In reply to Kevin WolfQuoted 1 line · Kevin Wolf
+{ 'command': 'job-complete', 'data': { 'id': 'str' } }
Do we have to name it this? I've always disliked how "complete" is used as both a verb and an adjective in our code (hence why I used such bizarre phrasings like "concluded" in my jobs patches) ...
I don't necessarily have a better suggestion, but now's the chance to ask the question.
I guess you don't have a better suggestion either, otherwise you'd have changed it.
--js
PATCH v2 37/40 job: Add query-jobs QMP command
In reply to Kevin WolfThis adds a minimal query-jobs implementation that shouldn't pose many design questions. It can later be extended to expose more information, and especially job-specific information.
--- qapi/job.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ include/qemu/job.h | 3 +++ job-qmp.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ job.c | 2 +- 4 files changed, 103 insertions(+), 1 deletion(-)
qapi/job.json +45 -0
@@ -205,3 +205,48 @@ # Since: 2.13 ## { 'command': 'job-finalize', 'data': { 'id': 'str' } } + +## +# @JobInfo: +# +# Information about a job. +# +# @id: The job identifier +# +# @type: The kind of job that is being performed +# +# @status: Current job state/status +# +# @current-progress: Progress made until now. The unit is arbitrary and the +# value can only meaningfully be used for the ratio of +# @offset to @len. The value is monotonically increasing. +# +# @total-progress: Estimated @offset value at the completion of the job. +# This value can arbitrarily change while the job is +# running, in both directions. +# +# @error: If this field is present, the job failed; if it is +# still missing in the CONCLUDED state, this indicates +# successful completion. +# +# The value is a human-readable error message to describe +# the reason for the job failure. It should not be parsed +# by applications. +# +# Since: 2.13 +## +{ 'struct': 'JobInfo', + 'data': { 'id': 'str', 'type': 'JobType', 'status': 'JobStatus', + 'current-progress': 'int', 'total-progress': 'int', + '*error': 'str' } } + +## +# @query-jobs: +# +# Return information about jobs. +# +# Returns: a list with a @JobInfo for each active job +# +# Since: 2.13 +## +{ 'command': 'query-jobs', 'returns': ['JobInfo'] }
include/qemu/job.h +3 -0
@@ -392,6 +392,9 @@ JobType job_type(const Job *job);/** Returns the enum string for the JobType of a given Job. */ const char *job_type_str (const Job *job); +/** Returns true if the job should not be visible to the management layer. */ +bool job_is_internal (Job *job); +/** Returns whether the job is scheduled for cancellation. */ bool job_is_cancelled (Job *job);
job-qmp.c +54 -0
57 lines not on this page · raw message
job.c +1 -1
@@ -158,7 +158,7 @@ static int job_txn_apply(JobTxn *txn, int fn(Job *), bool lock)return rc; } -static bool job_is_internal (Job *job) +bool job_is_internal (Job *job) {return (job->id ==NULL ); }
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH v2 37/40] job: Add query-jobs QMP command
Quoted 12 lines · Kevin Wolf
This adds a minimal query-jobs implementation that shouldn't pose many design questions. It can later be extended to expose more information, and especially job-specific information.
Signed-off-by Kevin Wolf <[email protected]>--- qapi/job.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ include/qemu/job.h | 3 +++ job-qmp.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ job.c | 2 +- 4 files changed, 103 insertions(+), 1 deletion(-)
qapi/job.json line 222
Quoted 4 lines
qapi/job.json +4
-0+# +# @current-progress: Progress made until now. The unit is arbitrary and the +# value can only meaningfully be used for the ratio of +# @offset to @len. The value is monotonically increasing.
s/@offset to @len/@current-progress to @total-progress/
Review
Quoted 2 lines
(quoted diff) +2
-0+# +# @total-progress: Estimated @offset value at the completion of the job.
s/@offset/@current-progress/
Quoted 18 lines
qapi/job.json +18
-0+# This value can arbitrarily change while the job is +# running, in both directions. +# +# @error: If this field is present, the job failed; if it is +# still missing in the CONCLUDED state, this indicates +# successful completion. +# +# The value is a human-readable error message to describe +# the reason for the job failure. It should not be parsed +# by applications. +# +# Since: 2.13 +## +{ 'struct': 'JobInfo', + 'data': { 'id': 'str', 'type': 'JobType', 'status': 'JobStatus', + 'current-progress': 'int', 'total-progress': 'int', + '*error': 'str' } } +
Signature · 3 lines
Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
Re: [Qemu-devel] [PATCH v2 37/40] job: Add query-jobs QMP command
In reply to Kevin WolfQuoted 15 lines · Kevin Wolf
This adds a minimal query-jobs implementation that shouldn't pose many design questions. It can later be extended to expose more information, and especially job-specific information.
Signed-off-by Kevin Wolf <[email protected]>--- qapi/job.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ include/qemu/job.h | 3 +++ job-qmp.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ job.c | 2 +- 4 files changed, 103 insertions(+), 1 deletion(-)qapi/job.json +2
-0+## +# @JobInfo:
qapi/job.json line 241
Quoted 10 lines
qapi/job.json +9
-0+# +# Information about a job. +# +# @id: The job identifier +## +{ 'struct': 'JobInfo', + 'data': { 'id': 'str', 'type': 'JobType', 'status': 'JobStatus', + 'current-progress': 'int', 'total-progress': 'int', + '*error': 'str' } }
Is it worth exposing whether a job is auto-finalize and auto-complete? Goes back to the issue of whether clients of the new job API would ever want/need to rely on the auto- features; while clients of the old blockjob API that get the auto- features by default will never be calling the new query-jobs command.
Signature · 3 lines
Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
Re: [Qemu-devel] [PATCH v2 37/40] job: Add query-jobs QMP command
Am 18.05.2018 um 20:22 hat Eric Blake geschrieben:
Quoted 21 lines · Kevin Wolf
Quoted 19 lines
This adds a minimal query-jobs implementation that shouldn't pose many design questions. It can later be extended to expose more information, and especially job-specific information.
Signed-off-by Kevin Wolf <[email protected]>--- qapi/job.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ include/qemu/job.h | 3 +++ job-qmp.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ job.c | 2 +- 4 files changed, 103 insertions(+), 1 deletion(-)qapi/job.json +6
-0+## +# @JobInfo: +# +# Information about a job. +# +# @id: The job identifier
Quoted 11 lines
qapi/job.json line 241
Quoted 5 lines
qapi/job.json +5
-0+## +{ 'struct': 'JobInfo', + 'data': { 'id': 'str', 'type': 'JobType', 'status': 'JobStatus', + 'current-progress': 'int', 'total-progress': 'int', + '*error': 'str' } }Is it worth exposing whether a job is auto-finalize and auto-complete? Goes back to the issue of whether clients of the new job API would ever want/need to rely on the auto- features; while clients of the old blockjob API that get the auto- features by default will never be calling the new query-jobs command.
There are probably more fields that could be exposed. For most of them, it's not obvious whether we want to expose them, so I went for the minimal useful information here. We can always add more information to a query command; but we can't take information away later if it turns out that exposing it was a bad idea.
Kevin
PATCH v2 38/40 blockjob: Remove BlockJob.driver
In reply to Kevin WolfBlockJob.driver is redundant with Job.driver and only used in very few places any more. Remove it.
--- include/block/blockjob.h | 3 --- blockjob.c | 17 ++++++++++------- 2 files changed, 10 insertions(+), 10 deletions(-)
include/block/blockjob.h +0 -3
@@ -43,9 +43,6 @@ typedef struct BlockJob {/** Data belonging to the generic Job infrastructure */ Job job; -/** The job type, including the job vtable. */ -const BlockJobDriver *driver; -/** The block device on which the job is operating. */ BlockBackend *blk;
blockjob.c +10 -7
@@ -103,10 +103,12 @@ static void block_job_attached_aio_context(AioContext *new_context, void *opaque) {BlockJob *job = opaque; +const JobDriver *drv = job->job.driver; +BlockJobDriver *bjdrv =container_of (drv, BlockJobDriver, job_driver); job->job.aio_context = new_context; -if (job->driver->attached_aio_context) { - job->driver->attached_aio_context (job, new_context); +if (bjdrv->attached_aio_context) { + bjdrv->attached_aio_context (job, new_context); }job_resume (&job->job);
@@ -115,10 +117,12 @@ static void block_job_attached_aio_context(AioContext *new_context,void block_job_drain (Job *job) {BlockJob *bjob =container_of (job, BlockJob, job); +const JobDriver *drv = job->driver; +BlockJobDriver *bjdrv =container_of (drv, BlockJobDriver, job_driver);blk_drain (bjob->blk); -if (bjob->driver->drain) { - bjob->driver->drain (bjob); +if (bjdrv->drain) { + bjdrv->drain (bjob); } }
@@ -201,7 +205,7 @@ bool block_job_is_internal(BlockJob *job)const BlockJobDriver *block_job_driver (BlockJob *job) { -return job->driver; +return container_of (job->job.driver, BlockJobDriver, job_driver); }/* Assumes the job_mutex is held */
@@ -386,8 +390,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,assert (job->job.driver->user_resume == &block_job_user_resume);assert (job->job.driver->drain == &block_job_drain); - job->driver = driver; - job->blk = blk; + job->blk = blk; job->finalize_cancelled_notifier.notify = block_job_event_cancelled; job->finalize_completed_notifier.notify = block_job_event_completed;
Signature · 1 line
2.13.6
PATCH v2 39/40 iotests: Move qmp_to_opts() to VM
In reply to Kevin Wolfqmp_to_opts() used to be a method of QMPTestCase, but recently we started to add more Python test cases that don't make use of QMPTestCase. In order to make the method usable there, move it to VM.
--- tests/qemu-iotests/041 | 6 +++--- tests/qemu-iotests/155 | 2 +- tests/qemu-iotests/iotests.py | 45 ++++++++++++++++++++++--------------------- 3 files changed, 27 insertions(+), 26 deletions(-)
tests/qemu-iotests/041 +3 -3
@@ -1030,9 +1030,9 @@ class TestOrphanedSource(iotests.QMPTestCase): 'read-only': 'on' } self.vm = iotests.VM() - self.vm.add_blockdev(self.qmp_to_opts(blk0)) - self.vm.add_blockdev(self.qmp_to_opts(blk1)) - self.vm.add_blockdev(self.qmp_to_opts(blk2)) + self.vm.add_blockdev(self.vm.qmp_to_opts(blk0)) + self.vm.add_blockdev(self.vm.qmp_to_opts(blk1)) + self.vm.add_blockdev(self.vm.qmp_to_opts(blk2)) self.vm.launch() def tearDown(self):
tests/qemu-iotests/155 +1 -1
@@ -63,7 +63,7 @@ class BaseClass(iotests.QMPTestCase): 'driver': iotests.imgfmt, 'file': {'driver': 'file', 'filename': source_img}} - self.vm.add_blockdev(self.qmp_to_opts(blockdev)) + self.vm.add_blockdev(self.vm.qmp_to_opts(blockdev)) self.vm.add_device('virtio-blk,id=qdev0,drive=source') self.vm.launch()
tests/qemu-iotests/iotests.py +23 -22
63 lines not on this page · raw message
Signature · 1 line
2.13.6
PATCH v2 40/40 qemu-iotests: Test job-* with block jobs
In reply to Kevin WolfThis adds a test case that tests the new job-* QMP commands with mirror and backup block jobs.
--- tests/qemu-iotests/219 | 209 +++++++++++++++++++++++++++++ tests/qemu-iotests/219.out | 327 +++++++++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/group | 1 + 3 files changed, 537 insertions(+) create mode 100755 tests/qemu-iotests/219 create mode 100644 tests/qemu-iotests/219.out
tests/qemu-iotests/219 new file +209 -0
@@ -0,0 +1,209 @@ +#!/usr/bin/env python +# +# Copyright (C) 2018 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Creator/Owner: Kevin Wolf <[email protected]> +# +# Check using the job-* QMP commands with block jobs + +import iotests + +iotests.verify_image_format(supported_fmts=['qcow2']) + +def pause_wait(vm, job_id): + with iotests.Timeout(3, "Timeout waiting for job to pause"): + while True: + result = vm.qmp('query-jobs') + for job in result['return']: + if job['id'] == job_id and job['status'] in ['paused', 'standby']: + return job + +# Test that block-job-pause/resume and job-pause/resume can be mixed +def test_pause_resume(vm): + for pause_cmd, pause_arg in [('block-job-pause', 'device'), + ('job-pause', 'id')]: + for resume_cmd, resume_arg in [('block-job-resume', 'device'), + ('job-resume', 'id')]: + iotests.log('=== Testing %s/%s ===' % (pause_cmd, resume_cmd)) + + iotests.log(vm.qmp(pause_cmd, **{pause_arg: 'job0'})) + pause_wait(vm, 'job0') + iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) + iotests.log(vm.qmp('query-jobs')) + + iotests.log(vm.qmp(resume_cmd, **{resume_arg: 'job0'})) + iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) + iotests.log(vm.qmp('query-jobs')) + +def test_job_lifecycle(vm, job, job_args, has_ready=False): + iotests.log('') + iotests.log('') + iotests.log('Starting block job: %s (auto-finalize: %s; auto-dismiss: %s)' % + (job, + job_args.get('auto-finalize', True), + job_args.get('auto-dismiss', True))) + iotests.log(vm.qmp(job, job_id='job0', **job_args)) + + # Depending on the storage, the first request may or may not have completed + # yet, so filter out the progress. Later query-job calls don't need the + # filtering because the progress is made deterministic by the block job + # speed + result = vm.qmp('query-jobs') + for j in result['return']: + del j['current-progress'] + iotests.log(result) + + # undefined -> created -> running + iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) + iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) + + # RUNNING state: + # pause/resume should work, complete/finalize/dismiss should error out + iotests.log('') + iotests.log('Pause/resume in RUNNING') + test_pause_resume(vm) + + iotests.log(vm.qmp('job-complete', id='job0')) + iotests.log(vm.qmp('job-finalize', id='job0')) + iotests.log(vm.qmp('job-dismiss', id='job0')) + + iotests.log(vm.qmp('block-job-complete', device='job0')) + iotests.log(vm.qmp('block-job-finalize', id='job0')) + iotests.log(vm.qmp('block-job-dismiss', id='job0')) + + # Let the job complete (or transition to READY if it supports that) + iotests.log(vm.qmp('block-job-set-speed', device='job0', speed=0)) + if has_ready: + iotests.log('') + iotests.log('Waiting for READY state...') + vm.event_wait('BLOCK_JOB_READY') + iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) + iotests.log(vm.qmp('query-jobs')) + + # READY state: + # pause/resume/complete should work, finalize/dismiss should error out + iotests.log('') + iotests.log('Pause/resume in READY') + test_pause_resume(vm) + + iotests.log(vm.qmp('job-finalize', id='job0')) + iotests.log(vm.qmp('job-dismiss', id='job0')) + + iotests.log(vm.qmp('block-job-finalize', id='job0')) + iotests.log(vm.qmp('block-job-dismiss', id='job0')) + + # Transition to WAITING + iotests.log(vm.qmp('job-complete', id='job0')) + + # Move to WAITING and PENDING state + iotests.log('') + iotests.log('Waiting for PENDING state...') + iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) + iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) + + if not job_args.get('auto-finalize', True): + # PENDING state: + # finalize should work, pause/complete/dismiss should error out + iotests.log(vm.qmp('query-jobs')) + + iotests.log(vm.qmp('job-pause', id='job0')) + iotests.log(vm.qmp('job-complete', id='job0')) + iotests.log(vm.qmp('job-dismiss', id='job0')) + + iotests.log(vm.qmp('block-job-pause', device='job0')) + iotests.log(vm.qmp('block-job-complete', device='job0')) + iotests.log(vm.qmp('block-job-dismiss', id='job0')) + + # Transition to CONCLUDED + iotests.log(vm.qmp('job-finalize', id='job0')) + + + # Move to CONCLUDED state + iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) + + if not job_args.get('auto-dismiss', True): + # CONCLUDED state: + # dismiss should work, pause/complete/finalize should error out + iotests.log(vm.qmp('query-jobs')) + + iotests.log(vm.qmp('job-pause', id='job0')) + iotests.log(vm.qmp('job-complete', id='job0')) + iotests.log(vm.qmp('job-finalize', id='job0')) + + iotests.log(vm.qmp('block-job-pause', device='job0')) + iotests.log(vm.qmp('block-job-complete', device='job0')) + iotests.log(vm.qmp('block-job-finalize', id='job0')) + + # Transition to NULL + iotests.log(vm.qmp('job-dismiss', id='job0')) + + # Move to NULL state + iotests.log(iotests.filter_qmp_event(vm.event_wait('JOB_STATUS_CHANGE'))) + iotests.log(vm.qmp('query-jobs')) + + +with iotests.FilePath('disk.img') as disk_path, \ + iotests.FilePath('copy.img') as copy_path, \ + iotests.VM() as vm: + + img_size = '4M' + iotests.qemu_img_create('-f', iotests.imgfmt, disk_path, img_size) + iotests.qemu_io('-c', 'write 0 %s' % (img_size), + '-f', iotests.imgfmt, disk_path) + + iotests.log('Launching VM...') + vm.add_blockdev(vm.qmp_to_opts({ + 'driver': iotests.imgfmt, + 'node-name': 'drive0-node', + 'file': { + 'driver': 'file', + 'filename': disk_path, + }, + })) + vm.launch() + + # In order to keep things deterministic (especially progress in query-job, + # but related to this also automatic state transitions like job + # completion), but still get pause points often enough to avoid making this + # test very slow, it's important to have the right ratio between speed and + # buf_size. + # + # For backup, buf_size is hard-coded to the source image cluster size (64k), + # so we'll pick the same for mirror. The slice time, i.e. the granularity + # of the rate limiting is 100ms. With a speed of 256k per second, we can + # get four pause points per second. This gives us 250ms per iteration, + # which should be enough to stay deterministic. + + test_job_lifecycle(vm, 'drive-mirror', has_ready=True, job_args={ + 'device': 'drive0-node', + 'target': copy_path, + 'sync': 'full', + 'speed': 262144, + 'buf_size': 65536, + }) + + for auto_finalize in [True, False]: + for auto_dismiss in [True, False]: + test_job_lifecycle(vm, 'drive-backup', job_args={ + 'device': 'drive0-node', + 'target': copy_path, + 'sync': 'full', + 'speed': 262144, + 'auto-finalize': auto_finalize, + 'auto-dismiss': auto_dismiss, + }) + + vm.shutdown()
tests/qemu-iotests/219.out new file +327 -0
327 lines not on this page · raw message
tests/qemu-iotests/group +1 -0
@@ -216,3 +216,4 @@ 215 rw auto quick 216 rw auto quick 218 rw auto quick +219 rw auto
Signature · 1 line
2.13.6
This series seems to have some coding style problems. See output below for more information:
Hi,
This series seems to have some coding style problems. See output below for more information:
Type: series
Message-id: [email protected]
Subject: [Qemu-devel] [PATCH v2 00/40] Generic background jobs
=== TEST SCRIPT BEGIN ===
#!/bin/bash
BASE=base
n=1
total=$(git log --oneline $BASE.. | wc -l)
failed=0
git config --local diff.renamelimit 0
git config --local diff.renames True
git config --local diff.algorithm histogram
commits="$(git log --format=%H --reverse $BASE..)" for c in $commits; do
echo "Checking PATCH $n/$total: $(git log -n 1 --format=%s $c)..."
if ! git show $c --format=email | ./scripts/checkpatch.pl --mailback -; then
failed=1
echo
fi
n=$((n+1))done
exit $failed
=== TEST SCRIPT END ===
Updating 3c8cf5a9c21ff8782164d1def7f44bd888713384
From https://github.com/patchew-project/qemu
t [tag update] patchew/[email protected] -> patchew/[email protected]
* [new tag] patchew/[email protected] -> patchew/[email protected]
Switched to a new branch 'test'be01aa198a qemu-iotests: Test job-* with block jobs 62233b758c iotests: Move qmp_to_opts() to VM3f946bc442 blockjob: Remove BlockJob.drivercae6373cbd job: Add query-jobs QMP command9bf0565e0c job: Add lifecycle QMP commandsf7363a69b4 job: Add JOB_STATUS_CHANGE QMP event 5a63efe4e4 job: Introduce qapi/job.json2a5e7375fa job: Move progress fields to Job2ae08c1d06 job: Add job_transition_to_ready() 9d76be11bd job: Add job_is_ready()9b51cc2a7e job: Add job_dismiss()555a61a69f job: Add job_yield()798978bcb1 block: Cancel job in bdrv_close_all() callers 4dcb71e4e5 job: Move completion and cancellation to Job ffad8fad4b job: Move transactions to Job0edc41ca48 job: Switch transactions to JobTxn a1073023f2 job: Move job_finish_sync() to Job 6ec521dbdb job: Move .complete callback to Job 9660a731ab job: Add job_drain()1958d0ac38 job: Convert block_job_cancel_async() to Job dc1a8d70b8 job: Move single job finalisation to Job 4e7a12008f job: Add job_event_*()d973eb6546 blockjob: Split block_job_event_pending() 7d447f8ab3 job: Move BlockJobCreateFlags to Job ffa1c9ad30 job: Replace BlockJob.completed with job_is_completed() 13e426855c job: Move pause/resume functions to Job 0c52ff1b24 job: Add job_sleep_ns()5ebf3ba66a job: Move coroutine and related code to Job 4e7b469718 job: Move defer_to_main_loop to Job 896fe05128 job: Add Job.aio_contextd73a94fde9 job: Move cancelled to Jobe386ae83f9 job: Add reference counting3915da0ada job: Move state transitions to Job e2ea58c7b0 job: Maintain a list of all jobs717f2e60bf job: Add job_delete()f31f336bca job: Add JobDriver.job_type048bb87d0a job: Rename BlockJobType into JobType b2707abc22 job: Create Job, JobDriver and job_create() f7c7867229 blockjob: Improve BlockJobInfo.offset/len documentation 7611d0c75f blockjob: Update block-job-pause/resume documentation
=== OUTPUT BEGIN ===
Checking PATCH 1/40: blockjob: Update block-job-pause/resume documentation... Checking PATCH 2/40: blockjob: Improve BlockJobInfo.offset/len documentation... Checking PATCH 3/40: job: Create Job, JobDriver and job_create()...
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? #353:
new file mode 100644
total: 0 errors, 1 warnings, 424 lines checked
Your patch has style problems, please review. If any of these errors are false positives report them to the maintainer, see CHECKPATCH in MAINTAINERS.
Checking PATCH 4/40: job: Rename BlockJobType into JobType... Checking PATCH 5/40: job: Add JobDriver.job_type... Checking PATCH 6/40: job: Add job_delete()...
Checking PATCH 7/40: job: Maintain a list of all jobs... Checking PATCH 8/40: job: Move state transitions to Job...
ERROR: space prohibited before open square bracket '[' #342: FILE: job.c:38:
+ /* U: */ [JOB_STATUS_UNDEFINED] = {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
ERROR: space prohibited before open square bracket '[' #343: FILE: job.c:39:
+ /* C: */ [JOB_STATUS_CREATED] = {0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1},
ERROR: space prohibited before open square bracket '[' #344: FILE: job.c:40:
+ /* R: */ [JOB_STATUS_RUNNING] = {0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0},
ERROR: space prohibited before open square bracket '[' #345: FILE: job.c:41:
+ /* P: */ [JOB_STATUS_PAUSED] = {0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
ERROR: space prohibited before open square bracket '[' #346: FILE: job.c:42:
+ /* Y: */ [JOB_STATUS_READY] = {0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0},
ERROR: space prohibited before open square bracket '[' #347: FILE: job.c:43:
+ /* S: */ [JOB_STATUS_STANDBY] = {0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
ERROR: space prohibited before open square bracket '[' #348: FILE: job.c:44:
+ /* W: */ [JOB_STATUS_WAITING] = {0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0},
ERROR: space prohibited before open square bracket '[' #349: FILE: job.c:45:
+ /* D: */ [JOB_STATUS_PENDING] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0},
ERROR: space prohibited before open square bracket '[' #350: FILE: job.c:46:
+ /* X: */ [JOB_STATUS_ABORTING] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0},
ERROR: space prohibited before open square bracket '[' #351: FILE: job.c:47:
+ /* E: */ [JOB_STATUS_CONCLUDED] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
ERROR: space prohibited before open square bracket '[' #352: FILE: job.c:48:
+ /* N: */ [JOB_STATUS_NULL] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
total: 11 errors, 0 warnings, 528 lines checked
Your patch has style problems, please review. If any of these errors are false positives report them to the maintainer, see CHECKPATCH in MAINTAINERS.
Checking PATCH 9/40: job: Add reference counting...
Checking PATCH 10/40: job: Move cancelled to Job...
Checking PATCH 11/40: job: Add Job.aio_context...
Checking PATCH 12/40: job: Move defer_to_main_loop to Job... Checking PATCH 13/40: job: Move coroutine and related code to Job... Checking PATCH 14/40: job: Add job_sleep_ns()...
Checking PATCH 15/40: job: Move pause/resume functions to Job... Checking PATCH 16/40: job: Replace BlockJob.completed with job_is_completed()... Checking PATCH 17/40: job: Move BlockJobCreateFlags to Job... Checking PATCH 18/40: blockjob: Split block_job_event_pending()... Checking PATCH 19/40: job: Add job_event_*()...
Checking PATCH 20/40: job: Move single job finalisation to Job...
WARNING: line over 80 characters
#508: FILE: include/qemu/job.h:221:
+ int flags, BlockCompletionFunc *cb, void *opaque, Error **errp);
ERROR: "(foo*)" should be "(foo *)"
#578: FILE: job.c:462:
+ block_job_txn_del_job((BlockJob*) job);
ERROR: "(foo*)" should be "(foo *)"
#659: FILE: job.c:543:
+ block_job_txn_del_job((BlockJob*) job);
total: 2 errors, 1 warnings, 620 lines checked
Your patch has style problems, please review. If any of these errors are false positives report them to the maintainer, see CHECKPATCH in MAINTAINERS.
Checking PATCH 21/40: job: Convert block_job_cancel_async() to Job... Checking PATCH 22/40: job: Add job_drain()...
Checking PATCH 23/40: job: Move .complete callback to Job...
Checking PATCH 24/40: job: Move job_finish_sync() to Job...
WARNING: line over 80 characters
#139: FILE: include/qemu/job.h:399:
+int job_finish_sync(Job *job, void (*finish)(Job *, Error **errp), Error **errp);
total: 0 errors, 1 warnings, 144 lines checked
Your patch has style problems, please review. If any of these errors are false positives report them to the maintainer, see CHECKPATCH in MAINTAINERS.
Checking PATCH 25/40: job: Switch transactions to JobTxn... Checking PATCH 26/40: job: Move transactions to Job... Checking PATCH 27/40: job: Move completion and cancellation to Job... Checking PATCH 28/40: block: Cancel job in bdrv_close_all() callers... Checking PATCH 29/40: job: Add job_yield()...
Checking PATCH 30/40: job: Add job_dismiss()...
Checking PATCH 31/40: job: Add job_is_ready()...
Checking PATCH 32/40: job: Add job_transition_to_ready()... Checking PATCH 33/40: job: Move progress fields to Job... Checking PATCH 34/40: job: Introduce qapi/job.json...
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? #235:
new file mode 100644
total: 0 errors, 1 warnings, 294 lines checked
Your patch has style problems, please review. If any of these errors are false positives report them to the maintainer, see CHECKPATCH in MAINTAINERS.
Checking PATCH 35/40: job: Add JOB_STATUS_CHANGE QMP event... Checking PATCH 36/40: job: Add lifecycle QMP commands...
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? #37:
new file mode 100644
total: 0 errors, 1 warnings, 265 lines checked
Your patch has style problems, please review. If any of these errors are false positives report them to the maintainer, see CHECKPATCH in MAINTAINERS.
Checking PATCH 37/40: job: Add query-jobs QMP command... Checking PATCH 38/40: blockjob: Remove BlockJob.driver...
ERROR: initializer for struct BlockJobDriver should normally be const #21: FILE: blockjob.c:107:
+ BlockJobDriver *bjdrv = container_of(drv, BlockJobDriver, job_driver);
ERROR: initializer for struct BlockJobDriver should normally be const #36: FILE: blockjob.c:121:
+ BlockJobDriver *bjdrv = container_of(drv, BlockJobDriver, job_driver);
total: 2 errors, 0 warnings, 54 lines checked
Your patch has style problems, please review. If any of these errors are false positives report them to the maintainer, see CHECKPATCH in MAINTAINERS.
Checking PATCH 39/40: iotests: Move qmp_to_opts() to VM...
WARNING: line over 80 characters
#109: FILE: tests/qemu-iotests/iotests.py:448:
+ self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
total: 0 errors, 1 warnings, 83 lines checked
Your patch has style problems, please review. If any of these errors are false positives report them to the maintainer, see CHECKPATCH in MAINTAINERS.
Checking PATCH 40/40: qemu-iotests: Test job-* with block jobs...
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? #13:
new file mode 100755
total: 0 errors, 1 warnings, 540 lines checked
Your patch has style problems, please review. If any of these errors are false positives report them to the maintainer, see CHECKPATCH in MAINTAINERS.
=== OUTPUT END ===
Test command exited with code: 1
---
Email generated automatically by Patchew [http://patchew.org/].
Please send your feedback to [email protected]
Quoted 3 lines · * Kevin Wolf ([email protected]) wrote:
Before we can make x-blockdev-create a background job, we need to generalise the job infrastructure so that it can be used without any associated block node.
Is there any relationship between what this does, and what Marc-André's 'monitor: add asynchronous command type' tries to do? (See [email protected] 26th March)
Dave
--
Dr. David Alan Gilbert / [email protected] / Manchester, UK
Am 18.05.2018 um 20:41 hat Dr. David Alan Gilbert geschrieben:
Quoted 8 lines · * Kevin Wolf ([email protected]) wrote:
Quoted 3 lines
Before we can make x-blockdev-create a background job, we need to generalise the job infrastructure so that it can be used without any associated block node.
Is there any relationship between what this does, and what Marc-André's 'monitor: add asynchronous command type' tries to do? (See [email protected] 26th March)
Depends on who you ask. :-)
In a way, yes. Marc-André's async commands are for long-running operations and jobs are for long-running operations. However, they are for a different kind of "long-running".
Basically, Marc-André's work is for commands that are generally quick, but perform some blocking operation. Usually commands that are currently implemented synchronously, but always bothered us because they block the VM for a while. They take maybe a few seconds at most, but you really don't want them to block the guest even for short time.
The important part here is you don't want to modify the operations while they're running, they just send their return value when they are done. (In the latest version, Marc-André even made sure not to modify the wire protocol, so IIUC the commands aren't really async any more in the sense that you can have multiple commands running, but they are just non-blocking in the sense that the guest can keep running while they are doing their work.)
In comparison, jobs are typically really long running, like several minutes (like copying a whole disk image). Some of them can even run indefinitely (like mirroring) until you explicitly tell them to stop. You want to continue using the monitor while they are running, and to be able to manage them at runtime (pause/resume/set speed/cancel/...).
So I think both address different problems.
Kevin
Hi
Quoted 30 lines · Kevin Wolf
Am 18.05.2018 um 20:41 hat Dr. David Alan Gilbert geschrieben:
Quoted 8 lines · * Kevin Wolf ([email protected]) wrote:
Quoted 3 lines
Before we can make x-blockdev-create a background job, we need to generalise the job infrastructure so that it can be used without any associated block node.
Is there any relationship between what this does, and what Marc-André's 'monitor: add asynchronous command type' tries to do? (See [email protected] 26th March)
Depends on who you ask. :-)
In a way, yes. Marc-André's async commands are for long-running operations and jobs are for long-running operations. However, they are for a different kind of "long-running".
Basically, Marc-André's work is for commands that are generally quick, but perform some blocking operation. Usually commands that are currently implemented synchronously, but always bothered us because they block the VM for a while. They take maybe a few seconds at most, but you really don't want them to block the guest even for short time.
The important part here is you don't want to modify the operations while they're running, they just send their return value when they are done. (In the latest version, Marc-André even made sure not to modify the wire protocol, so IIUC the commands aren't really async any more in the sense that you can have multiple commands running, but they are just non-blocking in the sense that the guest can keep running while they are doing their work.)
Quoted 7 lines
In comparison, jobs are typically really long running, like several minutes (like copying a whole disk image). Some of them can even run indefinitely (like mirroring) until you explicitly tell them to stop. You want to continue using the monitor while they are running, and to be able to manage them at runtime (pause/resume/set speed/cancel/...).
So I think both address different problems.
I agree with Kevin that both address different needs and complement each other. Right now, QMP commands are handled synchronous, so they block the main thread (or the "OOB" thread). I needed a simple way to handle them asynchronously, *without* breaking or changing an existing QMP command API. Job, on the other hand, provides a lot of facilities that are unnecessary for most short living commands, while adding complexity, at the protocol level (for instance, by using an extra "job-id" space and not being tied to the qmp session). Depending on the facilities Job provide, and the guarantee that must hold when using it, it may make sense or help to transform an existing command to an asynchronous version, but I doubt many of the existing commands will need it. However, if there is a need to introduce job-like facilities to QMP async (such as running concurrent commands, listing running commands, being able to cancel them, being notified on progression etc), then I think we should be careful to use Job. For now, this is unneeded, as QMP async is internal to qemu, the client can't run anything to manage an ongoing async command, so I think both are improvements and will coexist harmoniously.
Quoted 51 lines · * Marc-André Lureau ([email protected]) wrote:
Hi
Quoted 30 lines · Kevin Wolf
Am 18.05.2018 um 20:41 hat Dr. David Alan Gilbert geschrieben:
Quoted 8 lines · * Kevin Wolf ([email protected]) wrote:
Quoted 3 lines
Before we can make x-blockdev-create a background job, we need to generalise the job infrastructure so that it can be used without any associated block node.
Is there any relationship between what this does, and what Marc-André's 'monitor: add asynchronous command type' tries to do? (See [email protected] 26th March)
Depends on who you ask. :-)
In a way, yes. Marc-André's async commands are for long-running operations and jobs are for long-running operations. However, they are for a different kind of "long-running".
Basically, Marc-André's work is for commands that are generally quick, but perform some blocking operation. Usually commands that are currently implemented synchronously, but always bothered us because they block the VM for a while. They take maybe a few seconds at most, but you really don't want them to block the guest even for short time.
The important part here is you don't want to modify the operations while they're running, they just send their return value when they are done. (In the latest version, Marc-André even made sure not to modify the wire protocol, so IIUC the commands aren't really async any more in the sense that you can have multiple commands running, but they are just non-blocking in the sense that the guest can keep running while they are doing their work.)
Quoted 7 lines
In comparison, jobs are typically really long running, like several minutes (like copying a whole disk image). Some of them can even run indefinitely (like mirroring) until you explicitly tell them to stop. You want to continue using the monitor while they are running, and to be able to manage them at runtime (pause/resume/set speed/cancel/...).
So I think both address different problems.
I agree with Kevin that both address different needs and complement each other. Right now, QMP commands are handled synchronous, so they block the main thread (or the "OOB" thread). I needed a simple way to handle them asynchronously, *without* breaking or changing an existing QMP command API. Job, on the other hand, provides a lot of facilities that are unnecessary for most short living commands, while adding complexity, at the protocol level (for instance, by using an extra "job-id" space and not being tied to the qmp session). Depending on the facilities Job provide, and the guarantee that must hold when using it, it may make sense or help to transform an existing command
Quoted 8 lines
to an asynchronous version, but I doubt many of the existing commands will need it. However, if there is a need to introduce job-like facilities to QMP async (such as running concurrent commands, listing running commands, being able to cancel them, being notified on progression etc), then I think we should be careful to use Job. For now, this is unneeded, as QMP async is internal to qemu, the client can't run anything to manage an ongoing async command, so I think both are improvements and will coexist harmoniously.
OK; it seems we are carving stuff up into a whole bunch of different categories based on their longevity and blockableness. (Normal, OOB, Async, Job)
I do worry a bit that pretty much anything that's not 'instant' means it's waiting for something somewhere, and because you're waiting for something that means you've got to be able to fail or cancel it in case that thing never happens.
Dave
--
Dr. David Alan Gilbert / [email protected] / Manchester, UK
Kevin Wolf
AppliedAm 18.05.2018 um 15:20 hat Kevin Wolf geschrieben:
Quoted 11 lines
Before we can make x-blockdev-create a background job, we need to generalise the job infrastructure so that it can be used without any associated block node.
This series extracts a Job object from the block job infrastructure, which should contain everything related to jobs that doesn't require the block layer to be involved.
Job creation with a centralised job-create command (if we even want this) as well as the actual conversion of x-blockdev-create to Job is left for another series.
Applied to the block branch.
Kevin