feat: simplify failsafe mode for native proxy

This commit is contained in:
Siyuan 2025-11-13 15:43:43 +00:00
parent 21641ffda5
commit 953b9ded30
8 changed files with 145 additions and 137 deletions

View File

@ -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"
}
})
}

View File

@ -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()

111
internal/native/empty.go Normal file
View File

@ -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() {}

View File

@ -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
}

View File

@ -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()

View File

@ -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++

View File

@ -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)

View File

@ -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(),