From 953b9ded30d3133422991bf18a5c04b1940390b4 Mon Sep 17 00:00:00 2001 From: Siyuan Date: Thu, 13 Nov 2025 15:43:43 +0000 Subject: [PATCH] feat: simplify failsafe mode for native proxy --- failsafe.go | 4 +- internal/native/cgo_linux.go | 122 +-------------------------------- internal/native/empty.go | 111 ++++++++++++++++++++++++++++++ internal/native/grpc_client.go | 2 +- internal/native/native.go | 9 --- internal/native/proxy.go | 14 ++-- internal/native/server.go | 13 ++++ native.go | 7 +- 8 files changed, 145 insertions(+), 137 deletions(-) create mode 100644 internal/native/empty.go diff --git a/failsafe.go b/failsafe.go index 3c6b3d3a..14e7bbd5 100644 --- a/failsafe.go +++ b/failsafe.go @@ -77,10 +77,12 @@ func checkFailsafeReason() { _ = os.Remove(lastCrashPath) // TODO: read the goroutine stack trace and check which goroutine is panicking + failsafeModeActive = true if strings.Contains(failsafeCrashLog, "runtime.cgocall") { - failsafeModeActive = true failsafeModeReason = "video" return + } else { + failsafeModeReason = "unknown" } }) } diff --git a/internal/native/cgo_linux.go b/internal/native/cgo_linux.go index be1a5a36..b33eb534 100644 --- a/internal/native/cgo_linux.go +++ b/internal/native/cgo_linux.go @@ -50,17 +50,9 @@ static inline void jetkvm_cgo_setup_rpc_handler() { import "C" var ( - cgoLock sync.Mutex - cgoDisabled bool + cgoLock sync.Mutex ) -func setCgoDisabled(disabled bool) { - cgoLock.Lock() - defer cgoLock.Unlock() - - cgoDisabled = disabled -} - //export jetkvm_go_video_state_handler func jetkvm_go_video_state_handler(state *C.jetkvm_video_state_t) { videoState := VideoState{ @@ -104,10 +96,6 @@ func jetkvm_go_rpc_handler(method *C.cchar_t, params *C.cchar_t) { var eventCodeToNameMap = map[int]string{} func uiEventCodeToName(code int) string { - if cgoDisabled { - return "" - } - name, ok := eventCodeToNameMap[code] if !ok { cCode := C.int(code) @@ -120,10 +108,6 @@ func uiEventCodeToName(code int) string { } func setUpNativeHandlers() { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -135,10 +119,6 @@ func setUpNativeHandlers() { } func uiInit(rotation uint16) { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -148,10 +128,6 @@ func uiInit(rotation uint16) { } func uiTick() { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -159,10 +135,6 @@ func uiTick() { } func videoInit(factor float64) error { - if cgoDisabled { - return nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -176,10 +148,6 @@ func videoInit(factor float64) error { } func videoShutdown() { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -187,10 +155,6 @@ func videoShutdown() { } func videoStart() { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -198,10 +162,6 @@ func videoStart() { } func videoStop() { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -209,10 +169,6 @@ func videoStop() { } func videoLogStatus() string { - if cgoDisabled { - return "" - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -223,10 +179,6 @@ func videoLogStatus() string { } func uiSetVar(name string, value string) { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -240,10 +192,6 @@ func uiSetVar(name string, value string) { } func uiGetVar(name string) string { - if cgoDisabled { - return "" - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -254,10 +202,6 @@ func uiGetVar(name string) string { } func uiSwitchToScreen(screen string) { - if cgoDisabled { - return - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -267,10 +211,6 @@ func uiSwitchToScreen(screen string) { } func uiGetCurrentScreen() string { - if cgoDisabled { - return "" - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -279,10 +219,6 @@ func uiGetCurrentScreen() string { } func uiObjAddState(objName string, state string) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -295,10 +231,6 @@ func uiObjAddState(objName string, state string) (bool, error) { } func uiObjClearState(objName string, state string) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -311,10 +243,6 @@ func uiObjClearState(objName string, state string) (bool, error) { } func uiGetLVGLVersion() string { - if cgoDisabled { - return "" - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -323,10 +251,6 @@ func uiGetLVGLVersion() string { // TODO: use Enum instead of string but it's not a hot path and performance is not a concern now func uiObjAddFlag(objName string, flag string) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -339,10 +263,6 @@ func uiObjAddFlag(objName string, flag string) (bool, error) { } func uiObjClearFlag(objName string, flag string) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -363,10 +283,6 @@ func uiObjShow(objName string) (bool, error) { } func uiObjSetOpacity(objName string, opacity int) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -378,10 +294,6 @@ func uiObjSetOpacity(objName string, opacity int) (bool, error) { } func uiObjFadeIn(objName string, duration uint32) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -394,10 +306,6 @@ func uiObjFadeIn(objName string, duration uint32) (bool, error) { } func uiObjFadeOut(objName string, duration uint32) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -410,10 +318,6 @@ func uiObjFadeOut(objName string, duration uint32) (bool, error) { } func uiLabelSetText(objName string, text string) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -431,10 +335,6 @@ func uiLabelSetText(objName string, text string) (bool, error) { } func uiImgSetSrc(objName string, src string) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -450,10 +350,6 @@ func uiImgSetSrc(objName string, src string) (bool, error) { } func uiDispSetRotation(rotation uint16) (bool, error) { - if cgoDisabled { - return false, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -466,10 +362,6 @@ func uiDispSetRotation(rotation uint16) (bool, error) { } func videoGetStreamQualityFactor() (float64, error) { - if cgoDisabled { - return 0, nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -478,10 +370,6 @@ func videoGetStreamQualityFactor() (float64, error) { } func videoSetStreamQualityFactor(factor float64) error { - if cgoDisabled { - return nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -490,10 +378,6 @@ func videoSetStreamQualityFactor(factor float64) error { } func videoGetEDID() (string, error) { - if cgoDisabled { - return "", nil - } - cgoLock.Lock() defer cgoLock.Unlock() @@ -502,10 +386,6 @@ func videoGetEDID() (string, error) { } func videoSetEDID(edid string) error { - if cgoDisabled { - return nil - } - cgoLock.Lock() defer cgoLock.Unlock() diff --git a/internal/native/empty.go b/internal/native/empty.go new file mode 100644 index 00000000..846cf9ae --- /dev/null +++ b/internal/native/empty.go @@ -0,0 +1,111 @@ +package native + +type EmptyNativeInterface struct { +} + +func (e *EmptyNativeInterface) Start() error { return nil } + +func (e *EmptyNativeInterface) VideoSetSleepMode(enabled bool) error { return nil } + +func (e *EmptyNativeInterface) VideoGetSleepMode() (bool, error) { return false, nil } + +func (e *EmptyNativeInterface) VideoSleepModeSupported() bool { + return false +} + +func (e *EmptyNativeInterface) VideoSetQualityFactor(factor float64) error { + return nil +} + +func (e *EmptyNativeInterface) VideoGetQualityFactor() (float64, error) { + return 0, nil +} + +func (e *EmptyNativeInterface) VideoSetEDID(edid string) error { + return nil +} + +func (e *EmptyNativeInterface) VideoGetEDID() (string, error) { + return "", nil +} + +func (e *EmptyNativeInterface) VideoLogStatus() (string, error) { + return "", nil +} + +func (e *EmptyNativeInterface) VideoStop() error { + return nil +} + +func (e *EmptyNativeInterface) VideoStart() error { + return nil +} + +func (e *EmptyNativeInterface) GetLVGLVersion() (string, error) { + return "", nil +} + +func (e *EmptyNativeInterface) UIObjHide(objName string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjShow(objName string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UISetVar(name string, value string) { +} + +func (e *EmptyNativeInterface) UIGetVar(name string) string { + return "" +} + +func (e *EmptyNativeInterface) UIObjAddState(objName string, state string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjClearState(objName string, state string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjAddFlag(objName string, flag string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjClearFlag(objName string, flag string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjSetOpacity(objName string, opacity int) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjFadeIn(objName string, duration uint32) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjFadeOut(objName string, duration uint32) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjSetLabelText(objName string, text string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UIObjSetImageSrc(objName string, image string) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) DisplaySetRotation(rotation uint16) (bool, error) { + return false, nil +} + +func (e *EmptyNativeInterface) UpdateLabelIfChanged(objName string, newText string) {} + +func (e *EmptyNativeInterface) UpdateLabelAndChangeVisibility(objName string, newText string) {} + +func (e *EmptyNativeInterface) SwitchToScreenIf(screenName string, shouldSwitch []string) {} + +func (e *EmptyNativeInterface) SwitchToScreenIfDifferent(screenName string) {} + +func (e *EmptyNativeInterface) DoNotUseThisIsForCrashTestingOnly() {} diff --git a/internal/native/grpc_client.go b/internal/native/grpc_client.go index 772fb839..426fde8d 100644 --- a/internal/native/grpc_client.go +++ b/internal/native/grpc_client.go @@ -131,7 +131,7 @@ func (c *GRPCClient) startEventStream() { stream, err := c.client.StreamEvents(ctx, &pb.Empty{}) if err != nil { c.logger.Warn().Err(err).Msg("failed to start event stream, retrying ...") - time.Sleep(1 * time.Second) + time.Sleep(5 * time.Second) continue } diff --git a/internal/native/native.go b/internal/native/native.go index 770da675..5d22361f 100644 --- a/internal/native/native.go +++ b/internal/native/native.go @@ -9,7 +9,6 @@ import ( ) type Native struct { - disable bool ready chan struct{} l *zerolog.Logger lD *zerolog.Logger @@ -28,7 +27,6 @@ type Native struct { } type NativeOptions struct { - Disable bool SystemVersion *semver.Version AppVersion *semver.Version DisplayRotation uint16 @@ -81,7 +79,6 @@ func NewNative(opts NativeOptions) *Native { } return &Native{ - disable: opts.Disable, ready: make(chan struct{}), l: &nativeSubLogger, lD: &displaySubLogger, @@ -100,12 +97,6 @@ func NewNative(opts NativeOptions) *Native { } func (n *Native) Start() error { - if n.disable { - nativeLogger.Warn().Msg("native is disabled, skipping initialization") - setCgoDisabled(true) - return nil - } - // set up singleton setInstance(n) setUpNativeHandlers() diff --git a/internal/native/proxy.go b/internal/native/proxy.go index 3057a83f..5ba33726 100644 --- a/internal/native/proxy.go +++ b/internal/native/proxy.go @@ -61,7 +61,6 @@ func (n *NativeOptions) toProxyOptions() *nativeProxyOptions { maxRestartAttempts = n.MaxRestartAttempts } return &nativeProxyOptions{ - Disable: n.Disable, SystemVersion: n.SystemVersion, AppVersion: n.AppVersion, DisplayRotation: n.DisplayRotation, @@ -78,7 +77,6 @@ func (n *NativeOptions) toProxyOptions() *nativeProxyOptions { func (p *nativeProxyOptions) toNativeOptions() *NativeOptions { return &NativeOptions{ - Disable: p.Disable, SystemVersion: p.SystemVersion, AppVersion: p.AppVersion, DisplayRotation: p.DisplayRotation, @@ -135,7 +133,6 @@ type NativeProxy struct { // NewNativeProxy creates a new NativeProxy that spawns a separate process func NewNativeProxy(opts NativeOptions) (*NativeProxy, error) { proxyOptions := opts.toProxyOptions() - proxyOptions.CtrlUnixSocket = fmt.Sprintf("jetkvm/native/grpc/%s", randomId(4)) proxyOptions.VideoStreamUnixSocket = fmt.Sprintf("@jetkvm/native/video-stream/%s", randomId(4)) // Get the current executable path to spawn itself @@ -211,6 +208,12 @@ func (w *nativeProxyStdoutHandler) Write(p []byte) (n int, err error) { } func (p *NativeProxy) toProcessCommand() (*cmdWrapper, error) { + // generate a new random ID for the gRPC socket on each restart + // sometimes the socket is not closed properly when the process exits + // this is a workaround to avoid the issue + p.nativeUnixSocket = fmt.Sprintf("jetkvm/native/grpc/%s", randomId(4)) + p.options.CtrlUnixSocket = p.nativeUnixSocket + envArgs, err := utils.MarshalEnv(p.options) if err != nil { return nil, fmt.Errorf("failed to marshal environment variables: %w", err) @@ -407,7 +410,10 @@ func (p *NativeProxy) restartProcess() error { // Close old client if p.client != nil { - _ = p.client.Close() + if err := p.client.Close(); err != nil { + p.logger.Warn().Err(err).Msg("failed to close gRPC client") + } + p.client = nil // set to nil to avoid closing it again } p.restarts++ diff --git a/internal/native/server.go b/internal/native/server.go index dd660e73..a2835f19 100644 --- a/internal/native/server.go +++ b/internal/native/server.go @@ -85,6 +85,19 @@ func RunNativeProcess(binaryName string) { logger.Fatal().Err(err).Msg("failed to write handshake message to stdout") } + go func() { + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGHUP) + + // non-blocking receive + select { + case <-sigChan: + logger.Info().Msg("received SIGHUP signal, emulating crash") + nativeInstance.DoNotUseThisIsForCrashTestingOnly() + default: + } + }() + // Set up signal handling sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) diff --git a/native.go b/native.go index 0b1e3569..b51aa9b6 100644 --- a/native.go +++ b/native.go @@ -16,10 +16,15 @@ var ( ) func initNative(systemVersion *semver.Version, appVersion *semver.Version) { + if failsafeModeActive { + nativeInstance = &native.EmptyNativeInterface{} + nativeLogger.Warn().Msg("failsafe mode active, using empty native interface") + return + } + nativeLogger.Info().Msg("initializing native proxy") var err error nativeInstance, err = native.NewNativeProxy(native.NativeOptions{ - Disable: failsafeModeActive, SystemVersion: systemVersion, AppVersion: appVersion, DisplayRotation: config.GetDisplayRotation(),