<div dir="ltr"><div>I went ahead and implemented 2 new hooks on the existing nbdkit_plugin struct for async_pread and async_pwrite to get some testable numbers.<br><br></div><div>The test is set up with an all in memory block device (data being copied to and from a std vector as simulated reading and writing).<br>Every read and write op has a 64ms wait before completing (simulated device latency).<br></div><div>These tests are using nbd-client local to the nbd-server with a unix domain socket for communication<br></div><div>The device has an ext4 filesystem on it mkfs.ext4 -b 4096 /dev/nbd0<br></div><div><br></div><div><b>Baseline test with unmodified nbdkit</b><br></div><div></div><br><div style="margin-left:40px">dd if=/dev/zero of=./mnt/zeros bs=128k count=80 conv=fsync<br>80+0 records in<br>80+0 records out<br>10485760 bytes (10 MB, 10 MiB) copied, 5.40797 s, 1.9 MB/s<br><br>echo 1 | sudo tee /proc/sys/vm/drop_caches<br><br>time cat mnt/zeros > /dev/null<br>real    0m5.386s<br>user    0m0.004s<br>sys    0m0.000s<br></div><div></div><br><div>Read and Write performance are both around 2MB/s which is exactly a single 128k read or write every 64ms.<br><br></div><div><b>With nbdkit modified to use async_pread and async_pwrite and a buffer pool of 64 buffers (each 128k for a total of 8MB buffer memory)</b><br><br></div><div style="margin-left:40px">dd if=/dev/zero of=./mnt/zeros bs=128k count=8000 conv=fsync<br>8000+0 records in<br>8000+0 records out<br>1048576000 bytes (1.0 GB, 1000 MiB) copied, 8.7736 s, 120 MB/s<br><br>echo 1 | sudo tee /proc/sys/vm/drop_caches<br><br>time cat mnt/zeros > /dev/null<br>real    0m8.153s<br>user    0m0.000s<br>sys    0m0.320s<br></div><div><br></div>Read and Write performance are now 120MB/s (about 64x faster) because we can process 64 ops in parallel. Our throughput scaled nearly linearly without needing 64 threads in the nbdkit. Total memory for the buffers is 8MB.<br><br><b>With async_pread/async_pwrite and a buffer pool of 1024 buffers (128MB buffer memory) and 2 io service threads (3 threads total, 1 thread in nbdkit pulling requests off the socket)</b><br><br><div style="margin-left:40px">dd if=/dev/zero of=./mnt/zeros bs=128k count=80000 conv=fsync<br>80000+0 records in<br>80000+0 records out<br>10485760000 bytes (10 GB, 9.8 GiB) copied, 5.86029 s, 1.8 GB/s<br></div><div style="margin-left:40px"><br>echo 1 | sudo tee /proc/sys/vm/drop_caches <br><br>time cat mnt/zeros > /dev/null<br>real    0m12.545s<br>user    0m0.012s<br>sys    0m2.444s<br></div><div><div><br></div><div></div><div>Read performance was capped at around 825 MiB/s for 1 file sequential read but when performing 2 files in parallel the read throughput was 1.6GB/s and for 4 files in parallel 1.9GB/s.<br><br></div><div>Below is the patch for changes made to nbdkit to support this.<br></div><br>diff --git a/include/nbdkit-plugin.h b/include/nbdkit-plugin.h<br>index 95cba8d..2e88cad 100644<br>--- a/include/nbdkit-plugin.h<br>+++ b/include/nbdkit-plugin.h<br>@@ -38,15 +38,13 @@<br> <br> #include <stdarg.h><br> #include <stdint.h><br>-<br>-#ifdef __cplusplus<br>-extern "C" {<br>-#endif<br>+#include <stdbool.h><br> <br> #define NBDKIT_THREAD_MODEL_SERIALIZE_CONNECTIONS     0<br> #define NBDKIT_THREAD_MODEL_SERIALIZE_ALL_REQUESTS    1<br> #define NBDKIT_THREAD_MODEL_SERIALIZE_REQUESTS        2<br> #define NBDKIT_THREAD_MODEL_PARALLEL                  3<br>+#define NBDKIT_THREAD_MODEL_ASYNC                     4<br> <br> #define NBDKIT_API_VERSION                            1<br> <br>@@ -94,28 +92,35 @@ struct nbdkit_plugin {<br> <br>   int errno_is_preserved;<br> <br>+  int (*async_pread) (void *conn, uint64_t reqid, bool flush, void *handle, void *buf, uint32_t count, uint64_t offset);<br>+  int (*async_pwrite) (void *conn, uint64_t reqid, bool flush, void *handle, const void *buf, uint32_t count, uint64_t offset);<br>   /* int (*set_exportname) (void *handle, const char *exportname); */<br> };<br> <br>-extern void nbdkit_set_error (int err);<br>-extern void nbdkit_error (const char *msg, ...)<br>-  __attribute__((format (printf, 1, 2)));<br>-extern void nbdkit_verror (const char *msg, va_list args);<br>-extern void nbdkit_debug (const char *msg, ...)<br>-  __attribute__((format (printf, 1, 2)));<br>-extern void nbdkit_vdebug (const char *msg, va_list args);<br>-<br>-extern char *nbdkit_absolute_path (const char *path);<br>-extern int64_t nbdkit_parse_size (const char *str);<br>-<br> #ifdef __cplusplus<br>-#define NBDKIT_CXX_LANG_C extern "C"<br>+#define NBDKIT_CXX_LANG_C "C"<br> #else<br> #define NBDKIT_CXX_LANG_C /* nothing */<br> #endif<br> <br>+extern NBDKIT_CXX_LANG_C void nbdkit_set_error (int err);<br>+extern NBDKIT_CXX_LANG_C void nbdkit_error (const char *msg, ...)<br>+  __attribute__((format (printf, 1, 2)));<br>+extern NBDKIT_CXX_LANG_C void nbdkit_verror (const char *msg, va_list args);<br>+extern NBDKIT_CXX_LANG_C void nbdkit_debug (const char *msg, ...)<br>+  __attribute__((format (printf, 1, 2)));<br>+extern NBDKIT_CXX_LANG_C void nbdkit_vdebug (const char *msg, va_list args);<br>+<br>+extern NBDKIT_CXX_LANG_C char *nbdkit_absolute_path (const char *path);<br>+extern NBDKIT_CXX_LANG_C int64_t nbdkit_parse_size (const char *str);<br>+<br>+extern NBDKIT_CXX_LANG_C int nbdkit_async_reply (void *conn, uint64_t reqid);<br>+extern NBDKIT_CXX_LANG_C int nbdkit_async_reply_read (void *conn, uint64_t reqid, uint32_t count, void *buf);<br>+extern NBDKIT_CXX_LANG_C int nbdkit_async_reply_error (void *conn, uint64_t reqid);<br>+<br>+<br> #define NBDKIT_REGISTER_PLUGIN(plugin)                                  \<br>-  NBDKIT_CXX_LANG_C                                                     \<br>+  extern NBDKIT_CXX_LANG_C                                              \<br>   struct nbdkit_plugin *                                                \<br>   plugin_init (void)                                                    \<br>   {                                                                     \<br>@@ -125,8 +130,4 @@ extern int64_t nbdkit_parse_size (const char *str);<br>     return &(plugin);                                                   \<br>   }<br> <br>-#ifdef __cplusplus<br>-}<br>-#endif<br>-<br> #endif /* NBDKIT_PLUGIN_H */<br>diff --git a/src/connections.c b/src/connections.c<br>index a0d689a..e03a0f6 100644<br>--- a/src/connections.c<br>+++ b/src/connections.c<br>@@ -62,7 +62,8 @@<br> static struct connection *new_connection (int sockin, int sockout);<br> static void free_connection (struct connection *conn);<br> static int negotiate_handshake (struct connection *conn);<br>-static int recv_request_send_reply (struct connection *conn);<br>+static int recv_request (struct connection *conn);<br>+static int send_reply (struct connection *conn, uint64_t handle, uint32_t count, void *buf, uint32_t error);<br> <br> static int<br> _handle_single_connection (int sockin, int sockout)<br>@@ -86,7 +87,7 @@ _handle_single_connection (int sockin, int sockout)<br>    * a thread pool.<br>    */<br>   while (!quit) {<br>-    r = recv_request_send_reply (conn);<br>+    r = recv_request (conn);<br>     if (r == -1)<br>       goto err;<br>     if (r == 0)<br>@@ -127,6 +128,7 @@ new_connection (int sockin, int sockout)<br>   conn->sockin = sockin;<br>   conn->sockout = sockout;<br>   pthread_mutex_init (&conn->request_lock, NULL);<br>+  pthread_mutex_init (&conn->reply_lock, NULL);<br> <br>   return conn;<br> }<br>@@ -143,6 +145,7 @@ free_connection (struct connection *conn)<br>     close (conn->sockout);<br> <br>   pthread_mutex_destroy (&conn->request_lock);<br>+  pthread_mutex_destroy (&conn->reply_lock);<br> <br>   if (conn->handle)<br>     plugin_close (conn);<br>@@ -626,13 +629,13 @@ get_error (struct connection *conn)<br>  * On read/write errors, sets *error appropriately and returns 0.<br>  */<br> static int<br>-_handle_request (struct connection *conn,<br>+_handle_request (struct connection *conn, uint64_t handle,<br>                  uint32_t cmd, uint32_t flags, uint64_t offset, uint32_t count,<br>-                 void *buf,<br>-                 uint32_t *error)<br>+                 void *buf)<br> {<br>-  bool flush_after_command;<br>   int r;<br>+  uint32_t error = 0;<br>+  bool flush_after_command;<br> <br>   /* Flush after command performed? */<br>   flush_after_command = (flags & NBD_CMD_FLAG_FUA) != 0;<br>@@ -645,42 +648,54 @@ _handle_request (struct connection *conn,<br> <br>   switch (cmd) {<br>   case NBD_CMD_READ:<br>-    r = plugin_pread (conn, buf, count, offset);<br>+    if (plugin_can_async_read (conn)) {<br>+      r = plugin_async_pread (conn, handle, flush_after_command, buf, count, offset);<br>+      if (r == 0)<br>+        return 0; // plugin now has responsibility of sending response<br>+    }<br>+    else<br>+      r = plugin_pread (conn, buf, count, offset);<br>     if (r == -1) {<br>-      *error = get_error (conn);<br>-      return 0;<br>+      error = get_error (conn);<br>+      return send_reply (conn, handle, 0, NULL, error);<br>     }<br>     break;<br> <br>   case NBD_CMD_WRITE:<br>-    r = plugin_pwrite (conn, buf, count, offset);<br>+    if (plugin_can_async_write (conn)) {<br>+      r = plugin_async_pwrite (conn, handle, flush_after_command, buf, count, offset);<br>+      if (r == 0)<br>+        return 0; // plugin now has responsibility of sending response<br>+    }<br>+    else<br>+      r = plugin_pwrite (conn, buf, count, offset);<br>     if (r == -1) {<br>-      *error = get_error (conn);<br>-      return 0;<br>+      error = get_error (conn);<br>+      return send_reply (conn, handle, 0, NULL, error);<br>     }<br>     break;<br> <br>   case NBD_CMD_FLUSH:<br>     r = plugin_flush (conn);<br>     if (r == -1) {<br>-      *error = get_error (conn);<br>-      return 0;<br>+      error = get_error (conn);<br>+      return send_reply (conn, handle, 0, NULL, error);<br>     }<br>     break;<br> <br>   case NBD_CMD_TRIM:<br>     r = plugin_trim (conn, count, offset);<br>     if (r == -1) {<br>-      *error = get_error (conn);<br>-      return 0;<br>+      error = get_error (conn);<br>+      return send_reply (conn, handle, 0, NULL, error);<br>     }<br>     break;<br> <br>   case NBD_CMD_WRITE_ZEROES:<br>     r = plugin_zero (conn, count, offset, !(flags & NBD_CMD_FLAG_NO_HOLE));<br>     if (r == -1) {<br>-      *error = get_error (conn);<br>-      return 0;<br>+      error = get_error (conn);<br>+      return send_reply (conn, handle, 0, NULL, error);<br>     }<br>     break;<br> <br>@@ -691,24 +706,28 @@ _handle_request (struct connection *conn,<br>   if (flush_after_command) {<br>     r = plugin_flush (conn);<br>     if (r == -1) {<br>-      *error = get_error (conn);<br>-      return 0;<br>+      error = get_error (conn);<br>+      return send_reply (conn, handle, 0, NULL, error);<br>     }<br>   }<br> <br>-  return 0;<br>+  if (cmd == NBD_CMD_READ)<br>+    r = send_reply (conn, handle, count, buf, error);<br>+  else<br>+   r = send_reply (conn, handle, 0, NULL, error);<br>+<br>+  return r;<br> }<br> <br> static int<br>-handle_request (struct connection *conn,<br>+handle_request (struct connection *conn, uint64_t handle,<br>                 uint32_t cmd, uint32_t flags, uint64_t offset, uint32_t count,<br>-                void *buf,<br>-                uint32_t *error)<br>+                void *buf)<br> {<br>   int r;<br> <br>   plugin_lock_request (conn);<br>-  r = _handle_request (conn, cmd, flags, offset, count, buf, error);<br>+  r = _handle_request (conn, handle, cmd, flags, offset, count, buf);<br>   plugin_unlock_request (conn);<br> <br>   return r;<br>@@ -763,11 +782,10 @@ nbd_errno (int error)<br> }<br> <br> static int<br>-recv_request_send_reply (struct connection *conn)<br>+recv_request (struct connection *conn)<br> {<br>   int r;<br>   struct request request;<br>-  struct reply reply;<br>   uint32_t magic, cmd, flags, count, error = 0;<br>   uint64_t offset;<br>   CLEANUP_FREE char *buf = NULL;<br>@@ -808,7 +826,10 @@ recv_request_send_reply (struct connection *conn)<br>   if (r == 0) {                 /* request not valid */<br>     if (cmd == NBD_CMD_WRITE)<br>       skip_over_write_buffer (conn->sockin, count);<br>-    goto send_reply;<br>+    r = send_reply (conn, request.handle, 0, NULL, error);<br>+    if (r == -1)<br>+      return -1;<br>+    return 1;<br>   }<br> <br>   /* Allocate the data buffer used for either read or write requests. */<br>@@ -819,7 +840,9 @@ recv_request_send_reply (struct connection *conn)<br>       error = ENOMEM;<br>       if (cmd == NBD_CMD_WRITE)<br>         skip_over_write_buffer (conn->sockin, count);<br>-      goto send_reply;<br>+      r = send_reply (conn, request.handle, 0, NULL, error);<br>+      if (r == -1)<br>+        return -1;<br>     }<br>   }<br> <br>@@ -837,14 +860,20 @@ recv_request_send_reply (struct connection *conn)<br>   }<br> <br>   /* Perform the request.  Only this part happens inside the request lock. */<br>-  r = handle_request (conn, cmd, flags, offset, count, buf, &error);<br>+  r = handle_request (conn, request.handle, cmd, flags, offset, count, buf);<br>   if (r == -1)<br>     return -1;<br> <br>-  /* Send the reply packet. */<br>- send_reply:<br>+  return 1;                     /* command processed ok */<br>+}<br>+<br>+static int<br>+_send_reply (struct connection *conn, uint64_t handle, uint32_t count, void *buf, uint32_t error)<br>+{<br>+  int r;<br>+  struct reply reply;<br>   reply.magic = htobe32 (NBD_REPLY_MAGIC);<br>-  reply.handle = request.handle;<br>+  reply.handle = handle;<br>   reply.error = htobe32 (nbd_errno (error));<br> <br>   if (error != 0) {<br>@@ -862,8 +891,7 @@ recv_request_send_reply (struct connection *conn)<br>     return -1;<br>   }<br> <br>-  /* Send the read data buffer. */<br>-  if (cmd == NBD_CMD_READ) {<br>+  if (error == 0 && buf != NULL) { /* Send the read data buffer. */<br>     r = xwrite (conn->sockout, buf, count);<br>     if (r == -1) {<br>       nbdkit_error ("write data: %m");<br>@@ -871,5 +899,37 @@ recv_request_send_reply (struct connection *conn)<br>     }<br>   }<br> <br>-  return 1;                     /* command processed ok */<br>+  return 0;<br>+}<br>+<br>+static int<br>+send_reply (struct connection *conn, uint64_t handle, uint32_t count, void *buf, uint32_t error)<br>+{<br>+  int r;<br>+<br>+  plugin_lock_reply (conn);<br>+  r = _send_reply (conn, handle, count, buf, error);<br>+  plugin_unlock_reply (conn);<br>+<br>+  return r;<br>+}<br>+<br>+int<br>+nbdkit_async_reply (void *conn, uint64_t reqid)<br>+{<br>+  return send_reply (conn, reqid, 0, NULL, 0);<br>+}<br>+<br>+int<br>+nbdkit_async_reply_read (void *conn, uint64_t reqid, uint32_t count, void *buf)<br>+{<br>+<br>+  return send_reply (conn, reqid, count, buf, 0);<br>+}<br>+<br>+int<br>+nbdkit_async_reply_error (void *conn, uint64_t reqid)<br>+{<br>+  uint32_t error = get_error (conn);<br>+  return send_reply (conn, reqid, 0, NULL, error);<br> }<br>diff --git a/src/internal.h b/src/internal.h<br>index e73edf1..93d32e9 100644<br>--- a/src/internal.h<br>+++ b/src/internal.h<br>@@ -114,6 +114,7 @@ extern void cleanup_free (void *ptr);<br> struct connection {<br>   int sockin, sockout;<br>   pthread_mutex_t request_lock;<br>+  pthread_mutex_t reply_lock;<br>   void *handle;<br>   uint64_t exportsize;<br>   int readonly;<br>@@ -140,6 +141,8 @@ extern void plugin_lock_connection (void);<br> extern void plugin_unlock_connection (void);<br> extern void plugin_lock_request (struct connection *conn);<br> extern void plugin_unlock_request (struct connection *conn);<br>+extern void plugin_lock_reply (struct connection *conn);<br>+extern void plugin_unlock_reply (struct connection *conn);<br> extern int plugin_errno_is_preserved (void);<br> extern int plugin_open (struct connection *conn, int readonly);<br> extern void plugin_close (struct connection *conn);<br>@@ -148,8 +151,12 @@ extern int plugin_can_write (struct connection *conn);<br> extern int plugin_can_flush (struct connection *conn);<br> extern int plugin_is_rotational (struct connection *conn);<br> extern int plugin_can_trim (struct connection *conn);<br>+extern int plugin_can_async_read (struct connection *conn);<br> extern int plugin_pread (struct connection *conn, void *buf, uint32_t count, uint64_t offset);<br>+extern int plugin_async_pread (struct connection *conn, uint64_t handle, bool flush, void *buf, uint32_t count, uint64_t offset);<br>+extern int plugin_can_async_write (struct connection *conn);<br> extern int plugin_pwrite (struct connection *conn, void *buf, uint32_t count, uint64_t offset);<br>+extern int plugin_async_pwrite (struct connection *conn, uint64_t handle, bool flush, void *buf, uint32_t count, uint64_t offset);<br> extern int plugin_flush (struct connection *conn);<br> extern int plugin_trim (struct connection *conn, uint32_t count, uint64_t offset);<br> extern int plugin_zero (struct connection *conn, uint32_t count, uint64_t offset, int may_trim);<br>diff --git a/src/plugins.c b/src/plugins.c<br>index eeed8a9..9da30db 100644<br>--- a/src/plugins.c<br>+++ b/src/plugins.c<br>@@ -121,8 +121,9 @@ plugin_register (const char *_filename,<br>              program_name, filename);<br>     exit (EXIT_FAILURE);<br>   }<br>-  if (plugin.pread == NULL) {<br>-    fprintf (stderr, "%s: %s: plugin must have a .pread callback\n",<br>+  if (plugin.pread == NULL &&<br>+     (plugin._thread_model != NBDKIT_THREAD_MODEL_ASYNC || plugin.async_pread == NULL)) {<br>+    fprintf (stderr, "%s: %s: plugin must have either a .pread or .async_pread callback\n",<br>              program_name, filename);<br>     exit (EXIT_FAILURE);<br>   }<br>@@ -231,6 +232,9 @@ plugin_dump_fields (void)<br>   case NBDKIT_THREAD_MODEL_PARALLEL:<br>     printf ("parallel");<br>     break;<br>+  case NBDKIT_THREAD_MODEL_ASYNC:<br>+    printf ("async");<br>+    break;<br>   default:<br>     printf ("%d # unknown thread model!", plugin._thread_model);<br>     break;<br>@@ -258,6 +262,8 @@ plugin_dump_fields (void)<br>   HAS (flush);<br>   HAS (trim);<br>   HAS (zero);<br>+  HAS (async_pread);<br>+  HAS (async_pwrite);<br> #undef HAS<br> }<br> <br>@@ -350,6 +356,28 @@ plugin_unlock_request (struct connection *conn)<br>   }<br> }<br> <br>+void<br>+plugin_lock_reply (struct connection *conn)<br>+{<br>+  assert (dl);<br>+<br>+  if (plugin._thread_model >= NBDKIT_THREAD_MODEL_PARALLEL) {<br>+    debug ("acquire per-connection reply lock");<br>+    pthread_mutex_lock (&conn->reply_lock);<br>+  }<br>+}<br>+<br>+void<br>+plugin_unlock_reply (struct connection *conn)<br>+{<br>+  assert (dl);<br>+<br>+  if (plugin._thread_model >= NBDKIT_THREAD_MODEL_PARALLEL) {<br>+    debug ("release per-connection reply lock");<br>+    pthread_mutex_unlock (&conn->reply_lock);<br>+  }<br>+}<br>+<br> int<br> plugin_errno_is_preserved (void)<br> {<br>@@ -414,7 +442,8 @@ plugin_can_write (struct connection *conn)<br>   if (plugin.can_write)<br>     return plugin.can_write (conn->handle);<br>   else<br>-    return plugin.pwrite != NULL;<br>+    return plugin.pwrite != NULL ||<br>+           (plugin._thread_model == NBDKIT_THREAD_MODEL_ASYNC && plugin.async_pwrite != NULL);<br> }<br> <br> int<br>@@ -460,6 +489,16 @@ plugin_can_trim (struct connection *conn)<br> }<br> <br> int<br>+plugin_can_async_read (struct connection *conn)<br>+{<br>+  assert (dl);<br>+  assert (conn->handle);<br>+<br>+  return ((plugin._thread_model == NBDKIT_THREAD_MODEL_ASYNC) &&<br>+          (plugin.async_pread != NULL));<br>+}<br>+<br>+int<br> plugin_pread (struct connection *conn,<br>               void *buf, uint32_t count, uint64_t offset)<br> {<br>@@ -473,6 +512,27 @@ plugin_pread (struct connection *conn,<br> }<br> <br> int<br>+plugin_async_pread (struct connection *conn, uint64_t handle, bool flush,<br>+                    void *buf, uint32_t count, uint64_t offset)<br>+{<br>+  assert (dl);<br>+  assert (conn->handle);<br>+<br>+  debug ("async_pread count=%" PRIu32 " offset=%" PRIu64, count, offset);<br>+<br>+  return plugin.async_pread (conn, handle, flush, conn->handle, buf, count, offset);<br>+}<br>+<br>+int<br>+plugin_can_async_write (struct connection *conn)<br>+{<br>+  assert (dl);<br>+  assert (conn->handle);<br>+<br>+  return (plugin._thread_model == NBDKIT_THREAD_MODEL_ASYNC) && (plugin.async_pwrite != NULL);<br>+}<br>+<br>+int<br> plugin_pwrite (struct connection *conn,<br>                void *buf, uint32_t count, uint64_t offset)<br> {<br>@@ -490,23 +550,44 @@ plugin_pwrite (struct connection *conn,<br> }<br> <br> int<br>+plugin_async_pwrite (struct connection *conn, uint64_t handle, bool flush,<br>+                     void *buf, uint32_t count, uint64_t offset)<br>+{<br>+  assert (dl);<br>+  assert (conn->handle);<br>+<br>+  debug ("async_pwrite count=%" PRIu32 " offset=%" PRIu64, count, offset);<br>+<br>+  if (plugin.async_pwrite != NULL)<br>+    return plugin.async_pwrite (conn, handle, flush, conn->handle, buf, count, offset);<br>+  else {<br>+    errno = EROFS;<br>+    return -1;<br>+  }<br>+}<br>+<br>+int<br> plugin_flush (struct connection *conn)<br> {<br>+  int r;<br>   assert (dl);<br>   assert (conn->handle);<br> <br>   debug ("flush");<br> <br>   if (plugin.flush != NULL)<br>-    return plugin.flush (conn->handle);<br>+    r = plugin.flush (conn->handle);<br>   else {<br>     errno = EINVAL;<br>-    return -1;<br>+    r = -1;<br>   }<br>+<br>+  return r;<br> }<br> <br> int<br>-plugin_trim (struct connection *conn, uint32_t count, uint64_t offset)<br>+plugin_trim (struct connection *conn,<br>+             uint32_t count, uint64_t offset)<br> {<br>   assert (dl);<br>   assert (conn->handle);<br></div></div>