Make it possible for plugins to advertise caching support, and to
implement their own caching algorithm.
There are no universally common cache algorithms (calling .pread and
ignoring the buffer works great for local files, but actually
penalizes remote network access), so the code intentionally defaults
.can_cache to NONE if .cache is missing. However, as both .pread
(files) and a silent no-op (for plugins serving data that only resides
in memory already) are common implementations, we allow .can_cache to
return success even when .cache is missing (which is different from
many of the other callbacks, where .can_FOO should not return true
unless .FOO is present). On the other hand, if only .cache is present,
defaulting .can_cache to NATIVE makes sense. Thus, .can_cache is a
tri-state similar to .can_fua.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
docs/nbdkit-plugin.pod | 82 +++++++++++++++++++++++++++++++++++++++++
include/nbdkit-plugin.h | 2 +
server/plugins.c | 18 +++++++--
3 files changed, 98 insertions(+), 4 deletions(-)
diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod
index 318be4c..7f83234 100644
--- a/docs/nbdkit-plugin.pod
+++ b/docs/nbdkit-plugin.pod
@@ -222,6 +222,37 @@ landed in persistent storage.
=back
+The following defines are valid as successful return values for
+C<.can_cache>:
+
+=over 4
+
+=item C<NBDKIT_CACHE_NONE>
+
+The server does not advertise caching support, and rejects any
+client-requested caching. Any C<.cache> callback is ignored.
+
+=item C<NBDKIT_CACHE_EMULATE>
+
+The nbdkit server advertises cache support to the client, where the
+client may request that the server cache a region of the export to
+potentially speed up future read and/or write operations on that
+region. The nbdkit server implements the caching by calling C<.pread>
+and ignoring the results. This option exists to ease the
+implementation of a common form of caching; any C<.cache> callback is
+ignored.
+
+=item C<NBDKIT_CACHE_NATIVE>
+
+The nbdkit server advertises cache support to the client, where the
+client may request that the server cache a region of the export to
+potentially speed up future read and/or write operations on that
+region. The nbdkit server calls the C<.cache> callback to perform the
+caching; if that callback is missing, the client's cache request
+succeeds without doing anything.
+
+=back
+
=head1 ERROR HANDLING
If there is an error in the plugin, the plugin should call
@@ -620,6 +651,29 @@ with an error message and return C<-1>.
This callback is not required. If omitted, then we return false.
+=head2 C<.can_cache>
+
+ int can_cache (void *handle);
+
+This is called during the option negotiation phase to find out if the
+plugin supports a cache operation. The nature of the caching is
+unspecified (including whether there are limits on how much can be
+cached at once, and whether writes to a cached region have
+write-through or write-back semantics), but the command exists to let
+clients issue a hint to the server that they will be accessing that
+region of the export.
+
+If this returns C<NBDKIT_CACHE_NONE>, cache support is not advertised
+to the guest; if this returns C<NBDKIT_CACHE_EMULATE>, caching is
+emulated by the server calling C<.pread> and ignoring the results; if
+this returns C<NBDKIT_CACHE_NATIVE>, then the C<.cache> callback will
+be used. If there is an error, C<.can_cache> should call
+C<nbdkit_error> with an error message and return C<-1>.
+
+This callback is not required. If omitted, then we return
+C<NBDKIT_CACHE_NONE> if the C<.cache> callback is missing, or
+C<NBDKIT_CACHE_NATIVE> if it is defined.
+
=head2 C<.pread>
int pread (void *handle, void *buf, uint32_t count, uint64_t offset,
@@ -814,6 +868,34 @@ C<nbdkit_extent_add> returns C<0> on success or
C<-1> on failure. On
failure C<nbdkit_error> and/or C<nbdkit_set_error> has already been
called. C<errno> will be set to a suitable value.
+=head2 C<.cache>
+
+ int cache (void *handle, uint32_t count, uint64_t offset, uint32_t flags);
+
+During the data serving phase, this callback is used to give the
+plugin a hint that the client intends to make further accesses to the
+given region of the export. The nature of caching is not specified
+further by the NBD specification (for example, a server may place
+limits on how much may be cached at once, and there is no way to
+control if writes to a cached area have write-through or write-back
+semantics). In fact, the cache command can always fail and still be
+compliant, and success might not guarantee a performance gain. If
+this callback is omitted, then the results of C<.can_cache> determine
+whether nbdkit will reject cache requests, treat them as instant
+success, or emulate caching by calling C<.pread> over the same region
+and ignoring the results.
+
+This function will not be called if C<.can_cache> did not return
+C<NBDKIT_CACHE_NATIVE>. The parameter C<flags> exists in case of
+future NBD protocol extensions; at this time, it will be 0 on input. A
+plugin must fail this function if C<flags> includes an unrecognized
+flag, as that may indicate a requirement that the plugin comply must
+with a specific caching semantic.
+
+If there is an error, C<.cache> should call C<nbdkit_error> with an
+error message, and C<nbdkit_set_error> to record an appropriate error
+(unless C<errno> is sufficient), then return C<-1>.
+
=head1 THREADS
Each nbdkit plugin must declare its thread safety model by defining
diff --git a/include/nbdkit-plugin.h b/include/nbdkit-plugin.h
index 54b4ce2..e9b1808 100644
--- a/include/nbdkit-plugin.h
+++ b/include/nbdkit-plugin.h
@@ -128,6 +128,8 @@ struct nbdkit_plugin {
int (*can_extents) (void *handle);
int (*extents) (void *handle, uint32_t count, uint64_t offset, uint32_t flags,
struct nbdkit_extents *extents);
+ int (*can_cache) (void *handle);
+ int (*cache) (void *handle, uint32_t count, uint64_t offset, uint32_t flags);
};
extern void nbdkit_set_error (int err);
diff --git a/server/plugins.c b/server/plugins.c
index cb9a50c..f293e6a 100644
--- a/server/plugins.c
+++ b/server/plugins.c
@@ -201,6 +201,8 @@ plugin_dump_fields (struct backend *b)
HAS (can_multi_conn);
HAS (can_extents);
HAS (extents);
+ HAS (can_cache);
+ HAS (cache);
#undef HAS
/* Custom fields. */
@@ -451,11 +453,16 @@ plugin_can_multi_conn (struct backend *b, struct connection *conn)
static int
plugin_can_cache (struct backend *b, struct connection *conn)
{
+ struct backend_plugin *p = container_of (b, struct backend_plugin, backend);
+
assert (connection_get_handle (conn, 0));
debug ("can_cache");
- /* FIXME: return default based on plugin->cache */
+ if (p->plugin.can_cache)
+ return p->plugin.can_cache (connection_get_handle (conn, 0));
+ if (p->plugin.cache)
+ return NBDKIT_CACHE_NATIVE;
return NBDKIT_CACHE_NONE;
}
@@ -710,16 +717,19 @@ plugin_cache (struct backend *b, struct connection *conn,
int *err)
{
struct backend_plugin *p = container_of (b, struct backend_plugin, backend);
- int r = -1;
+ int r;
assert (connection_get_handle (conn, 0));
assert (!flags);
debug ("cache count=%" PRIu32 " offset=%" PRIu64, count, offset);
- /* FIXME: assert plugin->cache and call it */
- assert (false);
+ /* A plugin may advertise caching but not provide .cache; in that
+ * case, caching is explicitly a no-op. */
+ if (!p->plugin.cache)
+ return 0;
+ r = p->plugin.cache (connection_get_handle (conn, 0), count, offset, flags);
if (r == -1)
*err = get_error (p);
return r;
--
2.20.1