[lvm-devel] master - lvmcache: Use asynchronous I/O when scanning devices.

Alasdair Kergon agk at sourceware.org
Fri Feb 9 01:32:21 UTC 2018


Gitweb:        https://sourceware.org/git/?p=lvm2.git;a=commitdiff;h=db41fe6c5dab7ff66db9c0568f0e1e1b31657be3
Commit:        db41fe6c5dab7ff66db9c0568f0e1e1b31657be3
Parent:        8c7bbcfb0f6771a10acd238057fdb7931c3fbb12
Author:        Alasdair G Kergon <agk at redhat.com>
AuthorDate:    Mon Jan 22 18:17:58 2018 +0000
Committer:     Alasdair G Kergon <agk at redhat.com>
CommitterDate: Thu Feb 8 20:15:29 2018 +0000

lvmcache: Use asynchronous I/O when scanning devices.

---
 WHATS_NEW            |    1 +
 lib/cache/lvmcache.c |   11 +++
 lib/device/dev-io.c  |  165 ++++++++++++++++++++++++++++++++++++++++++++-----
 lib/device/device.h  |    4 +
 4 files changed, 164 insertions(+), 17 deletions(-)

diff --git a/WHATS_NEW b/WHATS_NEW
index 5aa4ed8..8ae79cf 100644
--- a/WHATS_NEW
+++ b/WHATS_NEW
@@ -1,5 +1,6 @@
 Version 2.02.178 - 
 =====================================
+  Support asynchronous I/O when scanning devices.
   Detect asynchronous I/O capability in configure or accept --disable-aio.
   Add AIO_SUPPORTED_CODE_PATH to indicate whether AIO may be used.
   Configure ensures /usr/bin dir is checked for dmpd tools.
diff --git a/lib/cache/lvmcache.c b/lib/cache/lvmcache.c
index 2eaf479..7678290 100644
--- a/lib/cache/lvmcache.c
+++ b/lib/cache/lvmcache.c
@@ -1103,6 +1103,11 @@ static void _process_label_data(int failed, unsigned ioflags, void *context, con
 {
 	int *nr_labels_outstanding = context;
 
+	if (!*nr_labels_outstanding) {
+		log_error(INTERNAL_ERROR "_process_label_data called too many times");
+		return;
+	}
+
 	(*nr_labels_outstanding)--;
 }
 
@@ -1163,6 +1168,12 @@ int lvmcache_label_scan(struct cmd_context *cmd)
 
 	dev_iter_destroy(iter);
 
+	while (nr_labels_outstanding) {
+		log_very_verbose("Scanned %d device labels (%d outstanding)", dev_count, nr_labels_outstanding);
+		if (!dev_async_getevents())
+			return_0;
+	}
+
 	log_very_verbose("Scanned %d device labels (%d outstanding)", dev_count, nr_labels_outstanding);
 
 	/*
diff --git a/lib/device/dev-io.c b/lib/device/dev-io.c
index 48f5b8d..f2bb128 100644
--- a/lib/device/dev-io.c
+++ b/lib/device/dev-io.c
@@ -103,8 +103,11 @@ void devbufs_release(struct device *dev)
 #  include <libaio.h>
 
 static io_context_t _aio_ctx = 0;
+static struct io_event *_aio_events = NULL;
 static int _aio_max = 128;
 
+#define DEFAULT_AIO_COLLECTION_EVENTS 32
+
 int dev_async_setup(struct cmd_context *cmd)
 {
 	int r;
@@ -115,6 +118,11 @@ int dev_async_setup(struct cmd_context *cmd)
 
 	log_debug_io("Setting up aio context for up to %d events.", _aio_max);
 
+	if (!_aio_events && !(_aio_events = dm_zalloc(sizeof(*_aio_events) * DEFAULT_AIO_COLLECTION_EVENTS))) {
+		log_error("Failed to allocate io_event array for asynchronous I/O.");
+		return 0;
+	}
+
 	if ((r = io_setup(_aio_max, &_aio_ctx)) < 0) {
 		/*
 		 * Possible errors:
@@ -126,6 +134,8 @@ int dev_async_setup(struct cmd_context *cmd)
 		 */
 		log_warn("WARNING: Asynchronous I/O setup for %d events failed: %s", _aio_max, strerror(-r));
 		log_warn("WARNING: Using only synchronous I/O.");
+		dm_free(_aio_events);
+		_aio_events = NULL;
 		_aio_ctx = 0;
 		return 0;
 	}
@@ -138,10 +148,116 @@ int dev_async_reset(struct cmd_context *cmd)
 {
 	log_debug_io("Resetting asynchronous I/O context.");
 	_aio_ctx = 0;
+	dm_free(_aio_events);
+	_aio_events = NULL;
 
 	return dev_async_setup(cmd);
 }
 
+static int _io(struct device_buffer *devbuf, unsigned ioflags);
+
+int dev_async_getevents(void)
+{
+	struct device_buffer *devbuf;
+	lvm_callback_fn_t dev_read_callback_fn;
+	void *dev_read_callback_context;
+	int r, event_nr;
+
+	if (!_aio_ctx)
+		return 1;
+
+	do {
+		/* FIXME Add timeout - currently NULL - waits for ever for at least 1 item */
+		r = io_getevents(_aio_ctx, 1, DEFAULT_AIO_COLLECTION_EVENTS, _aio_events, NULL);
+		if (r > 0)
+			break;
+		if (!r)
+			return 1; /* Timeout elapsed */
+		if (r == -EINTR)
+			continue;
+		if (r == -EAGAIN) {
+			usleep(100);
+			return 1; /* Give the caller the opportunity to do other work before repeating */
+		}
+		/*
+		 * ENOSYS - not supported by kernel
+		 * EFAULT - memory invalid
+		 * EINVAL - _aio_ctx invalid or min_nr/nr/timeout out of range
+		 */
+		log_error("Asynchronous event collection failed: %s", strerror(-r));
+		return 0;
+	} while (1);
+
+	for (event_nr = 0; event_nr < r; event_nr++) {
+		devbuf = _aio_events[event_nr].obj->data;
+		dm_free(_aio_events[event_nr].obj);
+
+		dev_read_callback_fn = devbuf->dev_read_callback_fn;
+		dev_read_callback_context = devbuf->dev_read_callback_context;
+
+		/* Clear the callbacks as a precaution */
+		devbuf->dev_read_callback_context = NULL;
+		devbuf->dev_read_callback_fn = NULL;
+
+		if (_aio_events[event_nr].res == devbuf->where.size) {
+			if (dev_read_callback_fn)
+				dev_read_callback_fn(0, AIO_SUPPORTED_CODE_PATH, dev_read_callback_context, (char *)devbuf->buf + devbuf->data_offset);
+		} else {
+			/* FIXME If partial read is possible, resubmit remainder */
+			log_error_once("%s: Asynchronous I/O failed: read only %" PRIu64 " of %" PRIu64 " bytes at %" PRIu64,
+				       dev_name(devbuf->where.dev),
+				       (uint64_t) _aio_events[event_nr].res, (uint64_t) devbuf->where.size,
+				       (uint64_t) devbuf->where.start);
+			_release_devbuf(devbuf);
+			if (dev_read_callback_fn)
+				dev_read_callback_fn(1, AIO_SUPPORTED_CODE_PATH, dev_read_callback_context, NULL);
+			r = 0;
+		}
+	}
+
+	return 1;
+}
+
+static int _io_async(struct device_buffer *devbuf)
+{
+	struct device_area *where = &devbuf->where;
+	struct iocb *iocb;
+	int r;
+
+	if (!(iocb = dm_malloc(sizeof(*iocb)))) {
+		log_error("Failed to allocate I/O control block array for asynchronous I/O.");
+		return 0;
+	}
+
+	io_prep_pread(iocb, dev_fd(where->dev), devbuf->buf, where->size, where->start);
+	iocb->data = devbuf;
+
+	do {
+		r = io_submit(_aio_ctx, 1L, &iocb);
+		if (r ==1)
+			break;	/* Success */
+		if (r == -EAGAIN) {
+			/* Try to release some resources then retry */
+			usleep(100);
+			if (dev_async_getevents())
+				return_0;
+			/* FIXME Add counter/timeout so we can't get stuck here for ever */
+			continue;
+		}
+		/*
+		 * Possible errors:
+		 *   EFAULT - invalid data
+		 *   ENOSYS - no aio support in kernel
+		 *   EBADF  - bad file descriptor in iocb
+		 *   EINVAL - invalid _aio_ctx / iocb not initialised / invalid operation for this fd
+		 */
+		log_error("Asynchronous event submission failed: %s", strerror(-r));
+		return 0;
+	} while (1);
+
+	return 1;
+}
+
 void dev_async_exit(void)
 {
 	int r;
@@ -154,6 +270,9 @@ void dev_async_exit(void)
 		/* Returns -ENOSYS if aio not in kernel or -EINVAL if _aio_ctx invalid */
 		log_error("Failed to destroy asynchronous I/O context: %s", strerror(-r));
 
+	dm_free(_aio_events);
+	_aio_events = NULL;
+
 	_aio_ctx = 0;
 }
 
@@ -171,10 +290,20 @@ int dev_async_reset(struct cmd_context *cmd)
 	return 1;
 }
 
+int dev_async_getevents(void)
+{
+	return 1;
+}
+
 void dev_async_exit(void)
 {
 }
 
+static int _io_async(struct device_buffer *devbuf)
+{
+	return 0;
+}
+
 #endif /* AIO_SUPPORT */
 
 /*-----------------------------------------------------------------
@@ -225,6 +354,7 @@ static int _io(struct device_buffer *devbuf, unsigned ioflags)
 {
 	struct device_area *where = &devbuf->where;
 	int fd = dev_fd(where->dev);
+	int async = (!devbuf->write && _aio_ctx && aio_supported_code_path(ioflags) && devbuf->dev_read_callback_fn) ? 1 : 0;
 
 	if (fd < 0) {
 		log_error("Attempt to read an unopened device (%s).",
@@ -237,9 +367,9 @@ static int _io(struct device_buffer *devbuf, unsigned ioflags)
 		return 0;
 	}
 
-	log_debug_io("%s %s(fd %d):%8" PRIu64 " bytes (sync) at %" PRIu64 "%s (for %s)",
+	log_debug_io("%s %s(fd %d):%8" PRIu64 " bytes (%ssync) at %" PRIu64 "%s (for %s)",
 		     devbuf->write ? "Write" : "Read ", dev_name(where->dev), fd,
-		     where->size, (uint64_t) where->start,
+		     where->size, async ? "a" : "", (uint64_t) where->start,
 		     (devbuf->write && test_mode()) ? " (test mode - suppressed)" : "", _reason_text(devbuf->reason));
 
 	/*
@@ -253,7 +383,7 @@ static int _io(struct device_buffer *devbuf, unsigned ioflags)
 		return 0;
 	}
 
-	return _io_sync(devbuf);
+	return async ? _io_async(devbuf) : _io_sync(devbuf);
 }
 
 /*-----------------------------------------------------------------
@@ -347,7 +477,7 @@ static void _widen_region(unsigned int block_size, struct device_area *region,
 
 static int _aligned_io(struct device_area *where, char *write_buffer,
 		       int should_write, dev_io_reason_t reason,
-		       unsigned ioflags)
+		       unsigned ioflags, lvm_callback_fn_t dev_read_callback_fn, void *dev_read_callback_context)
 {
 	unsigned int physical_block_size = 0;
 	unsigned int block_size = 0;
@@ -386,6 +516,8 @@ static int _aligned_io(struct device_area *where, char *write_buffer,
 	devbuf->where.size = widened.size;
 	devbuf->write = should_write;
 	devbuf->reason = reason;
+	devbuf->dev_read_callback_fn = dev_read_callback_fn;
+	devbuf->dev_read_callback_context = dev_read_callback_context;
 
 	/* Store location of requested data relative to start of buf */
 	devbuf->data_offset = where->start - devbuf->where.start;
@@ -882,15 +1014,11 @@ int dev_read_callback(struct device *dev, uint64_t offset, size_t len, dev_io_re
 
 	if (!dev->open_count) {
 		log_error(INTERNAL_ERROR "Attempt to access device %s while closed.", dev_name(dev));
-		ret = 0;
-		goto out;
+		return 0;
 	}
 
-	if (!_dev_is_valid(dev)) {
-		log_error("Not reading from %s - too many errors.", dev_name(dev));
-		ret = 0;
-		goto out;
-	}
+	if (!_dev_is_valid(dev))
+		return 0;
 
 	/*
 	 * Can we satisfy this from data we stored last time we read?
@@ -911,14 +1039,15 @@ int dev_read_callback(struct device *dev, uint64_t offset, size_t len, dev_io_re
 	where.start = offset;
 	where.size = len;
 
-	ret = _aligned_io(&where, NULL, 0, reason, ioflags);
+	ret = _aligned_io(&where, NULL, 0, reason, ioflags, dev_read_callback_fn, callback_context);
 	if (!ret) {
-		log_error("Read from %s failed.", dev_name(dev));
+		log_error("Read from %s failed", dev_name(dev));
 		_dev_inc_error_count(dev);
 	}
 
 out:
-	if (dev_read_callback_fn)
+	/* If we had an error or this was sync I/O, pass the result to any callback fn */
+	if ((!ret || !_aio_ctx || !aio_supported_code_path(ioflags) || cached) && dev_read_callback_fn)
 		dev_read_callback_fn(!ret, ioflags, callback_context, DEV_DEVBUF_DATA(dev, reason));
 
 	return ret;
@@ -936,8 +1065,10 @@ const char *dev_read(struct device *dev, uint64_t offset, size_t len, dev_io_rea
 /* Read into supplied retbuf owned by the caller. */
 int dev_read_buf(struct device *dev, uint64_t offset, size_t len, dev_io_reason_t reason, void *retbuf)
 {
-	if (!dev_read_callback(dev, offset, len, reason, 0, NULL, NULL))
-		return_0;
+	if (!dev_read_callback(dev, offset, len, reason, 0, NULL, NULL)) {
+		log_error("Read from %s failed", dev_name(dev));
+		return 0;
+	}
 	
 	memcpy(retbuf, DEV_DEVBUF_DATA(dev, reason), len);
 
@@ -1016,7 +1147,7 @@ int dev_write(struct device *dev, uint64_t offset, size_t len, dev_io_reason_t r
 
 	dev->flags |= DEV_ACCESSED_W;
 
-	ret = _aligned_io(&where, buffer, 1, reason, 0);
+	ret = _aligned_io(&where, buffer, 1, reason, 0, NULL, NULL);
 	if (!ret)
 		_dev_inc_error_count(dev);
 
diff --git a/lib/device/device.h b/lib/device/device.h
index 37d2fd2..b52522d 100644
--- a/lib/device/device.h
+++ b/lib/device/device.h
@@ -97,6 +97,9 @@ struct device_buffer {
 	struct device_area where;	/* Location of buf */
 	dev_io_reason_t reason;
 	unsigned write:1;	/* 1 if write; 0 if read */
+
+	lvm_callback_fn_t dev_read_callback_fn;
+	void *dev_read_callback_context;
 };
 
 /*
@@ -203,6 +206,7 @@ void devbufs_release(struct device *dev);
 const char *dev_name_confirmed(struct device *dev, int quiet);
 
 struct cmd_context;
+int dev_async_getevents(void);
 int dev_async_setup(struct cmd_context *cmd);
 void dev_async_exit(void);
 int dev_async_reset(struct cmd_context *cmd);




More information about the lvm-devel mailing list