summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlasdair G Kergon <agk@redhat.com>2018-01-22 18:17:58 +0000
committerAlasdair G Kergon <agk@redhat.com>2018-02-08 20:15:29 +0000
commitdb41fe6c5dab7ff66db9c0568f0e1e1b31657be3 (patch)
tree5aec60950be5743db0ab44aafc566e8adc278fca
parent8c7bbcfb0f6771a10acd238057fdb7931c3fbb12 (diff)
downloadlvm2-db41fe6c5dab7ff66db9c0568f0e1e1b31657be3.tar.gz
lvmcache: Use asynchronous I/O when scanning devices.
-rw-r--r--WHATS_NEW1
-rw-r--r--lib/cache/lvmcache.c11
-rw-r--r--lib/device/dev-io.c165
-rw-r--r--lib/device/device.h4
4 files changed, 164 insertions, 17 deletions
diff --git a/WHATS_NEW b/WHATS_NEW
index 5aa4ed8cb..8ae79cf94 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 2eaf479d2..767829083 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 48f5b8d3e..f2bb128b8 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 37d2fd202..b52522dc4 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);