[Libguestfs] [nbdkit PATCH v2 4/6] filters: Track next handle alongside next backend
Eric Blake
eblake at redhat.com
Mon Mar 1 16:57:24 UTC 2021
On 3/1/21 9:53 AM, Richard W.M. Jones wrote:
> So ... this is all complicated ... And reverts some changes which I
> found simplified things.
>
> Stepping back here, is it the case that next-ops is really a hack, and
> what filters should actually get is some kind of connection object
> (let's call it that, even if it doesn't exactly correspond to "struct
> connection"), on which they can make general calls to the layer
> beneath.
I'm almost thinking of calling it a 'context object', but yes, that's
the idea.
>
> Then we allow filters to open and close new connection objects.
Yes.
>
> How do filters get these connection objects? I would say that unlike
> next-ops, filters should not get a connection object with each
> callback. Instead in filter_open the filter should explicitly open a
> new connection, and in filter_close explicitly close it. Then
> filter_pread would make a pread() call on the connection object which
> the filter has saved in the filter's handle.
>
> My questions would be, assuming a design like this magically existed:
>
> (1) Does it solve the problem for multi-conn filter?
>
> (2) Does it solve the problem for background threads?
Right now, I'm still experimenting with design on top of this series
which will get a working background thread setup (with a
proof-of-concept for that using the ext2 filter), but here's my thoughts
so far:
A filter needs to know which backend it is (or which backend is next);
right now, we use 'void *nxdata' to encapsulate struct backend*, but
could make 'void*' more typesafe at the expense of a lot of mechanical
churn in the filters.
At any point in .after_fork(), a filter can call:
nbdkit_context *nbdkit_context_open (void *nxdata, int readonly, const
char *exportname);
to open up a new context visiting the appropriate backend stored in
nxdata (but leaving nxdata unchanged), along with a matching:
void nbdkit_context_close (nbdkit_context *context);
to close it. Most filters want 1:1 context per connection, which means
we want a way to associate a just-opened context into the usual handle
so that next_ops->FOO(nxdata) works as before. I'm thinking:
int nbdkit_context_associate (void *nxdata, nbdkit_context *context);
which returns 0 on success, -1 on failure, and updates nxdata so that
future calls to next_ops->FOO(nxdata) now operate on the context. The
existing:
filter_open (nbdkit_next_open *next_open, void *nxdata, ...)
where a filter calls next_open (nxdata, ...) remains as shorthand for
calling both nbdkit_context_open() and nbdkit_context_associate().
Filters like ext2 that want to share a single context among all
connections never call next_open() or nbdkit_context_associate(), but
instead need a way to operate on the single context opened during
.after_fork(); this could look like:
int nbdkit_context_next_ops (nbdkit_context *context, struct
nbdkit_next_ops **next_ops, void **nxdata);
which populates a filter-global next_ops and nxdata pointer so the
filter can later call shared_next_ops->FOO(shared_nxdata, ...) as
needed. Filters that do not call nbdkit_context_associate() (including
implicitly via .open's next_open) will be passed next_ops==NULL in their
other various .foo functions (to force them to call through their
shared_next_ops object obtained earlier).
The reopen filter currently calls next_ops->reopen() to first close out
the existing context and then attempt to reopen a new one in its place.
But it seems a bit more logical that we would get rid of
next_ops->reopen(), and instead have .reopen first attempt to use
nbdkit_context_open() to open a new context, and if successful, then
call nbdkit_context_close() on the old context, and
nbdkit_context_associate (nxdata) to associate the new context into place.
The multi-conn filter still needs to track a list of all open contexts,
in order to call list[n]->next_ops->flush(list[n]->nxdata) across each
context.
I also note that at present, the 'struct nbdkit_next_ops * next_ops' we
pass to all filters is constant (just a list of function pointers, no
per-connection state); all the per-connection details are shared between
a 'struct backend *' (which backend to call into, part of nxdata both
before and after this patch) and a 'struct handle *' (state of whether
we are safe, and what pointer to pass to that backend - stored in TLS
prior to this series, but explicitly in nxdata afterwards). The
combination of struct backend and struct handle is what forms an
nbdkit_context. And since the function pointers in next_ops are always
the same, we could change the signature for ALL filters to have just one
pointer instead of 2 as the parameter to all .foo callbacks, changing:
int filter_pread (struct nbdkit_next_ops *next_ops, void *nxdata,
void *handle, ...)
into:
int filter_pread (nbdkit_context *nxdata, void *handle, ...)
where nxdata is NULL if nothing was associated during .open, and when
non-NULL, the filter uses nxdata->pread(nxdata, ...) to call into the
next context. But that extra type-safety and change in signatures is
more churn across all the filters. It would also mean we don't need
nbdkit_context_next_ops() proposed above.
I hope to post some patches later this week (maybe today?) that
demonstrate where I'm headed.
--
Eric Blake, Principal Software Engineer
Red Hat, Inc. +1-919-301-3226
Virtualization: qemu.org | libvirt.org
More information about the Libguestfs
mailing list