Skip to content

libkernel: ccleste stalls after Vulkan device ready due to redirected stderr (fixed by #4536) #4664

Closed

Description

@Roojool

Resolved by merged #4536; original diagnosis corrected.

The stall was caused by the guest stderr-capture pipe feeding sceKernelDebugOutText back into itself, eventually blocking stderr writers during Vulkan construction. It was not an established guest-memory mirror/import lock cycle. The original knob table and raw stack scans did not prove that mechanism.

Local Windows 11 / Radeon 780M verification: ccleste on G09's saved bde332a59 runtime hangs before #4536; replacing only libkernel with that baseline plus the two merged #4536 commits creates the swapchain and keeps logging GPU submissions through 45 seconds with no APS5_* environment variables. AGC and the other libraries were identical in both arms. This is a check of #4536, not a full latest-main title run; RommPS was not rerun locally. See the resolution comment for actual output excerpts and limitations.

Original investigation (historical; causal claims withdrawn)

ccleste (PPSA59322, open-source MIT homebrew) deadlocks deterministically on main after Vulkan device ready: the VulkanDevice constructor stalls between the guest-memory mirror/import setup steps, wedging sceVideoOutOpen and the whole title — the "stops after Vulkan device ready with no swapchain" symptom from the 4th catalog run in #1578.

Reproduction (deterministic, every run)

Title: ccleste 1.00 (PPSA59322), built from https://github.com/darkxex/PS5_Celeste (MIT); catalog artifact sha256 96861b81...2fb43 verified against the catalog. eboot + sce_module/libc.prx unwrapped (ps5-payload-dev fSELF layout; two non-load segments zero-filled), relinked with the relinker built from the same commit:

relinker.exe --trap-sdk-syscall-stubs --windows run/eboot.elf out/eboot.exe   -> exit 0, 352 NID references

Run against the libraries built from main at 9aa5fd290007d816bfed6970ee259f9baaa0e24e, then:

[src/VulkanDevice.cpp:1570 VulkanDevice] Vulkan device ready device=0xa412ab0 queue=0xa4d9d40 family=0

…then nothing, forever. Verified hung at 150 s and again at 600 s with flat private bytes (253–263 MB) and a shrinking working set (255→43 MB): a true deadlock, not slow progress, not a crash (every APS5 log line flushes per line).

Environment: Windows 11, AMD Radeon 780M (Vulkan 1.1), WinLibs GCC 15.2, Release.

What it is not

  • The title's port flow is correct: window is non-null, Presentation window ... extent=1920x1080 and Vulkan surface created are logged before the stall.
  • No thread is inside an AMD Vulkan-driver call at the stall; the constructing thread parks on AnyPS5's own sync objects, with guest addresses (0x2_00914000 region) and mirror-block structures on its stack.
  • The guest's main thread is in sceVideoOutOpen waiting for the started promise that VideoOutDriver's present thread only sets after AgcDriverAttachWindow → VulkanDevice ctor returns (VideoOutDriver.cpp:555-565, ctor wait at :277) — so any ctor stall produces exactly this symptom with no error line.

Where it is

The silent window is the constructor's own setup between VulkanDevice.cpp:1571 and :1607 — the guest-memory host-import + image-mirror + parallel-refresh machinery (BufferPool, PipelineCache, TextureDetiler, GpuColorTransfer, PlaceholderImages/GpuMutex, Recorder::Activate).

Knob bisect on the same relinked title:

environment result
default deadlock after Vulkan device ready
APS5_NO_HOST_IMPORT=1 only deadlock
APS5_NO_LEASE_MIRROR=1 only deadlock
APS5_NO_SPARSE_HEAP_MIRROR=1 only deadlock
APS5_NO_PARALLEL_MIRROR_REFRESH=1 only deadlock
all four together Surface capabilities ... + Swapchain created swapchain=... images=2

So the cycle spans several guest-memory features interacting during construction, not one bad call site. The exact two-lock cycle is not yet named; stack captures of the parked threads are in the run logs below.

RommPS (PPSA76677) shows the same catalog symptom but was not re-run here; the artifact is catalog-verified and the same reproduction applies.

Evidence files

Full run log with commands and exit codes, the relink log, per-knob run outputs, and thread-stack captures: can be attached on request (the run receipt cites the baseline sha in its header).

AI-assisted: yes.

Activity

  1. Joekrry commented on Oct 12, 2026

    @Joekrry

    Hi,

    I traced the exact code path.

    The four env knobs you bisect (APS5_NO_HOST_IMPORT, APS5_NO_LEASE_MIRROR, APS5_NO_SPARSE_HEAP_MIRROR, APS5_NO_PARALLEL_MIRROR_REFRESH) all gate the host-import/image-mirror machinery, and that only runs once a guest texture or buffer is bound in a draw.

    In the constructor window you point to (VulkanDevice.cpp, between "Vulkan device ready" and swapchain creation), none of the code that actually runs there, BufferPool, PipelineCache, TextureDetiler, TextureCache, GpuColorTransfer, DescriptorCache, PlaceholderImages, touches guest memory, host imports, or mirrors at all. PlaceholderImages just creates 1x1 device-local images and clears them via a GPU submit/wait. And per your own repro, the guest's main thread is still parked in sceVideoOutOpen = no draw has happened yet.

    I also checked GuestMemory::GpuMutex (correctly recursive) and RefreshPool's helper threads and no lock-ordering cycle there either.

    The bisection table doesn't line up with code that can actually run in the window described, so I can't locate the deadlock.

    Can you attach the actual run log and stack captures?

  2. orishamir commented on Oct 12, 2026

    @orishamir
    Contributor

    This is the stall #4536 fixes. It merged at 18:31Z (21c27e6); main 9aa5fd2 above is from before it.

    The thread stacks are in #4536: the title's log reader thread is in sceKernelDebugOutText → fprintf → WriteFile, which does not return, and the thread constructing the VulkanDevice waits for the stderr lock in fprintf, called from Graphics::PrepareImportWatch (VulkanDevice.cpp:1598; on Windows that write is the [write-watch] Windows direct host imports use comparisons ... line at GuestBufferMemory.cpp:524). ccleste and RommPS move their stderr into a pipe with dup2(pipe_w, 2) and forward what they read from it to sceKernelDebugOutText, which wrote to host stderr, i.e. back into the same pipe, until the pipe was full.

    On current main cf4557e (with #4536), Windows 11, RTX 4080, WinLibs GCC 15.2.0 r7, Release, relinked with --trap-sdk-syscall-stubs, no APS5_* variables set, 60 s each:

    • ccleste (PPSA59322): Vulkan device ready, Surface capabilities ..., Swapchain created ... images=2; still running at 60 s; the frames saved every 300 presents show its title screen (logo, credits, "PS5 PORT", falling snow).
    • RommPS (PPSA76677): the same three lines; still running at 60 s; it draws its own "RomM Sync isn't running" screen (its payload loader on port 9021 refuses the connection here).

    The [write-watch] line now appears once in each title's output, forwarded by the title's reader: [debug:0] [ccleste] [write-watch] Windows direct host imports use comparisons; separate shared aliases retain guest write tracking.

    AI-assisted: yes (Claude Code Opus 5.5 xhigh)

  3. changed the title [-]libSceAgcDriver: VulkanDevice constructor deadlocks in guest-memory mirror/import setup after 'Vulkan device ready' (ccleste PPSA59322, deterministic)[/-] [+]libkernel: ccleste stalls after Vulkan device ready due to redirected stderr (fixed by #4536)[/+] on Oct 12, 2026
  4. Roojool commented on Oct 12, 2026

    @Roojool
    ContributorAuthor

    Confirmed locally on Windows 11 / Radeon 780M. The original mirror/import lock-cycle diagnosis was incorrect; I withdraw it. The single-knob/all-knob outcomes did not establish that mechanism, and the original raw stack scans were not symbolic backtraces. Sorry for the misleading attribution.

    I reused G09's runtime built from bde332a59ce98ce8aa776b26a4ed04e53417946d, including its diagnostic AGC binary, and ran ccleste (PPSA59322) with no APS5_* variables:

    Actual output excerpts:

    [src\VulkanDevice.cpp:1588 VulkanDevice] Vulkan device ready device=0000000009f94180 queue=0000000009f9f0d0 family=0
    [src\VulkanDevice.cpp:1645 VulkanDevice] Surface capabilities drawable=1152x648 min=1152x648 max=1152x648 minImages=2 maxImages=16 usage=0x9f
    [src\VulkanDevice.cpp:1689 VulkanDevice] Swapchain created swapchain=000000000a1d91f0 extent=1152x648 images=2
    [debug:0] [ccleste] [write-watch] Windows direct host imports use comparisons; separate shared aliases retain guest write tracking
    [debug:0] [ccleste] radv/ps5 submissions: window count=355 kwords=340 claim_ms=0.6 copy_ms=3.1 flush_ms=0.2 agc_ms=17.3 suspend_ms=5.5
    [debug:0] [ccleste] radv/ps5 submissions: window count=599 kwords=581 claim_ms=1.4 copy_ms=6.7 flush_ms=0.4 agc_ms=33.3 suspend_ms=3.3
    [debug:0] [ccleste] radv/ps5 submissions: window count=600 kwords=582 claim_ms=1.2 copy_ms=5.8 flush_ms=0.3 agc_ms=28.7 suspend_ms=3.0
    [debug:0] [ccleste] radv/ps5 submissions: window count=599 kwords=581 claim_ms=1.4 copy_ms=7.0 flush_ms=0.4 agc_ms=34.1 suspend_ms=3.3
    

    This is a before/after check of the existing merged fix on the saved runtime, not a full latest-main title run. I did not rerun RommPS. The initial attempt to substitute a much newer kernel into the older runtime failed at DLL loading and was excluded from the comparison.

    The evidence agrees with @orishamir's explanation: the title's stderr-capture reader fed sceKernelDebugOutText back into its own pipe. #4536 breaks that loop by preserving host stderr. Thanks @Joekrry for questioning the constructor-path claim and @orishamir for the exact diagnosis and verification. Closing as already fixed by #4536; no separate G09 fix PR.

    AI-assisted: yes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions