mirror of https://github.com/xemu-project/xemu.git
9pfs: add new function v9fs_co_readdir_many()
The newly added function v9fs_co_readdir_many() retrieves multiple directory entries with a single fs driver request. It is intended to replace uses of v9fs_co_readdir(), the latter only retrieves a single directory entry per fs driver request instead. The reason for this planned replacement is that for every fs driver request the coroutine is dispatched from main I/O thread to a background I/O thread and eventually dispatched back to main I/O thread. Hopping between threads adds latency. So if a 9pfs Treaddir request reads a large amount of directory entries, this currently sums up to huge latencies of several hundred ms or even more. So using v9fs_co_readdir_many() instead of v9fs_co_readdir() will provide significant performance improvements. Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com> Message-Id: <73dc827a12ef577ae7e644dcf34a5c0e443ab42f.1596012787.git.qemu_oss@crudebyte.com> Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
This commit is contained in:
parent
dd8151f4fe
commit
2149675b19
22
hw/9pfs/9p.h
22
hw/9pfs/9p.h
|
@ -215,6 +215,28 @@ static inline void v9fs_readdir_init(V9fsDir *dir)
|
||||||
qemu_co_mutex_init(&dir->readdir_mutex);
|
qemu_co_mutex_init(&dir->readdir_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type for 9p fs drivers' (a.k.a. 9p backends) result of readdir requests,
|
||||||
|
* which is a chained list of directory entries.
|
||||||
|
*/
|
||||||
|
typedef struct V9fsDirEnt {
|
||||||
|
/* mandatory (must not be NULL) information for all readdir requests */
|
||||||
|
struct dirent *dent;
|
||||||
|
/*
|
||||||
|
* optional (may be NULL): A full stat of each directory entry is just
|
||||||
|
* done if explicitly told to fs driver.
|
||||||
|
*/
|
||||||
|
struct stat *st;
|
||||||
|
/*
|
||||||
|
* instead of an array, directory entries are always returned as
|
||||||
|
* chained list, that's because the amount of entries retrieved by fs
|
||||||
|
* drivers is dependent on the individual entries' name (since response
|
||||||
|
* messages are size limited), so the final amount cannot be estimated
|
||||||
|
* before hand
|
||||||
|
*/
|
||||||
|
struct V9fsDirEnt *next;
|
||||||
|
} V9fsDirEnt;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Filled by fs driver on open and other
|
* Filled by fs driver on open and other
|
||||||
* calls.
|
* calls.
|
||||||
|
|
171
hw/9pfs/codir.c
171
hw/9pfs/codir.c
|
@ -39,6 +39,10 @@ static int do_readdir(V9fsPDU *pdu, V9fsFidState *fidp, struct dirent **dent)
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: This will be removed for performance reasons.
|
||||||
|
* Use v9fs_co_readdir_many() instead.
|
||||||
|
*/
|
||||||
int coroutine_fn v9fs_co_readdir(V9fsPDU *pdu, V9fsFidState *fidp,
|
int coroutine_fn v9fs_co_readdir(V9fsPDU *pdu, V9fsFidState *fidp,
|
||||||
struct dirent **dent)
|
struct dirent **dent)
|
||||||
{
|
{
|
||||||
|
@ -53,6 +57,173 @@ int coroutine_fn v9fs_co_readdir(V9fsPDU *pdu, V9fsFidState *fidp,
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is solely executed on a background IO thread.
|
||||||
|
*
|
||||||
|
* See v9fs_co_readdir_many() (as its only user) below for details.
|
||||||
|
*/
|
||||||
|
static int do_readdir_many(V9fsPDU *pdu, V9fsFidState *fidp,
|
||||||
|
struct V9fsDirEnt **entries, off_t offset,
|
||||||
|
int32_t maxsize, bool dostat)
|
||||||
|
{
|
||||||
|
V9fsState *s = pdu->s;
|
||||||
|
V9fsString name;
|
||||||
|
int len, err = 0;
|
||||||
|
int32_t size = 0;
|
||||||
|
off_t saved_dir_pos;
|
||||||
|
struct dirent *dent;
|
||||||
|
struct V9fsDirEnt *e = NULL;
|
||||||
|
V9fsPath path;
|
||||||
|
struct stat stbuf;
|
||||||
|
|
||||||
|
*entries = NULL;
|
||||||
|
v9fs_path_init(&path);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: Here should be a warn_report_once() if lock failed.
|
||||||
|
*
|
||||||
|
* With a good 9p client we should not get into concurrency here,
|
||||||
|
* because a good client would not use the same fid for concurrent
|
||||||
|
* requests. We do the lock here for safety reasons though. However
|
||||||
|
* the client would then suffer performance issues, so better log that
|
||||||
|
* issue here.
|
||||||
|
*/
|
||||||
|
v9fs_readdir_lock(&fidp->fs.dir);
|
||||||
|
|
||||||
|
/* seek directory to requested initial position */
|
||||||
|
if (offset == 0) {
|
||||||
|
s->ops->rewinddir(&s->ctx, &fidp->fs);
|
||||||
|
} else {
|
||||||
|
s->ops->seekdir(&s->ctx, &fidp->fs, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* save the directory position */
|
||||||
|
saved_dir_pos = s->ops->telldir(&s->ctx, &fidp->fs);
|
||||||
|
if (saved_dir_pos < 0) {
|
||||||
|
err = saved_dir_pos;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
/* interrupt loop if request was cancelled by a Tflush request */
|
||||||
|
if (v9fs_request_cancelled(pdu)) {
|
||||||
|
err = -EINTR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get directory entry from fs driver */
|
||||||
|
err = do_readdir(pdu, fidp, &dent);
|
||||||
|
if (err || !dent) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* stop this loop as soon as it would exceed the allowed maximum
|
||||||
|
* response message size for the directory entries collected so far,
|
||||||
|
* because anything beyond that size would need to be discarded by
|
||||||
|
* 9p controller (main thread / top half) anyway
|
||||||
|
*/
|
||||||
|
v9fs_string_init(&name);
|
||||||
|
v9fs_string_sprintf(&name, "%s", dent->d_name);
|
||||||
|
len = v9fs_readdir_response_size(&name);
|
||||||
|
v9fs_string_free(&name);
|
||||||
|
if (size + len > maxsize) {
|
||||||
|
/* this is not an error case actually */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* append next node to result chain */
|
||||||
|
if (!e) {
|
||||||
|
*entries = e = g_malloc0(sizeof(V9fsDirEnt));
|
||||||
|
} else {
|
||||||
|
e = e->next = g_malloc0(sizeof(V9fsDirEnt));
|
||||||
|
}
|
||||||
|
e->dent = g_malloc0(sizeof(struct dirent));
|
||||||
|
memcpy(e->dent, dent, sizeof(struct dirent));
|
||||||
|
|
||||||
|
/* perform a full stat() for directory entry if requested by caller */
|
||||||
|
if (dostat) {
|
||||||
|
err = s->ops->name_to_path(
|
||||||
|
&s->ctx, &fidp->path, dent->d_name, &path
|
||||||
|
);
|
||||||
|
if (err < 0) {
|
||||||
|
err = -errno;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s->ops->lstat(&s->ctx, &path, &stbuf);
|
||||||
|
if (err < 0) {
|
||||||
|
err = -errno;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
e->st = g_malloc0(sizeof(struct stat));
|
||||||
|
memcpy(e->st, &stbuf, sizeof(struct stat));
|
||||||
|
}
|
||||||
|
|
||||||
|
size += len;
|
||||||
|
saved_dir_pos = dent->d_off;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* restore (last) saved position */
|
||||||
|
s->ops->seekdir(&s->ctx, &fidp->fs, saved_dir_pos);
|
||||||
|
|
||||||
|
out:
|
||||||
|
v9fs_readdir_unlock(&fidp->fs.dir);
|
||||||
|
v9fs_path_free(&path);
|
||||||
|
if (err < 0) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reads multiple directory entries in one rush.
|
||||||
|
*
|
||||||
|
* Retrieves the requested (max. amount of) directory entries from the fs
|
||||||
|
* driver. This function must only be called by the main IO thread (top half).
|
||||||
|
* Internally this function call will be dispatched to a background IO thread
|
||||||
|
* (bottom half) where it is eventually executed by the fs driver.
|
||||||
|
*
|
||||||
|
* @discussion Acquiring multiple directory entries in one rush from the fs
|
||||||
|
* driver, instead of retrieving each directory entry individually, is very
|
||||||
|
* beneficial from performance point of view. Because for every fs driver
|
||||||
|
* request latency is added, which in practice could lead to overall
|
||||||
|
* latencies of several hundred ms for reading all entries (of just a single
|
||||||
|
* directory) if every directory entry was individually requested from fs
|
||||||
|
* driver.
|
||||||
|
*
|
||||||
|
* @note You must @b ALWAYS call @c v9fs_free_dirents(entries) after calling
|
||||||
|
* v9fs_co_readdir_many(), both on success and on error cases of this
|
||||||
|
* function, to avoid memory leaks once @p entries are no longer needed.
|
||||||
|
*
|
||||||
|
* @param pdu - the causing 9p (T_readdir) client request
|
||||||
|
* @param fidp - already opened directory where readdir shall be performed on
|
||||||
|
* @param entries - output for directory entries (must not be NULL)
|
||||||
|
* @param offset - initial position inside the directory the function shall
|
||||||
|
* seek to before retrieving the directory entries
|
||||||
|
* @param maxsize - maximum result message body size (in bytes)
|
||||||
|
* @param dostat - whether a stat() should be performed and returned for
|
||||||
|
* each directory entry
|
||||||
|
* @returns resulting response message body size (in bytes) on success,
|
||||||
|
* negative error code otherwise
|
||||||
|
*/
|
||||||
|
int coroutine_fn v9fs_co_readdir_many(V9fsPDU *pdu, V9fsFidState *fidp,
|
||||||
|
struct V9fsDirEnt **entries,
|
||||||
|
off_t offset, int32_t maxsize,
|
||||||
|
bool dostat)
|
||||||
|
{
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
if (v9fs_request_cancelled(pdu)) {
|
||||||
|
return -EINTR;
|
||||||
|
}
|
||||||
|
v9fs_co_run_in_worker({
|
||||||
|
err = do_readdir_many(pdu, fidp, entries, offset, maxsize, dostat);
|
||||||
|
});
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
off_t v9fs_co_telldir(V9fsPDU *pdu, V9fsFidState *fidp)
|
off_t v9fs_co_telldir(V9fsPDU *pdu, V9fsFidState *fidp)
|
||||||
{
|
{
|
||||||
off_t err;
|
off_t err;
|
||||||
|
|
|
@ -49,6 +49,9 @@
|
||||||
void co_run_in_worker_bh(void *);
|
void co_run_in_worker_bh(void *);
|
||||||
int coroutine_fn v9fs_co_readlink(V9fsPDU *, V9fsPath *, V9fsString *);
|
int coroutine_fn v9fs_co_readlink(V9fsPDU *, V9fsPath *, V9fsString *);
|
||||||
int coroutine_fn v9fs_co_readdir(V9fsPDU *, V9fsFidState *, struct dirent **);
|
int coroutine_fn v9fs_co_readdir(V9fsPDU *, V9fsFidState *, struct dirent **);
|
||||||
|
int coroutine_fn v9fs_co_readdir_many(V9fsPDU *, V9fsFidState *,
|
||||||
|
struct V9fsDirEnt **, off_t, int32_t,
|
||||||
|
bool);
|
||||||
off_t coroutine_fn v9fs_co_telldir(V9fsPDU *, V9fsFidState *);
|
off_t coroutine_fn v9fs_co_telldir(V9fsPDU *, V9fsFidState *);
|
||||||
void coroutine_fn v9fs_co_seekdir(V9fsPDU *, V9fsFidState *, off_t);
|
void coroutine_fn v9fs_co_seekdir(V9fsPDU *, V9fsFidState *, off_t);
|
||||||
void coroutine_fn v9fs_co_rewinddir(V9fsPDU *, V9fsFidState *);
|
void coroutine_fn v9fs_co_rewinddir(V9fsPDU *, V9fsFidState *);
|
||||||
|
|
Loading…
Reference in New Issue