Summary
Calling Helm.install() (or upgrade --install when the release doesn't exist yet) permanently replaces the host JVM's OS-level signal handlers for SIGINT/SIGTERM/SIGQUIT, leaving the JVM unable to react to SIGTERM afterwards.
Details
The native binding is a Go c-shared library. The install path calls Go's signal.Notify for SIGINT/SIGTERM/SIGQUIT in native/internal/helm/install.go. The first such call makes the in-process Go runtime install its own OS-level signal handlers, replacing the JVM's handlers, and they are never restored.
After that, the embedding JVM no longer responds to SIGTERM: shutdown hooks don't run and graceful shutdown (e.g. Quarkus/Spring) never happens. In Kubernetes this is particularly painful — rollouts hang on pod termination until the kubelet SIGKILLs the pod after terminationGracePeriodSeconds.
Environment
- helm-java 0.0.20
- Linux (JVM embedding, observed in Kubernetes pods)
Steps to reproduce
- From a JVM process, call
Helm.install(...) (or upgrade with install enabled for a non-existent release) so the Go code path reaches signal.Notify.
- Send SIGTERM to the JVM process.
- The process ignores it (Go runtime forwards the signal to a channel nobody in the library context consumes for termination); the JVM's default terminate-on-SIGTERM behavior and shutdown hooks never run.
Suggested fix
Avoid signal.Notify in the library/c-shared context (the interrupt-handling it enables isn't reachable for embedders anyway), or call signal.Reset/signal.Stop after the operation completes so the previous handlers are restored.
Workaround
We currently re-register JVM handlers via sun.misc.Signal.handle after each native call that can hit the install path, restoring exit-on-signal semantics (exit code 128+n, which runs JVM shutdown hooks).
Summary
Calling
Helm.install()(orupgrade --installwhen the release doesn't exist yet) permanently replaces the host JVM's OS-level signal handlers for SIGINT/SIGTERM/SIGQUIT, leaving the JVM unable to react to SIGTERM afterwards.Details
The native binding is a Go c-shared library. The install path calls Go's
signal.Notifyfor SIGINT/SIGTERM/SIGQUIT innative/internal/helm/install.go. The first such call makes the in-process Go runtime install its own OS-level signal handlers, replacing the JVM's handlers, and they are never restored.After that, the embedding JVM no longer responds to SIGTERM: shutdown hooks don't run and graceful shutdown (e.g. Quarkus/Spring) never happens. In Kubernetes this is particularly painful — rollouts hang on pod termination until the kubelet SIGKILLs the pod after
terminationGracePeriodSeconds.Environment
Steps to reproduce
Helm.install(...)(orupgradewith install enabled for a non-existent release) so the Go code path reachessignal.Notify.Suggested fix
Avoid
signal.Notifyin the library/c-shared context (the interrupt-handling it enables isn't reachable for embedders anyway), or callsignal.Reset/signal.Stopafter the operation completes so the previous handlers are restored.Workaround
We currently re-register JVM handlers via
sun.misc.Signal.handleafter each native call that can hit the install path, restoring exit-on-signal semantics (exit code 128+n, which runs JVM shutdown hooks).