Commit 19d803e8 authored by Jin Yu's avatar Jin Yu Committed by Tomasz Zawadzki
Browse files

bdev: add bdev timeout support



Add an API so that the user can enable/disable the bdev IO
timeout. Also, add the bdev io timeout handling callback.
So it means to let the upper user determine how to handle
the IO timeout scenario reset the device or abort the IO.

Change-Id: I9c7138ca46c74c045b687adab59a18d6bccc4996
Signed-off-by: default avatarJin Yu <jin.yu@intel.com>
Reviewed-on: https://review.gerrithub.io/c/spdk/spdk/+/469228


Reviewed-by: default avatarJim Harris <james.r.harris@intel.com>
Reviewed-by: default avatarShuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com>
Reviewed-by: default avatarChangpeng Liu <changpeng.liu@intel.com>
Reviewed-by: default avatarAlexey Marchuk <alexeymar@mellanox.com>
Community-CI: Broadcom SPDK FC-NVMe CI <spdk-ci.pdl@broadcom.com>
Community-CI: SPDK CI Jenkins <sys_sgci@intel.com>
Tested-by: default avatarSPDK CI Jenkins <sys_sgci@intel.com>
parent 8458c85c
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
@@ -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.
 *
+135 −7
Original line number Diff line number Diff line
@@ -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);