diff --git a/include/spdk/bdev.h b/include/spdk/bdev.h index 6de73a8ae..4778ed1ed 100644 --- a/include/spdk/bdev.h +++ b/include/spdk/bdev.h @@ -199,6 +199,14 @@ typedef void (*spdk_bdev_fini_cb)(void *cb_arg); typedef void (*spdk_bdev_get_device_stat_cb)(struct spdk_bdev *bdev, struct spdk_bdev_io_stat *stat, void *cb_arg, int rc); +/** + * Block device channel IO timeout callback + * + * \param cb_arg Callback argument + * \param bdev_io The IO cause the timeout + */ +typedef void (*spdk_bdev_io_timeout_cb)(void *cb_arg, struct spdk_bdev_io *bdev_io); + /** * Initialize block device modules. * @@ -326,6 +334,27 @@ void spdk_bdev_close(struct spdk_bdev_desc *desc); */ struct spdk_bdev *spdk_bdev_desc_get_bdev(struct spdk_bdev_desc *desc); +/** + * Set a time limit for the timeout IO of the bdev and timeout callback. + * We can use this function to enable/disable the timeout handler. If + * the timeout_in_sec > 0 then it means to enable the timeout IO handling + * or change the time limit. If the timeout_in_sec == 0 it means to + * disable the timeout IO handling. If you want to enable or change the + * timeout IO handle you need to specify the spdk_bdev_io_timeout_cb it + * means the upper user determines what to do if you meet the timeout IO, + * for example, you can reset the device or abort the IO. + * Note: This function must run in the desc's thread. + * + * \param desc Block device descriptor. + * \param timeout_in_sec Timeout value + * \param cb_fn Bdev IO timeout callback + * \param cb_arg Callback argument + * + * \return 0 on success, negated errno on failure. + */ +int spdk_bdev_set_timeout(struct spdk_bdev_desc *desc, uint64_t timeout_in_sec, + spdk_bdev_io_timeout_cb cb_fn, void *cb_arg); + /** * Check whether the block device supports the I/O type. * diff --git a/lib/bdev/bdev.c b/lib/bdev/bdev.c index 4bdb57640..8dcc48cc3 100644 --- a/lib/bdev/bdev.c +++ b/lib/bdev/bdev.c @@ -81,6 +81,7 @@ int __itt_init_ittlib(const char *, __itt_group_id); #define SPDK_BDEV_QOS_MIN_IOS_PER_SEC 1000 #define SPDK_BDEV_QOS_MIN_BYTES_PER_SEC (1024 * 1024) #define SPDK_BDEV_QOS_LIMIT_NOT_DEFINED UINT64_MAX +#define SPDK_BDEV_IO_POLL_INTERVAL_IN_MSEC 1000 #define SPDK_BDEV_POOL_ALIGNMENT 512 @@ -293,6 +294,11 @@ struct spdk_bdev_desc { pthread_mutex_t mutex; uint32_t refs; TAILQ_ENTRY(spdk_bdev_desc) link; + + uint64_t timeout_in_sec; + spdk_bdev_io_timeout_cb cb_fn; + void *cb_arg; + struct spdk_poller *io_timeout_poller; }; struct spdk_bdev_iostat_ctx { @@ -2119,6 +2125,133 @@ bdev_enable_qos(struct spdk_bdev *bdev, struct spdk_bdev_channel *ch) } } +struct poll_timeout_ctx { + struct spdk_bdev_desc *desc; + uint64_t timeout_in_sec; + spdk_bdev_io_timeout_cb cb_fn; + void *cb_arg; +}; + +static void +bdev_desc_free(struct spdk_bdev_desc *desc) +{ + pthread_mutex_destroy(&desc->mutex); + free(desc); +} + +static void +bdev_channel_poll_timeout_io_done(struct spdk_io_channel_iter *i, int status) +{ + struct poll_timeout_ctx *ctx = spdk_io_channel_iter_get_ctx(i); + struct spdk_bdev_desc *desc = ctx->desc; + + free(ctx); + + pthread_mutex_lock(&desc->mutex); + desc->refs--; + if (desc->closed == true && desc->refs == 0) { + pthread_mutex_unlock(&desc->mutex); + bdev_desc_free(desc); + return; + } + pthread_mutex_unlock(&desc->mutex); +} + +static void +bdev_channel_poll_timeout_io(struct spdk_io_channel_iter *i) +{ + struct poll_timeout_ctx *ctx = spdk_io_channel_iter_get_ctx(i); + struct spdk_io_channel *io_ch = spdk_io_channel_iter_get_channel(i); + struct spdk_bdev_channel *bdev_ch = spdk_io_channel_get_ctx(io_ch); + struct spdk_bdev_desc *desc = ctx->desc; + struct spdk_bdev_io *bdev_io; + uint64_t now; + + pthread_mutex_lock(&desc->mutex); + if (desc->closed == true) { + pthread_mutex_unlock(&desc->mutex); + spdk_for_each_channel_continue(i, -1); + return; + } + pthread_mutex_unlock(&desc->mutex); + + now = spdk_get_ticks(); + TAILQ_FOREACH(bdev_io, &bdev_ch->io_submitted, internal.ch_link) { + /* I/O are added to this TAILQ as they are submitted. + * So once we find an I/O that has not timed out, we can immediately exit the loop. */ + if (now < (bdev_io->internal.submit_tsc + + ctx->timeout_in_sec * spdk_get_ticks_hz())) { + goto end; + } + + if (bdev_io->internal.desc == desc) { + ctx->cb_fn(ctx->cb_arg, bdev_io); + } + } + +end: + spdk_for_each_channel_continue(i, 0); +} + +static int +bdev_poll_timeout_io(void *arg) +{ + struct spdk_bdev_desc *desc = arg; + struct spdk_bdev *bdev = spdk_bdev_desc_get_bdev(desc); + struct poll_timeout_ctx *ctx; + + ctx = calloc(1, sizeof(struct poll_timeout_ctx)); + if (!ctx) { + SPDK_ERRLOG("failed to allocate memory\n"); + return 1; + } + ctx->desc = desc; + ctx->cb_arg = desc->cb_arg; + ctx->cb_fn = desc->cb_fn; + ctx->timeout_in_sec = desc->timeout_in_sec; + + /* Take a ref on the descriptor in case it gets closed while we are checking + * all of the channels. + */ + pthread_mutex_lock(&desc->mutex); + desc->refs++; + pthread_mutex_unlock(&desc->mutex); + + spdk_for_each_channel(__bdev_to_io_dev(bdev), + bdev_channel_poll_timeout_io, + ctx, + bdev_channel_poll_timeout_io_done); + + return 1; +} + +int +spdk_bdev_set_timeout(struct spdk_bdev_desc *desc, uint64_t timeout_in_sec, + spdk_bdev_io_timeout_cb cb_fn, void *cb_arg) +{ + assert(desc->thread == spdk_get_thread()); + + spdk_poller_unregister(&desc->io_timeout_poller); + + if (timeout_in_sec) { + assert(cb_fn != NULL); + desc->io_timeout_poller = spdk_poller_register(bdev_poll_timeout_io, + desc, + SPDK_BDEV_IO_POLL_INTERVAL_IN_MSEC * SPDK_SEC_TO_USEC / + 1000); + if (desc->io_timeout_poller == NULL) { + SPDK_ERRLOG("can not register the desc timeout IO poller\n"); + return -1; + } + } + + desc->cb_fn = cb_fn; + desc->cb_arg = cb_arg; + desc->timeout_in_sec = timeout_in_sec; + + return 0; +} + static int bdev_channel_create(void *io_device, void *ctx_buf) { @@ -2689,13 +2822,6 @@ spdk_bdev_set_qd_sampling_period(struct spdk_bdev *bdev, uint64_t period) } } -static void -bdev_desc_free(struct spdk_bdev_desc *desc) -{ - pthread_mutex_destroy(&desc->mutex); - free(desc); -} - static void _resize_notify(void *arg) { @@ -4559,6 +4685,8 @@ spdk_bdev_close(struct spdk_bdev_desc *desc) assert(desc->thread == spdk_get_thread()); + spdk_poller_unregister(&desc->io_timeout_poller); + pthread_mutex_lock(&bdev->internal.mutex); pthread_mutex_lock(&desc->mutex);