fix: destroy the ncnn GPU instance before exit instead of during DLL unload - #1500
Open
FortunaCournot wants to merge 4 commits into
Open
fix: destroy the ncnn GPU instance before exit instead of during DLL unload#1500FortunaCournot wants to merge 4 commits into
FortunaCournot wants to merge 4 commits into
Conversation
… actions/cache@v2) CONTROL BRANCH - upstream 6.4.0 with no source change, only the CI fix that is needed for the Windows job to start at all. Used to prove the segfault exists in a binary built by this very workflow, before claiming a patch fixes it.
setup-vulkan-sdk builds the Vulkan components from source. The pinned 1.3.204.0 is from 2021 and its CMakeLists declare cmake_minimum_required below 3.5, which CMake 4 - now on windows-latest - refuses outright. The Windows job therefore cannot even configure.
…olset The install step spelled out boost_program_options-vc143-mt-gd-x64-1_86.dll. windows-latest now ships Visual Studio 18, so Boost builds as vc180 and the install fails with "file INSTALL cannot find ...-vc143-...dll" - after everything has already compiled and linked. install(DIRECTORY ... FILES_MATCHING) is evaluated at install time, so the glob sees the DLL that does not exist yet when CMake configures.
…unload video2x always terminates with a segmentation fault, even when the run succeeds. It prints "Video processed successfully", writes its complete processing summary, and only then dies. A successful run and a failed one are therefore indistinguishable by exit code, and every script driving video2x has to ignore the exit code and inspect the output file instead. Cause: nothing ever destroys ncnn's Vulkan instance on purpose. ncnn creates it lazily on first use and only tears it down from its static __ncnn_vulkan_instance_holder destructor plus an atexit() handler it registers itself. On Windows ncnn is a SHARED library here (CMakeLists.txt: "Use the pre-built shared ncnn library on Windows"; build.yml fetches ncnn-*-windows-vs2022-shared.zip), so both of those run while the loader is already unloading DLLs at process exit. Calling into another DLL at that point is unsafe, and the teardown does exactly that: vkDeviceWaitIdle(), vkDestroyInstance() and glslang::FinalizeProcess(). The access violation lands after main() has returned, which is also why Windows Error Reporting never logs it. ncnn's author knows the hazard - gpu.cpp says "atexit() seems to be helpful for calling it earlier --- nihui" - and his reference tools (rife-ncnn-vulkan, realesrgan-ncnn-vulkan) call destroy_gpu_instance() explicitly from main() rather than relying on that teardown. Do the same. libvideo2x gains video2x::release_gpu_resources(), and the CLI calls it from an RAII guard declared as the first local in main(), so it fires on every exit path and is destroyed after the VideoProcessor - the processor's Vulkan buffers must be gone before the instance they came from is. destroy_gpu_instance() returns immediately when no instance was ever created, so this is a no-op for the libplacebo path, for --help, and for a failed argument parse; ncnn's own atexit handler still runs afterwards and finds nothing to do.
FortunaCournot
added a commit
to FortunaCournot/video2x
that referenced
this pull request
Jul 14, 2026
The VR we are installer needs a Video2X whose exit code means something. Upstream segfaults on exit on every run, and has not released the fix (k4yt3x#1500). Until it does, build and publish the Windows binary here. Replaces upstream's release.yml: that one fires on every tag and then dies in its container job on GHCR secrets this fork does not have. Packages the FLAT layout of the official archive - video2x.exe, the DLLs and models/ at the root - because Video2X looks for its models next to the executable and the installer looks for the exe at the root of what it unpacks. Ships LICENSE, NOTICE and MODIFICATIONS.md inside the archive. Upstream's zip ships no licence text at all; AGPL 5(a) and 6 still apply. The release notes name the Corresponding Source of every component, including the bundled FFmpeg DLLs, which are a GPL build.
FortunaCournot
added a commit
to FortunaCournot/video2x
that referenced
this pull request
Jul 14, 2026
The VR we are installer needs a Video2X whose exit code means something. Upstream segfaults on exit on every run, and has not released the fix (k4yt3x#1500). Until it does, build and publish the Windows binary here. Replaces upstream's release.yml: that one fires on every tag and then dies in its container job on GHCR secrets this fork does not have. Packages the FLAT layout of the official archive - video2x.exe, the DLLs and models/ at the root - because Video2X looks for its models next to the executable and the installer looks for the exe at the root of what it unpacks. Ships LICENSE, NOTICE and MODIFICATIONS.md inside the archive. Upstream's zip ships no licence text at all; AGPL 5(a) and 6 still apply. The release notes name the Corresponding Source of every component, including the bundled FFmpeg DLLs, which are a GPL build.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
video2xalways terminates with a segmentation fault — even when the run succeeds. It printsVideo processed successfully, writes its complete processing summary, and only then dies:A successful run and a failed one are therefore indistinguishable by exit code, so every script driving video2x has to ignore the exit code and inspect the output file instead. That is a bad contract for a CLI tool.
Cause
Nothing ever destroys ncnn's Vulkan instance on purpose. ncnn creates it lazily on first use and only tears it down from its static
__ncnn_vulkan_instance_holderdestructor plus anatexit()handler it registers itself.On Windows that is too late. ncnn is a shared library here —
CMakeLists.txtsays "Use the pre-built shared ncnn library on Windows", andbuild.ymlfetchesncnn-*-windows-vs2022-shared.zip— so both of those run while the loader is already unloading DLLs at process exit. Calling into another DLL at that point is documented as unsafe, and the teardown does exactly that:vkDeviceWaitIdle(),vkDestroyInstance()andglslang::FinalizeProcess().The access violation therefore lands after
main()has returned, which is also why Windows Error Reporting never logs it — it happens inside the loader teardown.ncnn's author is aware of the hazard.
gpu.cppcarries this comment next to theatexit()call:and his own reference tools (
rife-ncnn-vulkan,realesrgan-ncnn-vulkan) calldestroy_gpu_instance()explicitly frommain()rather than relying on that teardown.The fix
Do the same.
libvideo2xgainsvideo2x::release_gpu_resources(), and the CLI calls it from an RAII guard declared as the first local inmain()— so it fires on every exit path, and is destroyed after theVideoProcessor: the processor's Vulkan buffers must be gone before the instance they came from is.destroy_gpu_instance()returns immediately when no instance was ever created, so this is a no-op for the libplacebo path, for--help, and for a failed argument parse. ncnn's ownatexithandler still runs afterwards and finds nothing to do.Evidence
I built two Windows Release binaries from this workflow whose only difference is the 64-line fix, and ran both on the same clip:
-p rife -m 2)-p realesrgan -s 2)6.4.0139,139,1391390,0,00The produced videos are byte-identical (same MD5) — the fix changes the exit code and nothing else.
About the first three commits
They are not padding: the Windows job cannot currently build at all, so without them this PR would arrive with red CI and the fix could not be verified by anyone.
setup-vulkan-sdk@v1.2.0pullsactions/cache@v2, which GitHub now auto-fails → bumped tov1.2.1.1.3.204.0(2021) declarescmake_minimum_requiredbelow 3.5, which CMake 4 — now onwindows-latest— refuses outright → bumped to1.4.313.0.CMakeLists.txtspells out the Boost DLL asvc143;windows-latestnow builds it asvc145, soinstallfails after everything has compiled and linked → matched by pattern instead.Happy to split those into a separate PR if you would rather review them apart.
Caveat
Tested on Windows 11 with an RTX 5070 Ti. I have no stack trace from a debug build — the diagnosis rests on the observed behaviour, on the source, and on the control experiment above. Corrections welcome.