Repository navigation
Standalone Python process remains alive after SimulationApp.close() returns (Isaac Sim 5.1.0.0) #877
Description
Summary
A bounded signal-free standalone Python run using Isaac Sim 5.1.0.0 / Kit 107.3.3 returned from the project wrapper's IsaacApp.close(), persisted its final report, and returned 0 from worker main. The child process nevertheless remained alive until the parent deadline and was terminated for cleanup. Natural process exit was not observed; the cause is unresolved. This report does not claim an SDK defect.
No task-specific USD scene, robot, camera, or dataset was created. The SDK default startup created an empty stage. This reduced run shows that camera construction is not necessary for this observed process-survival symptom; it does not prove the earlier camera qualification had the same cause.
Environment and launch
- Isaac Sim distribution/application:
5.1.0.0/5.1.0 - Kit:
107.3.3+production.229672.69cbf6ad.gl - CPython
3.11.15; Ubuntu22.04.5 LTS, kernel6.8.0-138-generic - RTX 4080 SUPER, driver
580.178.04, Vulkan - Headless standalone launch;
fast_shutdown=False; child-onlyOMNICLIENT_HUB_MODE=disabled; working directory set to repository root - The run used the pinned bootstrap and worker linked below. The short code sketch is an illustration of the measured path, not a separately tested SDK-only reproducer.
# Illustrative sketch of the measured project-wrapper path; not independently tested.
from navfm.isaac.app import IsaacApp, SimConfig
app = IsaacApp(SimConfig(headless=True), fast_shutdown=False)
app.start()
app.close()Observed event sequence
| Event | Elapsed seconds |
|---|---|
Project IsaacApp wrapper constructor returned |
0.025 |
IsaacApp.start() returned |
5.756 |
IsaacApp.close() entered |
5.761 |
IsaacApp.close() returned |
35.810 |
| Final worker report persisted | 35.819 |
Worker main returned 0 |
35.821 |
| Parent deadline reached | 59.998 |
| Cleanup SIGTERM requested | 60.001 |
Child reaped with exit -15 |
60.167 |
IsaacApp.__init__ is the project wrapper constructor, not the SDK SimulationApp constructor. SDK construction occurs inside IsaacApp.start() and was not separately bracketed. Worker return, process exit, and SDK cleanup are distinct; natural process exit and complete SDK cleanup were not observed.
Sanitized startup excerpts:
| Driver Version: 580.178.04 | Graphics API: Vulkan
| 0 | NVIDIA GeForce RTX 4080 SUPER | ...
| OS: 22.04.5 LTS (Jammy Jellyfish) ubuntu, Version: 22.04.5, Kernel: 6.8.0-138-generic
[5.757s] Simulation App Shutting Down
Immutable source and evidence references
- Full prepared issue package
- Project
IsaacAppwrapper — SHA-25627a94026c4b6a370971a34f5e7d7ee8674aebcf4aa97e2c96299d62df6694b87 - Signal-free worker — SHA-256
c45a3f6776c2083c722c9b09a563becb340f37a7a804e3ea5d65ca14c80a0281 - Bootstrap — SHA-256
7a6c0cd80a45edfc4baa7bf397525779af34be28e7f509228c395daf112f8bf4 - Frozen launch configuration — SHA-256
33869a56df6a59ae19b09f1d0e9223720d5c36b34de596a91d1dcf8976981d8d - Signal-free supervisor — SHA-256
3b820bf0739b831b2b140aef895997d19794b6c32d0fdc7a3fc34ef1c2abed6a - Pinned measured result
The sanitized evidence identities and SDK-source hash are recorded in the issue package. Raw logs and runtime evidence are not attached or publicly linked; they remain available locally if a support engineer requests a specific artifact.
Questions
- Is natural process exit expected after
SimulationApp.close()returns withfast_shutdown=Falsein this standalone 5.1.0.0 / Kit build? - Is there a supported shutdown or cleanup sequence for a child where
close()returns and worker main returns but the process remains alive? - Which specific diagnostic artifact or additional observation would best distinguish Python interpreter shutdown from native Kit/framework teardown or another remaining process-lifetime condition?
No workaround is proposed. The run did not test camera qualification, navigation, or a task-specific scene.
Activity
vick-yu commentedon Oct 13, 2026
Thanks for the careful writeup and the immutable evidence package -- the
fsync-backed lifecycle markers make this much easier to analyze than the
typical "my script hangs" report.
To answer your three questions directly:
1. Is natural process exit expected after
SimulationApp.close()withfast_shutdown=False?Not guaranteed in Isaac Sim 5.1.0, and this is a known architectural limitation
of that version.
In 5.1.0,
close()withfast_shutdown=Falsecallsshutdown_and_release_framework(), which performs extension-by-extensionteardown and then returns control to Python. After that point, CPython's
interpreter shutdown proceeds normally -- but native Kit and Carbonite worker
threads (the tasking pool, renderer threads, etc.) are not joined by the
Python side before returning. These are non-daemon C threads. CPython's
atexitandthreadingcleanup wait for daemon threads, but non-daemonnative C threads that are not tracked by Python's
threadingmodule are leftto finish on their own schedule.
In your measurement, the process survived ~24 seconds after
main()returned.That interval corresponds to Kit's native teardown completing independently of
the Python runtime -- this is the framework cleaning up after itself, but doing
so asynchronously from Python's perspective.
The 5.1 documentation describes the option as:
"Shutdown each extension" refers to the extension teardown sequence, not a
guarantee that the process exits immediately after
close()returns.2. Is there a supported cleanup or shutdown sequence for this case?
Yes. The supported workaround in 5.1.0 is to use
fast_shutdown=True(the default) if bounded process exit is a hard requirement for your supervisor.
With
fast_shutdown=True, Kit callsquickReleaseFrameworkAndTerminateinternally, which terminates the process immediately after the framework is
released -- your supervisor will see a clean exit within seconds.
If you need graceful extension teardown and bounded process exit, the
cleanest approach in 5.1.0 is to add an explicit
os._exit(0)afterclose()returns:os._exit()bypasses CPython'satexithandlers and terminates immediately,which is safe after
close()has already finished its cleanup sequence.Note for Isaac Sim 6.0.0 and later: this behavior is improved. The 6.0+
SimulationAppswitches fromshutdown_and_release_framework()toapp.shutdown()(which handlesfast_shutdowninternally and avoids a GILdeadlock that could occur in the older path). It also registers an
atexithandler so the SDK can clean up even if
close()is never called explicitly.If you are able to move to 6.0.0 or later, the post-close process-survival
window is substantially reduced even with
fast_shutdown=False.3. Which artifact would best distinguish the remaining process-lifetime condition?
A
strace -p <pid> -e trace=futex,nanosleep,epoll_wait -fsnapshot taken inthe ~24-second window between
main()returning and process exit would showwhich syscalls the surviving native threads are blocked on. If you see
futexwaits with a consistent caller (e.g.,
carb.taskingthread joining its pool),that pinpoints the specific subsystem keeping the process alive.
Alternatively, attaching
gdb -p <pid>and runninginfo threadsfollowedby
thread apply all btat that moment would give the full native threadbacktraces and identify any threads that have not yet exited.
In practice, for production use, the
os._exit(0)pattern orfast_shutdown=Trueis the pragmatic solution rather than diagnosing the specific native teardown
sequencing.
The issue package you provided is thorough and the observation is accurate.
This is a real limitation of the 5.1.0 teardown path, not a user error.
vick-yu commentedon Oct 27, 2026
Hi @FrankAsanteVanLaarhoven — thanks again for the exceptionally thorough writeup and the immutable evidence package. We haven't heard back since our reply explaining the non-daemon native thread behavior and the available workarounds (
fast_shutdown=True,sys.exit()afterclose()). We'll go ahead and close this out. If you're still hitting this or have follow-up findings, you can reopen it directly via the 'Reopen issue' button, or open a new issue for 6.0.0+ referencing this one. Thanks for contributing such a well-documented report!