Add the can_fast_zero callback, and yet another bool keyword parameter
to the zero callback.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
plugins/python/nbdkit-python-plugin.pod | 35 ++++++++++++++-----
plugins/python/python.c | 45 ++++++++++++++++++++++++-
plugins/python/example.py | 2 +-
3 files changed, 71 insertions(+), 11 deletions(-)
diff --git a/plugins/python/nbdkit-python-plugin.pod
b/plugins/python/nbdkit-python-plugin.pod
index 6940f4d9..7b0051af 100644
--- a/plugins/python/nbdkit-python-plugin.pod
+++ b/plugins/python/nbdkit-python-plugin.pod
@@ -215,6 +215,18 @@ contents will be garbage collected.
def can_zero(h):
# return a boolean
+
+=item C<can_fast_zero>
+
+(Optional)
+
+ def can_fast_zero(h):
+ # return a boolean
+
+Unlike the C counterpart, this can often be omitted: the Python
+callback uses Python introspection to default to true if the C<zero>
+callback supports an optional parameter C<fast>.
+
=item C<can_fua>
(Optional)
@@ -313,7 +325,7 @@ request for FUA by calling C<flush>.
(Optional)
- def zero(h, count, offset, may_trim=False, fua=False):
+ def zero(h, count, offset, may_trim=False, fua=False, fast=False):
# no return value
The body of your C<zero> function should ensure that C<count> bytes
@@ -331,13 +343,19 @@ return success until the write has landed in persistent storage.
If
it is absent but C<can_fua> returned True, then nbdkit emulates a
client request for FUA by calling C<flush>.
-NBD only supports whole writes, so your function should try to
-write the whole region (perhaps requiring a loop). If the write
-fails or is partial, your function should throw an exception,
-optionally using C<nbdkit.set_error> first. In particular, if
-you would like to automatically fall back to C<pwrite> (perhaps
-because there is nothing to optimize if C<may_trim> is false),
-use C<nbdkit.set_error(errno.EOPNOTSUPP)>.
+Your function may support an optional C<fast> parameter; if it is
+present and the caller sets it to True, then your callback must return
+a failure if it is not any faster than a corresponding C<pwrite>. If
+it is absent but C<can_fast_zero> returned True, then nbdkit treats
+all fast zero requests as failures.
+
+NBD only supports whole writes, so your function should try to write
+the whole region (perhaps requiring a loop). If the write fails or is
+partial, your function should throw an exception, optionally using
+C<nbdkit.set_error> first. In particular, if you would like to
+automatically fall back to C<pwrite> (perhaps because there is nothing
+to optimize if C<may_trim> is false) or declare that fast zero is not
+possible, use C<nbdkit.set_error(errno.EOPNOTSUPP)>.
=back
@@ -362,7 +380,6 @@ C<description>,
C<config_help>,
C<magic_config_key>,
C<can_cache>,
-C<can_fast_zero>,
C<can_extents>,
C<can_multi_conn>,
C<cache>,
diff --git a/plugins/python/python.c b/plugins/python/python.c
index 677420bd..fee339f4 100644
--- a/plugins/python/python.c
+++ b/plugins/python/python.c
@@ -699,11 +699,14 @@ py_zero (void *handle, uint32_t count, uint64_t offset, uint32_t
flags)
PyObject *r;
int may_trim = (flags & NBDKIT_FLAG_MAY_TRIM) != 0;
int fua = (flags & NBDKIT_FLAG_FUA) != 0;
+ int fast = (flags & NBDKIT_FLAG_FAST_ZERO) != 0;
int need_flush = fua && !zero_has_fua;
- assert (!(flags & ~(NBDKIT_FLAG_MAY_TRIM | NBDKIT_FLAG_FUA)));
+ assert (!(flags & ~(NBDKIT_FLAG_MAY_TRIM | NBDKIT_FLAG_FUA |
+ NBDKIT_FLAG_FAST_ZERO)));
if (callback_defined ("zero", &fn)) {
static int zero_may_trim = -1;
+ static int zero_fast = -1;
if (zero_may_trim < 0)
zero_may_trim = callback_has_parameter (fn, "may_trim");
@@ -711,6 +714,18 @@ py_zero (void *handle, uint32_t count, uint64_t offset, uint32_t
flags)
check_python_failure ("zero");
return -1;
}
+ if (zero_fast < 0)
+ zero_fast = callback_has_parameter (fn, "fast");
+ if (zero_fast < 0) {
+ check_python_failure ("zero");
+ return -1;
+ }
+
+ if (fast && zero_fast != 1) {
+ nbdkit_debug ("zero lacks fast support, failing fast request");
+ nbdkit_set_error (EOPNOTSUPP);
+ return -1;
+ }
PyErr_Clear ();
@@ -746,6 +761,15 @@ py_zero (void *handle, uint32_t count, uint64_t offset, uint32_t
flags)
Py_DECREF (fn);
return -1;
}
+ if (zero_fast &&
+ PyDict_SetItemString (kwargs, "fast",
+ fast ? Py_True : Py_False) == -1) {
+ check_python_failure ("zero");
+ Py_DECREF (kwargs);
+ Py_DECREF (args);
+ Py_DECREF (fn);
+ return -1;
+ }
r = PyObject_Call (fn, args, kwargs);
Py_DECREF (fn);
Py_DECREF (args);
@@ -831,6 +855,24 @@ py_can_zero (void *handle)
return boolean_callback (handle, "can_zero", "zero", 0);
}
+static int
+py_can_fast_zero (void *handle)
+{
+ int r = boolean_callback (handle, "can_fast_zero", NULL, 2);
+ PyObject *fn;
+
+ if (r == 2) {
+ /* can_fast_zero was missing, but we still want to default to true
+ * if the zero callback is missing or supports optional fast argument.
+ */
+ if (callback_defined ("zero", &fn))
+ r = callback_has_parameter (fn, "fast");
+ else
+ r = 1;
+ }
+ return r;
+}
+
static int
py_can_fua (void *handle)
{
@@ -876,6 +918,7 @@ static struct nbdkit_plugin plugin = {
.can_flush = py_can_flush,
.can_trim = py_can_trim,
.can_zero = py_can_zero,
+ .can_fast_zero = py_can_fast_zero,
.can_fua = py_can_fua,
.pread = py_pread,
diff --git a/plugins/python/example.py b/plugins/python/example.py
index ca7c2661..b1a9fed1 100644
--- a/plugins/python/example.py
+++ b/plugins/python/example.py
@@ -67,7 +67,7 @@ def pwrite(h, buf, offset, fua=False):
disk[offset:end] = buf
-def zero(h, count, offset, may_trim=False, fua=False):
+def zero(h, count, offset, may_trim=False, fua=False, fast=False):
global disk
if may_trim:
disk[offset:offset+count] = bytearray(count)
--
2.21.0