On Sun, May 31, 2026 at 12:34:37PM +0200, Christian Hergert wrote:
> This keeps consistent reads but defers writes to a half-second timer by
> implementing the suggested TODO in block/vvfat.c.
> 
> I noticed slow boot performance when testing a UKI image. It was roughly
> 18 seconds to a login prompt with a simple userspace. I profiled quickly
> with Sysprof which showed an overwhelming amount of time spent in
> check_directory_consistency().
> 
> The -drive in question here is the following:
> 
>  file=fat:rw:/tmp/machines-uki-esp-HVCXP3,format=raw,media=disk
> 
> which has a temporary NvVars in it for quick throwaway VM testing which
> I use in conjunction with mkosi.

By NvVars are you referring to a NVRAM backing store for use
with EDK2 ?

If so, then newer EDK2 supports a JSON backing store which would
be a better bet for quick & easy VM testing IMHO.

Not an objection to this patch, but we tend to recommend that any
use of vvfat is best avoided where any other alternative is available.

> 
> With this commit in place, boot to login reduces from 18 seconds to 6.
> 
> Signed-off-by: Christian Hergert <[email protected]>
> ---
>  block/vvfat.c | 130 ++++++++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 125 insertions(+), 5 deletions(-)

FYI, applying this patch appears to break the I/O tests for vvfat:

$ ./build/run ./build/tests/qemu-iotests/check -vvfat  vvfat
QEMU          -- "/home/berrange/src/virt/qemu/build/qemu-system-x86_64" 
-nodefaults -display none -accel qtest
QEMU_IMG      -- "/home/berrange/src/virt/qemu/build/qemu-img" 
QEMU_IO       -- "/home/berrange/src/virt/qemu/build/qemu-io" --cache writeback 
--aio threads -f vvfat
QEMU_NBD      -- "/home/berrange/src/virt/qemu/build/qemu-nbd" 
IMGFMT        -- vvfat
IMGPROTO      -- file
PLATFORM      -- Linux/x86_64 berrange.csb 7.0.9-205.fc44.x86_64
TEST_DIR      -- /home/berrange/src/virt/qemu/scratch
SOCK_DIR      -- /tmp/qemu-iotests-4_wjh3ml
GDB_OPTIONS   -- 
VALGRIND_QEMU -- 
PRINT_QEMU_OUTPUT -- 

vvfat   fail       [12:16:35] [12:16:39]   3.3s   (last: 3.2s)  failed, exit 
status 1
--- /home/berrange/src/virt/qemu/tests/qemu-iotests/tests/vvfat.out
+++ /home/berrange/src/virt/qemu/scratch/vvfat-file-vvfat/vvfat.out.bad
@@ -1,5 +1,105 @@
-................
+E..F..FFFFFFF..F
+======================================================================
+ERROR: test_create_file (__main__.TestVVFatDriver.test_create_file)
+Test creating a new file
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "/home/berrange/src/virt/qemu/tests/qemu-iotests/tests/vvfat", line 
477, in test_create_file
+    with open(os.path.join(filesystem, "newfile.txt"), "rb") as f:
+         ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+FileNotFoundError: [Errno 2] No such file or directory: 
'/home/berrange/src/virt/qemu/scratch/vvfat-file-vvfat/filesystem/newfile.txt'
+
+======================================================================
+FAIL: test_modify_content_same_clusters 
(__main__.TestVVFatDriver.test_modify_content_same_clusters)
+Test modifying the content of the file without changing the number of
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "/home/berrange/src/virt/qemu/tests/qemu-iotests/tests/vvfat", line 
300, in test_modify_content_same_clusters
+    self.assertEqual(f.read(), new_content)
+    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
+AssertionError: b'Hello, world! 0\n' != b'Hello, world! Modified\n'
+
+======================================================================
+FAIL: test_truncate_file_change_clusters_less 
(__main__.TestVVFatDriver.test_truncate_file_change_clusters_less)
+Test truncating a file by reducing the number of clusters
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "/home/berrange/src/virt/qemu/tests/qemu-iotests/tests/vvfat", line 
377, in test_truncate_file_change_clusters_less
+    self.assertEqual(f.read(), b"A")
+    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
+AssertionError: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[16338 chars]PPPP' 
!= b'A'
+
+======================================================================
+FAIL: test_truncate_file_same_clusters_less 
(__main__.TestVVFatDriver.test_truncate_file_same_clusters_less)
+Test truncating the file without changing number of clusters
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "/home/berrange/src/virt/qemu/tests/qemu-iotests/tests/vvfat", line 
319, in test_truncate_file_same_clusters_less
+    self.assertEqual(f.read(), new_content)
+    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
+AssertionError: b'Hello, world! 0\n' != b'Hello'
+
+======================================================================
+FAIL: test_truncate_file_same_clusters_more 
(__main__.TestVVFatDriver.test_truncate_file_same_clusters_more)
+Test truncating the file without changing number of clusters
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "/home/berrange/src/virt/qemu/tests/qemu-iotests/tests/vvfat", line 
343, in test_truncate_file_same_clusters_more
+    self.assertEqual(f.read(), new_content)
+    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
+AssertionError: b'Hello, world! 0\n' != b'Hello, world! 0\n(((('
+
+======================================================================
+FAIL: test_write_file_change_clusters_less 
(__main__.TestVVFatDriver.test_write_file_change_clusters_less)
+Test truncating a file by reducing the number of clusters
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "/home/berrange/src/virt/qemu/tests/qemu-iotests/tests/vvfat", line 
393, in test_write_file_change_clusters_less
+    self.assertEqual(f.read(), new_content)
+    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
+AssertionError: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[24531 chars]XXXX' 
!= b'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX[16339 chars]YYYY'
+
+======================================================================
+FAIL: test_write_file_change_clusters_more 
(__main__.TestVVFatDriver.test_write_file_change_clusters_more)
+Test truncating a file by increasing the number of clusters
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "/home/berrange/src/virt/qemu/tests/qemu-iotests/tests/vvfat", line 
415, in test_write_file_change_clusters_more
+    self.assertEqual(f.read(), new_content)
+    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
+AssertionError: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[24531 chars]XXXX' 
!= b'WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[32723 chars]ZZZZ'
+
+======================================================================
+FAIL: test_write_file_change_clusters_more_non_contiguous_2_mappings 
(__main__.TestVVFatDriver.test_write_file_change_clusters_more_non_contiguous_2_mappings)
+Test truncating a file by increasing the number of clusters Here we
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "/home/berrange/src/virt/qemu/tests/qemu-iotests/tests/vvfat", line 
435, in test_write_file_change_clusters_more_non_contiguous_2_mappings
+    self.assertEqual(f.read(), new_content)
+    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
+AssertionError: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[16339 chars]PPPP' 
!= b'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX[24531 chars]ZZZZ'
+
+======================================================================
+FAIL: test_write_file_change_clusters_more_non_contiguous_3_mappings 
(__main__.TestVVFatDriver.test_write_file_change_clusters_more_non_contiguous_3_mappings)
+Test truncating a file by increasing the number of clusters Here we
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "/home/berrange/src/virt/qemu/tests/qemu-iotests/tests/vvfat", line 
460, in test_write_file_change_clusters_more_non_contiguous_3_mappings
+    self.assertEqual(f.read(), new_content)
+    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
+AssertionError: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[16339 chars]PPPP' 
!= b'WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW[32723 chars]ZZZZ'
+
+======================================================================
+FAIL: test_write_large_file (__main__.TestVVFatDriver.test_write_large_file)
+Test writing a large file
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "/home/berrange/src/virt/qemu/tests/qemu-iotests/tests/vvfat", line 
362, in test_write_large_file
+    self.assertEqual(f.read(), new_content)
+    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
+AssertionError: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[16339 chars]PPPP' 
!= b'ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ[16339 chars]KKKK'
+
 ----------------------------------------------------------------------
 Ran 16 tests

-OK
+FAILED (failures=9, errors=1)
Failures: vvfat
Failed 1 of 1 iotests


I didn't investigate whether this is a real functional bug, or whether
the tests are making bad assumptions that need fixing, or whether this
is just inherant behaviour from delaying writes.

> 
> diff --git a/block/vvfat.c b/block/vvfat.c
> index e334b9febb..49756ac5e0 100644
> --- a/block/vvfat.c
> +++ b/block/vvfat.c
> @@ -32,6 +32,7 @@
>  #include "block/qdict.h"
>  #include "qemu/module.h"
>  #include "qemu/option.h"
> +#include "qemu/timer.h"
>  #include "qemu/bswap.h"
>  #include "migration/blocker.h"
>  #include "qobject/qdict.h"
> @@ -53,8 +54,6 @@
>      Note that DOS assumes the system files to be the first files in the
>      file system (test if the boot sector still relies on that fact)! */
>  /* MAYBE TODO: write block-visofs.c */
> -/* TODO: call try_commit() only after a timeout */
> -
>  /* #define DEBUG */
>  
>  #ifdef DEBUG
> @@ -80,6 +79,8 @@ static void checkpoint(void);
>  #define DIR_KANJI_FAKE 0x05
>  #define DIR_FREE 0x00
>  
> +#define VVFAT_COMMIT_DELAY_NS (NANOSECONDS_PER_SECOND / 2)
> +
>  /* dynamic array functions */
>  typedef struct array_t {
>      char* pointer;
> @@ -332,6 +333,8 @@ typedef struct BDRVVVFATState {
>      void* fat2;
>      char* used_clusters;
>      array_t commits;
> +    QEMUTimer *commit_timer;
> +    bool commit_pending;
>      const char* path;
>      int downcase_short_names;
>  
> @@ -1052,6 +1055,8 @@ static BDRVVVFATState *vvv = NULL;
>  
>  static int enable_write_target(BlockDriverState *bs, Error **errp);
>  static int coroutine_fn is_consistent(BDRVVVFATState *s);
> +static void vvfat_attach_aio_context(BlockDriverState *bs,
> +                                     AioContext *new_context);
>  
>  static QemuOptsList runtime_opts = {
>      .name = "vvfat",
> @@ -1278,6 +1283,7 @@ static int vvfat_open(BlockDriverState *bs, QDict 
> *options, int flags,
>      }
>  
>      qemu_co_mutex_init(&s->lock);
> +    vvfat_attach_aio_context(bs, bdrv_get_aio_context(bs));
>  
>      qemu_opts_del(opts);
>  
> @@ -2973,6 +2979,60 @@ DLOG(checkpoint());
>      return do_commit(s);
>  }
>  
> +static int coroutine_fn GRAPH_RDLOCK
> +vvfat_commit_now(BDRVVVFATState *s)
> +{
> +    int ret;
> +
> +    if (s->commit_timer) {
> +        timer_del(s->commit_timer);
> +    }
> +    if (!s->commit_pending) {
> +        return 0;
> +    }
> +
> +    qemu_co_mutex_lock(&s->lock);
> +    ret = try_commit(s);
> +    qemu_co_mutex_unlock(&s->lock);
> +
> +    if (ret == 0) {
> +        s->commit_pending = false;
> +    }
> +
> +    return ret;
> +}
> +
> +static void coroutine_fn vvfat_commit_timer_entry(void *opaque)
> +{
> +    BlockDriverState *bs = opaque;
> +    BDRVVVFATState *s = bs->opaque;
> +    GRAPH_RDLOCK_GUARD();
> +
> +    vvfat_commit_now(s);
> +    bdrv_dec_in_flight(bs);
> +}
> +
> +static void vvfat_commit_timer_cb(void *opaque)
> +{
> +    BlockDriverState *bs = opaque;
> +    Coroutine *co;
> +
> +    co = qemu_coroutine_create(vvfat_commit_timer_entry, bs);
> +    bdrv_inc_in_flight(bs);
> +    qemu_coroutine_enter(co);
> +}
> +
> +static void vvfat_schedule_commit(BDRVVVFATState *s)
> +{
> +    s->commit_pending = true;
> +
> +    if (s->commit_timer) {
> +        timer_mod_ns(s->commit_timer,
> +                     qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
> +                     VVFAT_COMMIT_DELAY_NS);
> +    }
> +}
> +
>  static int coroutine_fn GRAPH_RDLOCK
>  vvfat_write(BlockDriverState *bs, int64_t sector_num,
>              const uint8_t *buf, int nb_sectors)
> @@ -3098,8 +3158,7 @@ DLOG(fprintf(stderr, "Write to qcow backend: %d + 
> %d\n", (int)sector_num, nb_sec
>      }
>  
>  DLOG(checkpoint());
> -    /* TODO: add timeout */
> -    try_commit(s);
> +    vvfat_schedule_commit(s);
>  
>  DLOG(checkpoint());
>      return 0;
> @@ -3133,6 +3192,34 @@ vvfat_co_pwritev(BlockDriverState *bs, int64_t offset, 
> int64_t bytes,
>      return ret;
>  }
>  
> +static int coroutine_fn GRAPH_RDLOCK
> +vvfat_co_flush(BlockDriverState *bs)
> +{
> +    BDRVVVFATState *s = bs->opaque;
> +
> +    if (s->qcow == NULL) {
> +        return 0;
> +    }
> +
> +    return vvfat_commit_now(s);
> +}
> +
> +static void vvfat_drain_begin(BlockDriverState *bs)
> +{
> +    BDRVVVFATState *s = bs->opaque;
> +    Coroutine *co;
> +
> +    if (!s->commit_timer || !timer_pending(s->commit_timer)) {
> +        return;
> +    }
> +
> +    timer_del(s->commit_timer);
> +
> +    co = qemu_coroutine_create(vvfat_commit_timer_entry, bs);
> +    bdrv_inc_in_flight(bs);
> +    aio_co_enter(bdrv_get_aio_context(bs), co);
> +}
> +
>  static int coroutine_fn vvfat_co_block_status(BlockDriverState *bs,
>                                                unsigned int mode,
>                                                int64_t offset, int64_t bytes,
> @@ -3229,6 +3316,11 @@ static void vvfat_close(BlockDriverState *bs)
>  {
>      BDRVVVFATState *s = bs->opaque;
>  
> +    if (s->commit_timer) {
> +        timer_free(s->commit_timer);
> +        s->commit_timer = NULL;
> +    }
> +
>      vvfat_close_current_file(s);
>      array_free(&(s->fat));
>      array_free(&(s->directory));
> @@ -3240,6 +3332,30 @@ static void vvfat_close(BlockDriverState *bs)
>      }
>  }
>  
> +static void vvfat_detach_aio_context(BlockDriverState *bs)
> +{
> +    BDRVVVFATState *s = bs->opaque;
> +
> +    if (s->commit_timer) {
> +        timer_free(s->commit_timer);
> +        s->commit_timer = NULL;
> +    }
> +}
> +
> +static void vvfat_attach_aio_context(BlockDriverState *bs,
> +                                     AioContext *new_context)
> +{
> +    BDRVVVFATState *s = bs->opaque;
> +
> +    if (s->qcow) {
> +        s->commit_timer = aio_timer_new(new_context, QEMU_CLOCK_VIRTUAL,
> +                                        SCALE_NS, vvfat_commit_timer_cb, bs);
> +        if (s->commit_pending) {
> +            vvfat_schedule_commit(s);
> +        }
> +    }
> +}
> +
>  static const char *const vvfat_strong_runtime_opts[] = {
>      "dir",
>      "fat-type",
> @@ -3263,7 +3379,11 @@ static BlockDriver bdrv_vvfat = {
>  
>      .bdrv_co_preadv         = vvfat_co_preadv,
>      .bdrv_co_pwritev        = vvfat_co_pwritev,
> -    .bdrv_co_block_status   = vvfat_co_block_status,
> +    .bdrv_co_flush          = vvfat_co_flush,
> +    .bdrv_drain_begin       = vvfat_drain_begin,
> +    .bdrv_co_block_status    = vvfat_co_block_status,
> +    .bdrv_detach_aio_context = vvfat_detach_aio_context,
> +    .bdrv_attach_aio_context = vvfat_attach_aio_context,
>  
>      .strong_runtime_opts    = vvfat_strong_runtime_opts,
>  };
> -- 
> 2.54.0
> 
> 

With regards,
Daniel
-- 
|: https://berrange.com       ~~        https://hachyderm.io/@berrange :|
|: https://libvirt.org          ~~          https://entangle-photo.org :|
|: https://pixelfed.art/berrange   ~~    https://fstop138.berrange.com :|


Reply via email to