When jobs are sleeping, for example to enforce a given rate limit, they
can be reentered early, in particular in order to get paused, to update
the rate limit or to get cancelled.
Before this patch, they behave in this case as if they had fully
completed their rate limiting delay. This means that requests are sped
up beyond their limit, violating the constraints that the user gave us.
Change the block jobs to sleep in a loop until the necessary delay is
completed, while still allowing cancelling them immediately as well
pausing (handled by the pause point in job_sleep_ns()) and updating the
rate limit.
This change is also motivated by iotests cases being prone to fail
because drain operations pause and unpause them so often that block jobs
complete earlier than they are supposed to. In particular, the next
commit would fail iotests 030 without this change.
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-Id: <
20230510203601.418015-8-kwolf@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
{
CommitBlockJob *s = container_of(job, CommitBlockJob, common.job);
int64_t offset;
- uint64_t delay_ns = 0;
int ret = 0;
int64_t n = 0; /* bytes */
QEMU_AUTO_VFREE void *buf = NULL;
/* 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.
*/
- job_sleep_ns(&s->common.job, delay_ns);
+ block_job_ratelimit_sleep(&s->common);
if (job_is_cancelled(&s->common.job)) {
break;
}
job_progress_update(&s->common.job, n);
if (copy) {
- delay_ns = block_job_ratelimit_get_delay(&s->common, n);
- } else {
- delay_ns = 0;
+ block_job_ratelimit_processed_bytes(&s->common, n);
}
}
return bytes_handled;
}
-static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
+static void coroutine_fn mirror_iteration(MirrorBlockJob *s)
{
BlockDriverState *source = s->mirror_top_bs->backing->bs;
MirrorOp *pseudo_op;
int64_t offset;
- uint64_t delay_ns = 0, ret = 0;
/* At least the first dirty chunk is mirrored in one iteration. */
int nb_chunks = 1;
bool write_zeroes_ok = bdrv_can_write_zeroes_with_unmap(blk_bs(s->target));
assert(io_bytes);
offset += io_bytes;
nb_chunks -= DIV_ROUND_UP(io_bytes, s->granularity);
- delay_ns = block_job_ratelimit_get_delay(&s->common, io_bytes_acct);
+ block_job_ratelimit_processed_bytes(&s->common, io_bytes_acct);
}
- ret = delay_ns;
fail:
QTAILQ_REMOVE(&s->ops_in_flight, pseudo_op, next);
qemu_co_queue_restart_all(&pseudo_op->waiting_requests);
g_free(pseudo_op);
-
- return ret;
}
static void mirror_free_init(MirrorBlockJob *s)
assert(!s->dbi);
s->dbi = bdrv_dirty_iter_new(s->dirty_bitmap);
for (;;) {
- uint64_t delay_ns = 0;
int64_t cnt, delta;
bool should_complete;
mirror_wait_for_free_in_flight_slot(s);
continue;
} else if (cnt != 0) {
- delay_ns = mirror_iteration(s);
+ mirror_iteration(s);
}
}
}
if (job_is_ready(&s->common.job) && !should_complete) {
- delay_ns = (s->in_flight == 0 &&
- cnt == 0 ? BLOCK_JOB_SLICE_TIME : 0);
+ if (s->in_flight == 0 && cnt == 0) {
+ trace_mirror_before_sleep(s, cnt, job_is_ready(&s->common.job),
+ BLOCK_JOB_SLICE_TIME);
+ job_sleep_ns(&s->common.job, BLOCK_JOB_SLICE_TIME);
+ }
+ } else {
+ block_job_ratelimit_sleep(&s->common);
}
- trace_mirror_before_sleep(s, cnt, job_is_ready(&s->common.job),
- delay_ns);
- job_sleep_ns(&s->common.job, delay_ns);
s->last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
}
BlockDriverState *unfiltered_bs = bdrv_skip_filters(s->target_bs);
int64_t len;
int64_t offset = 0;
- uint64_t delay_ns = 0;
int error = 0;
int64_t n = 0; /* bytes */
/* 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.
*/
- job_sleep_ns(&s->common.job, delay_ns);
+ block_job_ratelimit_sleep(&s->common);
if (job_is_cancelled(&s->common.job)) {
break;
}
/* Publish progress */
job_progress_update(&s->common.job, n);
if (copy) {
- delay_ns = block_job_ratelimit_get_delay(&s->common, n);
- } else {
- delay_ns = 0;
+ block_job_ratelimit_processed_bytes(&s->common, n);
}
}
return block_job_set_speed_locked(job, speed, errp);
}
-int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n)
+void block_job_ratelimit_processed_bytes(BlockJob *job, uint64_t n)
{
IO_CODE();
- return ratelimit_calculate_delay(&job->limit, n);
+ ratelimit_calculate_delay(&job->limit, n);
+}
+
+void block_job_ratelimit_sleep(BlockJob *job)
+{
+ uint64_t delay_ns;
+
+ /*
+ * Sleep at least once. If the job is reentered early, keep waiting until
+ * we've waited for the full time that is necessary to keep the job at the
+ * right speed.
+ *
+ * Make sure to recalculate the delay after each (possibly interrupted)
+ * sleep because the speed can change while the job has yielded.
+ */
+ do {
+ delay_ns = ratelimit_calculate_delay(&job->limit, 0);
+ job_sleep_ns(&job->job, delay_ns);
+ } while (delay_ns && !job_is_cancelled(&job->job));
}
BlockJobInfo *block_job_query_locked(BlockJob *job, Error **errp)
*/
/**
- * block_job_ratelimit_get_delay:
+ * block_job_ratelimit_processed_bytes:
*
- * Calculate and return delay for the next request in ns. See the documentation
- * of ratelimit_calculate_delay() for details.
+ * To be called after some work has been done. Adjusts the delay for the next
+ * request. See the documentation of ratelimit_calculate_delay() for details.
*/
-int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n);
+void block_job_ratelimit_processed_bytes(BlockJob *job, uint64_t n);
+
+/**
+ * Put the job to sleep (assuming that it wasn't canceled) to throttle it to the
+ * right speed according to its rate limiting.
+ */
+void block_job_ratelimit_sleep(BlockJob *job);
/**
* block_job_error_action: