From 4e9052012f033da164152ddf7a3818e98fbd9e7e Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Mon, 27 Jul 2026 02:58:52 +0200 Subject: [PATCH] Keep imgutil pack and capture synchronous Both functions became coroutines solely to await one get_hashes call, but their bodies are long stretches of blocking work: mksquashfs, the encrypt_image copy loop, rsync, ssh and osdeploy. From Python 3.11 on, asyncio.run installs a SIGINT handler that cancels the main task and returns rather than raising, so an interrupt is only noticed at the next await. Interrupting a pack during mksquashfs surfaced as a CalledProcessError from the dying child instead of a KeyboardInterrupt, and with a base profile, where nothing is ever awaited, pack carried on and published the profile before exiting. Run the loop only around the call that needs it. --- imgutil/imgutil | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/imgutil/imgutil b/imgutil/imgutil index 1fb62b96..00500bbc 100644 --- a/imgutil/imgutil +++ b/imgutil/imgutil @@ -197,7 +197,7 @@ def build_el_boot_tree(targpath): gather_bootloader(targpath) -async def capture_remote(args): +def capture_remote(args): targ = args.node outdir = args.profilename os.umask(0o022) @@ -267,7 +267,7 @@ async def capture_remote(args): indir = '{}/profiles/default'.format(confdir) if os.path.exists(indir): copy_tree(indir, outdir) - hmap = await osimage.get_hashes(outdir, indir) + hmap = asyncio.run(osimage.get_hashes(outdir, indir)) with open('{0}/manifest.yaml'.format(outdir), 'w') as yout: yout.write('# This manifest enables rebase to know original source of profile data and if any customizations have been done\n') manifestdata = {'distdir': indir, 'disthashes': hmap} @@ -1015,13 +1015,13 @@ def main(): if args.subcommand == 'build': build_root(args) elif args.subcommand == 'capture': - asyncio.run(capture_remote(args)) + capture_remote(args) elif args.subcommand == 'unpack': unpack_image(args) elif args.subcommand == 'exec': exec_root(args) elif args.subcommand == 'pack': - asyncio.run(pack_image(args)) + pack_image(args) else: parser.print_usage() @@ -1544,7 +1544,7 @@ def recursecp(source, targ): shutil.copy2(source, targ) -async def pack_image(args): +def pack_image(args): outdir = args.profilename if '/' in outdir: raise Exception('Full path not supported, supply only the profile name\n') @@ -1663,7 +1663,7 @@ async def pack_image(args): indir = '{}/profiles/default'.format(confdir) if os.path.exists(indir): copy_tree(indir, outdir) - hmap = await osimage.get_hashes(outdir, indir) + hmap = asyncio.run(osimage.get_hashes(outdir, indir)) with open('{0}/manifest.yaml'.format(outdir), 'w') as yout: yout.write('# This manifest enables rebase to know original source of profile data and if any customizations have been done\n') manifestdata = {'distdir': indir, 'disthashes': hmap}