From dcd042282d855edf70df90b7d61d33b515320b7a Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Thu, 8 May 2014 16:34:37 +0200 Subject: block: add bdrv_set_aio_context() Up until now all BlockDriverState instances have used the QEMU main loop for fd handlers, timers, and BHs. This is not scalable on SMP guests and hosts so we need to move to a model with multiple event loops on different host CPUs. bdrv_set_aio_context() assigns the AioContext event loop to use for a particular BlockDriverState. It first detaches the entire BlockDriverState graph from the current AioContext and then attaches to the new AioContext. This function will be used by virtio-blk data-plane to assign a BlockDriverState to its IOThread AioContext. Make bdrv_aio_set_context() public since data-plane should not include block_int.h. Signed-off-by: Stefan Hajnoczi --- include/block/block.h | 11 +++++++++++ include/block/block_int.h | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) (limited to 'include') diff --git a/include/block/block.h b/include/block/block.h index faee3aa246..292754f9fe 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -574,4 +574,15 @@ int bdrv_debug_remove_breakpoint(BlockDriverState *bs, const char *tag); int bdrv_debug_resume(BlockDriverState *bs, const char *tag); bool bdrv_debug_is_suspended(BlockDriverState *bs, const char *tag); +/** + * bdrv_set_aio_context: + * + * Changes the #AioContext used for fd handlers, timers, and BHs by this + * BlockDriverState and all its children. + * + * This function must be called from the old #AioContext or with a lock held so + * the old #AioContext is not executing. + */ +void bdrv_set_aio_context(BlockDriverState *bs, AioContext *new_context); + #endif diff --git a/include/block/block_int.h b/include/block/block_int.h index f2e753f632..93ec86f3f7 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -247,6 +247,19 @@ struct BlockDriver { */ int (*bdrv_has_zero_init)(BlockDriverState *bs); + /* Remove fd handlers, timers, and other event loop callbacks so the event + * loop is no longer in use. Called with no in-flight requests and in + * depth-first traversal order with parents before child nodes. + */ + void (*bdrv_detach_aio_context)(BlockDriverState *bs); + + /* Add fd handlers, timers, and other event loop callbacks so I/O requests + * can be processed again. Called with no in-flight requests and in + * depth-first traversal order with child nodes before parent nodes. + */ + void (*bdrv_attach_aio_context)(BlockDriverState *bs, + AioContext *new_context); + QLIST_ENTRY(BlockDriver) list; }; @@ -297,6 +310,8 @@ struct BlockDriverState { const BlockDevOps *dev_ops; void *dev_opaque; + AioContext *aio_context; /* event loop used for fd handlers, timers, etc */ + char filename[1024]; char backing_file[1024]; /* if non zero, the image is a diff of this file image */ @@ -396,6 +411,27 @@ void bdrv_add_before_write_notifier(BlockDriverState *bs, */ AioContext *bdrv_get_aio_context(BlockDriverState *bs); +/** + * bdrv_detach_aio_context: + * + * May be called from .bdrv_detach_aio_context() to detach children from the + * current #AioContext. This is only needed by block drivers that manage their + * own children. Both ->file and ->backing_hd are automatically handled and + * block drivers should not call this function on them explicitly. + */ +void bdrv_detach_aio_context(BlockDriverState *bs); + +/** + * bdrv_attach_aio_context: + * + * May be called from .bdrv_attach_aio_context() to attach children to the new + * #AioContext. This is only needed by block drivers that manage their own + * children. Both ->file and ->backing_hd are automatically handled and block + * drivers should not call this function on them explicitly. + */ +void bdrv_attach_aio_context(BlockDriverState *bs, + AioContext *new_context); + #ifdef _WIN32 int is_windows_drive(const char *filename); #endif -- cgit v1.2.3-55-g7522 From 76ef2cf5493a215efc351f48ae7094d6c183fcac Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Thu, 8 May 2014 16:34:58 +0200 Subject: raw-posix: drop raw_get_aio_fd() since it is no longer used virtio-blk data-plane now uses the QEMU block layer for I/O. We do not need raw_get_aio_fd() anymore. It was a layering violation anyway, so let's get rid of it. Signed-off-by: Stefan Hajnoczi --- block/raw-posix.c | 34 ---------------------------------- include/block/block.h | 9 --------- 2 files changed, 43 deletions(-) (limited to 'include') diff --git a/block/raw-posix.c b/block/raw-posix.c index ffdb1763f8..c2b30be3d3 100644 --- a/block/raw-posix.c +++ b/block/raw-posix.c @@ -2331,40 +2331,6 @@ static BlockDriver bdrv_host_cdrom = { }; #endif /* __FreeBSD__ */ -#ifdef CONFIG_LINUX_AIO -/** - * Return the file descriptor for Linux AIO - * - * This function is a layering violation and should be removed when it becomes - * possible to call the block layer outside the global mutex. It allows the - * caller to hijack the file descriptor so I/O can be performed outside the - * block layer. - */ -int raw_get_aio_fd(BlockDriverState *bs) -{ - BDRVRawState *s; - - if (!bs->drv) { - return -ENOMEDIUM; - } - - if (bs->drv == bdrv_find_format("raw")) { - bs = bs->file; - } - - /* raw-posix has several protocols so just check for raw_aio_readv */ - if (bs->drv->bdrv_aio_readv != raw_aio_readv) { - return -ENOTSUP; - } - - s = bs->opaque; - if (!s->use_aio) { - return -ENOTSUP; - } - return s->fd; -} -#endif /* CONFIG_LINUX_AIO */ - static void bdrv_file_init(void) { /* diff --git a/include/block/block.h b/include/block/block.h index 292754f9fe..29ac56da2c 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -481,15 +481,6 @@ void bdrv_op_block_all(BlockDriverState *bs, Error *reason); void bdrv_op_unblock_all(BlockDriverState *bs, Error *reason); bool bdrv_op_blocker_is_empty(BlockDriverState *bs); -#ifdef CONFIG_LINUX_AIO -int raw_get_aio_fd(BlockDriverState *bs); -#else -static inline int raw_get_aio_fd(BlockDriverState *bs) -{ - return -ENOTSUP; -} -#endif - enum BlockAcctType { BDRV_ACCT_READ, BDRV_ACCT_WRITE, -- cgit v1.2.3-55-g7522 From db519cba8713fb49aa5233e8debe61dccdd3a57f Mon Sep 17 00:00:00 2001 From: Fam Zheng Date: Thu, 15 May 2014 19:22:05 +0800 Subject: block: Move declaration of bdrv_get_aio_context to block.h block_int.h is for block layer and block drivers, other code shouldn't include it. But similar to bdrv_set_aio_context, bdrv_get_aio_context should also be accessible from outside of block layer. Move it. Signed-off-by: Fam Zheng Signed-off-by: Stefan Hajnoczi --- include/block/block.h | 7 +++++++ include/block/block_int.h | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/include/block/block.h b/include/block/block.h index 29ac56da2c..7d86e29cf4 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -565,6 +565,13 @@ int bdrv_debug_remove_breakpoint(BlockDriverState *bs, const char *tag); int bdrv_debug_resume(BlockDriverState *bs, const char *tag); bool bdrv_debug_is_suspended(BlockDriverState *bs, const char *tag); +/** + * bdrv_get_aio_context: + * + * Returns: the currently bound #AioContext + */ +AioContext *bdrv_get_aio_context(BlockDriverState *bs); + /** * bdrv_set_aio_context: * diff --git a/include/block/block_int.h b/include/block/block_int.h index 93ec86f3f7..8d58334c1d 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -404,13 +404,6 @@ void bdrv_set_io_limits(BlockDriverState *bs, void bdrv_add_before_write_notifier(BlockDriverState *bs, NotifierWithReturn *notifier); -/** - * bdrv_get_aio_context: - * - * Returns: the currently bound #AioContext - */ -AioContext *bdrv_get_aio_context(BlockDriverState *bs); - /** * bdrv_detach_aio_context: * -- cgit v1.2.3-55-g7522 From 5a05cbeeaaa2ec463d48c0026e8e6be243ea0bab Mon Sep 17 00:00:00 2001 From: Fam Zheng Date: Thu, 22 May 2014 16:22:42 +0800 Subject: virtio-blk: Factor out virtio_blk_handle_scsi_req from virtio_blk_handle_scsi The common logic to process a scsi request in a VirtQueueElement is extracted to a function to share with dataplane. This makes VirtIOBlockReq.scsi unused, so drop it. Signed-off-by: Fam Zheng Signed-off-by: Stefan Hajnoczi --- hw/block/virtio-blk.c | 75 +++++++++++++++++++++++------------------- include/hw/virtio/virtio-blk.h | 3 ++ 2 files changed, 44 insertions(+), 34 deletions(-) (limited to 'include') diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c index 5e9433d9ba..0b1446e2c4 100644 --- a/hw/block/virtio-blk.c +++ b/hw/block/virtio-blk.c @@ -33,7 +33,6 @@ typedef struct VirtIOBlockReq VirtQueueElement elem; struct virtio_blk_inhdr *in; struct virtio_blk_outhdr *out; - struct virtio_scsi_inhdr *scsi; QEMUIOVector qiov; struct VirtIOBlockReq *next; BlockAcctCookie acct; @@ -125,13 +124,15 @@ static VirtIOBlockReq *virtio_blk_get_request(VirtIOBlock *s) return req; } -static void virtio_blk_handle_scsi(VirtIOBlockReq *req) +int virtio_blk_handle_scsi_req(VirtIOBlock *blk, + VirtQueueElement *elem) { + int status = VIRTIO_BLK_S_OK; + struct virtio_scsi_inhdr *scsi = NULL; #ifdef __linux__ - int ret; int i; + struct sg_io_hdr hdr; #endif - int status = VIRTIO_BLK_S_OK; /* * We require at least one output segment each for the virtio_blk_outhdr @@ -140,19 +141,18 @@ static void virtio_blk_handle_scsi(VirtIOBlockReq *req) * We also at least require the virtio_blk_inhdr, the virtio_scsi_inhdr * and the sense buffer pointer in the input segments. */ - if (req->elem.out_num < 2 || req->elem.in_num < 3) { - virtio_blk_req_complete(req, VIRTIO_BLK_S_IOERR); - g_free(req); - return; + if (elem->out_num < 2 || elem->in_num < 3) { + status = VIRTIO_BLK_S_IOERR; + goto fail; } /* * The scsi inhdr is placed in the second-to-last input segment, just * before the regular inhdr. */ - req->scsi = (void *)req->elem.in_sg[req->elem.in_num - 2].iov_base; + scsi = (void *)elem->in_sg[elem->in_num - 2].iov_base; - if (!req->dev->blk.scsi) { + if (!blk->blk.scsi) { status = VIRTIO_BLK_S_UNSUPP; goto fail; } @@ -160,43 +160,42 @@ static void virtio_blk_handle_scsi(VirtIOBlockReq *req) /* * No support for bidirection commands yet. */ - if (req->elem.out_num > 2 && req->elem.in_num > 3) { + if (elem->out_num > 2 && elem->in_num > 3) { status = VIRTIO_BLK_S_UNSUPP; goto fail; } #ifdef __linux__ - struct sg_io_hdr hdr; memset(&hdr, 0, sizeof(struct sg_io_hdr)); hdr.interface_id = 'S'; - hdr.cmd_len = req->elem.out_sg[1].iov_len; - hdr.cmdp = req->elem.out_sg[1].iov_base; + hdr.cmd_len = elem->out_sg[1].iov_len; + hdr.cmdp = elem->out_sg[1].iov_base; hdr.dxfer_len = 0; - if (req->elem.out_num > 2) { + if (elem->out_num > 2) { /* * If there are more than the minimally required 2 output segments * there is write payload starting from the third iovec. */ hdr.dxfer_direction = SG_DXFER_TO_DEV; - hdr.iovec_count = req->elem.out_num - 2; + hdr.iovec_count = elem->out_num - 2; for (i = 0; i < hdr.iovec_count; i++) - hdr.dxfer_len += req->elem.out_sg[i + 2].iov_len; + hdr.dxfer_len += elem->out_sg[i + 2].iov_len; - hdr.dxferp = req->elem.out_sg + 2; + hdr.dxferp = elem->out_sg + 2; - } else if (req->elem.in_num > 3) { + } else if (elem->in_num > 3) { /* * If we have more than 3 input segments the guest wants to actually * read data. */ hdr.dxfer_direction = SG_DXFER_FROM_DEV; - hdr.iovec_count = req->elem.in_num - 3; + hdr.iovec_count = elem->in_num - 3; for (i = 0; i < hdr.iovec_count; i++) - hdr.dxfer_len += req->elem.in_sg[i].iov_len; + hdr.dxfer_len += elem->in_sg[i].iov_len; - hdr.dxferp = req->elem.in_sg; + hdr.dxferp = elem->in_sg; } else { /* * Some SCSI commands don't actually transfer any data. @@ -204,11 +203,11 @@ static void virtio_blk_handle_scsi(VirtIOBlockReq *req) hdr.dxfer_direction = SG_DXFER_NONE; } - hdr.sbp = req->elem.in_sg[req->elem.in_num - 3].iov_base; - hdr.mx_sb_len = req->elem.in_sg[req->elem.in_num - 3].iov_len; + hdr.sbp = elem->in_sg[elem->in_num - 3].iov_base; + hdr.mx_sb_len = elem->in_sg[elem->in_num - 3].iov_len; - ret = bdrv_ioctl(req->dev->bs, SG_IO, &hdr); - if (ret) { + status = bdrv_ioctl(blk->bs, SG_IO, &hdr); + if (status) { status = VIRTIO_BLK_S_UNSUPP; goto fail; } @@ -224,23 +223,31 @@ static void virtio_blk_handle_scsi(VirtIOBlockReq *req) hdr.status = CHECK_CONDITION; } - stl_p(&req->scsi->errors, + stl_p(&scsi->errors, hdr.status | (hdr.msg_status << 8) | (hdr.host_status << 16) | (hdr.driver_status << 24)); - stl_p(&req->scsi->residual, hdr.resid); - stl_p(&req->scsi->sense_len, hdr.sb_len_wr); - stl_p(&req->scsi->data_len, hdr.dxfer_len); + stl_p(&scsi->residual, hdr.resid); + stl_p(&scsi->sense_len, hdr.sb_len_wr); + stl_p(&scsi->data_len, hdr.dxfer_len); - virtio_blk_req_complete(req, status); - g_free(req); - return; + return status; #else abort(); #endif fail: /* Just put anything nonzero so that the ioctl fails in the guest. */ - stl_p(&req->scsi->errors, 255); + if (scsi) { + stl_p(&scsi->errors, 255); + } + return status; +} + +static void virtio_blk_handle_scsi(VirtIOBlockReq *req) +{ + int status; + + status = virtio_blk_handle_scsi_req(req->dev, &req->elem); virtio_blk_req_complete(req, status); g_free(req); } diff --git a/include/hw/virtio/virtio-blk.h b/include/hw/virtio/virtio-blk.h index e4c41ff2ef..4bc9b549ad 100644 --- a/include/hw/virtio/virtio-blk.h +++ b/include/hw/virtio/virtio-blk.h @@ -155,4 +155,7 @@ typedef struct VirtIOBlock { void virtio_blk_set_conf(DeviceState *dev, VirtIOBlkConf *blk); +int virtio_blk_handle_scsi_req(VirtIOBlock *blk, + VirtQueueElement *elem); + #endif -- cgit v1.2.3-55-g7522 From 13af91ebf08d463d3b025cd396d4d11caceac02d Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Wed, 14 May 2014 16:22:45 +0200 Subject: throttle: add throttle_detach/attach_aio_context() Block I/O throttling uses timers and currently always adds them to the main loop. Throttling will break if bdrv_set_aio_context() is used to move a BlockDriverState to a different AioContext. This patch adds throttle_detach/attach_aio_context() interfaces so the throttling timers and uses them to move timers to the new AioContext. Note that bdrv_set_aio_context() already drains all requests so we're sure no throttled requests are pending. The test cases need to be updated since the throttle_init() interface has changed. Signed-off-by: Stefan Hajnoczi Reviewed-by: Benoit Canet --- block.c | 7 +++++++ include/qemu/throttle.h | 10 ++++++++++ tests/test-throttle.c | 25 ++++++++++++++++++++----- util/throttle.c | 27 +++++++++++++++++++++++---- 4 files changed, 60 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/block.c b/block.c index 1fd38159e2..17f763db79 100644 --- a/block.c +++ b/block.c @@ -179,6 +179,7 @@ void bdrv_io_limits_enable(BlockDriverState *bs) { assert(!bs->io_limits_enabled); throttle_init(&bs->throttle_state, + bdrv_get_aio_context(bs), QEMU_CLOCK_VIRTUAL, bdrv_throttle_read_timer_cb, bdrv_throttle_write_timer_cb, @@ -5671,6 +5672,9 @@ void bdrv_detach_aio_context(BlockDriverState *bs) return; } + if (bs->io_limits_enabled) { + throttle_detach_aio_context(&bs->throttle_state); + } if (bs->drv->bdrv_detach_aio_context) { bs->drv->bdrv_detach_aio_context(bs); } @@ -5702,6 +5706,9 @@ void bdrv_attach_aio_context(BlockDriverState *bs, if (bs->drv->bdrv_attach_aio_context) { bs->drv->bdrv_attach_aio_context(bs, new_context); } + if (bs->io_limits_enabled) { + throttle_attach_aio_context(&bs->throttle_state, new_context); + } } void bdrv_set_aio_context(BlockDriverState *bs, AioContext *new_context) diff --git a/include/qemu/throttle.h b/include/qemu/throttle.h index ab29b0b918..b890613a9c 100644 --- a/include/qemu/throttle.h +++ b/include/qemu/throttle.h @@ -67,6 +67,11 @@ typedef struct ThrottleState { int64_t previous_leak; /* timestamp of the last leak done */ QEMUTimer * timers[2]; /* timers used to do the throttling */ QEMUClockType clock_type; /* the clock used */ + + /* Callbacks */ + QEMUTimerCB *read_timer_cb; + QEMUTimerCB *write_timer_cb; + void *timer_opaque; } ThrottleState; /* operations on single leaky buckets */ @@ -82,6 +87,7 @@ bool throttle_compute_timer(ThrottleState *ts, /* init/destroy cycle */ void throttle_init(ThrottleState *ts, + AioContext *aio_context, QEMUClockType clock_type, void (read_timer)(void *), void (write_timer)(void *), @@ -89,6 +95,10 @@ void throttle_init(ThrottleState *ts, void throttle_destroy(ThrottleState *ts); +void throttle_detach_aio_context(ThrottleState *ts); + +void throttle_attach_aio_context(ThrottleState *ts, AioContext *new_context); + bool throttle_have_timer(ThrottleState *ts); /* configuration */ diff --git a/tests/test-throttle.c b/tests/test-throttle.c index 1d4ffd3603..5fa5000124 100644 --- a/tests/test-throttle.c +++ b/tests/test-throttle.c @@ -12,8 +12,10 @@ #include #include +#include "block/aio.h" #include "qemu/throttle.h" +AioContext *ctx; LeakyBucket bkt; ThrottleConfig cfg; ThrottleState ts; @@ -104,7 +106,8 @@ static void test_init(void) memset(&ts, 1, sizeof(ts)); /* init the structure */ - throttle_init(&ts, QEMU_CLOCK_VIRTUAL, read_timer_cb, write_timer_cb, &ts); + throttle_init(&ts, ctx, QEMU_CLOCK_VIRTUAL, + read_timer_cb, write_timer_cb, &ts); /* check initialized fields */ g_assert(ts.clock_type == QEMU_CLOCK_VIRTUAL); @@ -126,7 +129,8 @@ static void test_init(void) static void test_destroy(void) { int i; - throttle_init(&ts, QEMU_CLOCK_VIRTUAL, read_timer_cb, write_timer_cb, &ts); + throttle_init(&ts, ctx, QEMU_CLOCK_VIRTUAL, + read_timer_cb, write_timer_cb, &ts); throttle_destroy(&ts); for (i = 0; i < 2; i++) { g_assert(!ts.timers[i]); @@ -165,7 +169,8 @@ static void test_config_functions(void) orig_cfg.op_size = 1; - throttle_init(&ts, QEMU_CLOCK_VIRTUAL, read_timer_cb, write_timer_cb, &ts); + throttle_init(&ts, ctx, QEMU_CLOCK_VIRTUAL, + read_timer_cb, write_timer_cb, &ts); /* structure reset by throttle_init previous_leak should be null */ g_assert(!ts.previous_leak); throttle_config(&ts, &orig_cfg); @@ -324,7 +329,8 @@ static void test_have_timer(void) g_assert(!throttle_have_timer(&ts)); /* init the structure */ - throttle_init(&ts, QEMU_CLOCK_VIRTUAL, read_timer_cb, write_timer_cb, &ts); + throttle_init(&ts, ctx, QEMU_CLOCK_VIRTUAL, + read_timer_cb, write_timer_cb, &ts); /* timer set by init should return true */ g_assert(throttle_have_timer(&ts)); @@ -357,7 +363,8 @@ static bool do_test_accounting(bool is_ops, /* are we testing bps or ops */ cfg.op_size = op_size; - throttle_init(&ts, QEMU_CLOCK_VIRTUAL, read_timer_cb, write_timer_cb, &ts); + throttle_init(&ts, ctx, QEMU_CLOCK_VIRTUAL, + read_timer_cb, write_timer_cb, &ts); throttle_config(&ts, &cfg); /* account a read */ @@ -461,7 +468,15 @@ static void test_accounting(void) int main(int argc, char **argv) { + GSource *src; + init_clocks(); + + ctx = aio_context_new(); + src = aio_get_g_source(ctx); + g_source_attach(src, NULL); + g_source_unref(src); + do {} while (g_main_context_iteration(NULL, false)); /* tests in the same order as the header function declarations */ diff --git a/util/throttle.c b/util/throttle.c index 02e6f15587..f976ac7de5 100644 --- a/util/throttle.c +++ b/util/throttle.c @@ -22,6 +22,7 @@ #include "qemu/throttle.h" #include "qemu/timer.h" +#include "block/aio.h" /* This function make a bucket leak * @@ -157,8 +158,18 @@ bool throttle_compute_timer(ThrottleState *ts, return false; } +/* Add timers to event loop */ +void throttle_attach_aio_context(ThrottleState *ts, AioContext *new_context) +{ + ts->timers[0] = aio_timer_new(new_context, ts->clock_type, SCALE_NS, + ts->read_timer_cb, ts->timer_opaque); + ts->timers[1] = aio_timer_new(new_context, ts->clock_type, SCALE_NS, + ts->write_timer_cb, ts->timer_opaque); +} + /* To be called first on the ThrottleState */ void throttle_init(ThrottleState *ts, + AioContext *aio_context, QEMUClockType clock_type, QEMUTimerCB *read_timer_cb, QEMUTimerCB *write_timer_cb, @@ -167,8 +178,10 @@ void throttle_init(ThrottleState *ts, memset(ts, 0, sizeof(ThrottleState)); ts->clock_type = clock_type; - ts->timers[0] = timer_new_ns(clock_type, read_timer_cb, timer_opaque); - ts->timers[1] = timer_new_ns(clock_type, write_timer_cb, timer_opaque); + ts->read_timer_cb = read_timer_cb; + ts->write_timer_cb = write_timer_cb; + ts->timer_opaque = timer_opaque; + throttle_attach_aio_context(ts, aio_context); } /* destroy a timer */ @@ -181,8 +194,8 @@ static void throttle_timer_destroy(QEMUTimer **timer) *timer = NULL; } -/* To be called last on the ThrottleState */ -void throttle_destroy(ThrottleState *ts) +/* Remove timers from event loop */ +void throttle_detach_aio_context(ThrottleState *ts) { int i; @@ -191,6 +204,12 @@ void throttle_destroy(ThrottleState *ts) } } +/* To be called last on the ThrottleState */ +void throttle_destroy(ThrottleState *ts) +{ + throttle_detach_aio_context(ts); +} + /* is any throttling timer configured */ bool throttle_have_timer(ThrottleState *ts) { -- cgit v1.2.3-55-g7522