Release 3.8.2.5: cheaper idle UI poll and tighter hot paths.
Delta getState by rev, pause when hidden, cancellable background loops, hostCache bounds, unlock PollUI around engine status, corebin singleflight, PingAll worker pool. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+91
-19
@@ -3,6 +3,7 @@ package apphost
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -39,6 +40,8 @@ type App struct {
|
||||
OpenURL func(string) error
|
||||
OnAfterUpdate func()
|
||||
APIToken string
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
type UIState struct {
|
||||
@@ -63,6 +66,8 @@ type UIState struct {
|
||||
ConnectOnLaunch bool `json:"connect_on_launch"`
|
||||
AutoReconnect bool `json:"auto_reconnect"`
|
||||
LogTail string `json:"log_tail,omitempty"`
|
||||
Rev string `json:"rev,omitempty"`
|
||||
Unchanged bool `json:"unchanged,omitempty"`
|
||||
}
|
||||
|
||||
type PingBestResult struct {
|
||||
@@ -74,10 +79,13 @@ type PingBestResult struct {
|
||||
}
|
||||
|
||||
func New(mgr *core.Manager, cfgPath string, logBuf *logbuf.Buffer) *App {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &App{
|
||||
Mgr: mgr,
|
||||
CfgPath: cfgPath,
|
||||
LogBuf: logBuf,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
UpdateStatus: update.Status{
|
||||
Current: update.DisplayVersion(),
|
||||
ManifestURL: update.DefaultManifestURL,
|
||||
@@ -85,13 +93,40 @@ func New(mgr *core.Manager, cfgPath string, logBuf *logbuf.Buffer) *App {
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown cancels background loops (dock icon / watchdog). Safe to call more than once.
|
||||
func (a *App) Shutdown() {
|
||||
if a == nil || a.cancel == nil {
|
||||
return
|
||||
}
|
||||
a.cancel()
|
||||
}
|
||||
|
||||
func (a *App) GetState() (UIState, error) {
|
||||
return a.getState(false)
|
||||
return a.GetStateSince("")
|
||||
}
|
||||
|
||||
// GetStateSince returns a full poll snapshot, or {rev, unchanged:true} when since
|
||||
// matches the current fingerprint (cheap idle HTTP polls).
|
||||
func (a *App) GetStateSince(since string) (UIState, error) {
|
||||
out, err := a.getState(false)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.Rev = fingerprintState(out)
|
||||
if since != "" && since == out.Rev {
|
||||
return UIState{Rev: out.Rev, Unchanged: true}, nil
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// GetEditState returns full active proxy / hy2 secrets for the editor form (not for polls).
|
||||
func (a *App) GetEditState() (UIState, error) {
|
||||
return a.getState(true)
|
||||
out, err := a.getState(true)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.Rev = fingerprintState(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *App) getState(includeSecrets bool) (UIState, error) {
|
||||
@@ -155,6 +190,23 @@ func (a *App) getState(includeSecrets bool) (UIState, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func fingerprintState(s UIState) string {
|
||||
var b strings.Builder
|
||||
b.Grow(256)
|
||||
fmt.Fprintf(&b, "%t|%s|%s|%s|%t|%s|%t|%t|%t|%s|",
|
||||
s.Connected, s.Profile, s.ActiveProfile, s.Protocol, s.CoreReady, s.LastError,
|
||||
s.SystemProxy, s.ConnectOnLaunch, s.AutoReconnect, s.Subscription)
|
||||
fmt.Fprintf(&b, "%s|%s|%t|", s.Update.Latest, s.Update.Current, s.Update.Available)
|
||||
for _, p := range s.Profiles {
|
||||
fmt.Fprintf(&b, "%s,%s,%s,%t;", p.Name, p.Protocol, p.Host, p.Active)
|
||||
}
|
||||
for _, p := range s.Pings {
|
||||
fmt.Fprintf(&b, "%s,%t,%t,%d,%s;", p.Name, p.OK, p.Soft, p.Ms, p.Error)
|
||||
}
|
||||
sum := sha256.Sum256([]byte(b.String()))
|
||||
return hex.EncodeToString(sum[:8])
|
||||
}
|
||||
|
||||
func trimLogTail(s string, max int) string {
|
||||
if max <= 0 || len(s) <= max {
|
||||
return s
|
||||
@@ -318,7 +370,11 @@ func (a *App) StartBackground() {
|
||||
prefs := a.Mgr.Prefs()
|
||||
if prefs.ConnectOnLaunch {
|
||||
go func() {
|
||||
time.Sleep(1200 * time.Millisecond)
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
case <-time.After(1200 * time.Millisecond):
|
||||
}
|
||||
if a.Mgr.Status().Connected {
|
||||
return
|
||||
}
|
||||
@@ -331,29 +387,45 @@ func (a *App) dockIconLoop() {
|
||||
dockicon.SetConnected(false)
|
||||
var last bool
|
||||
first := true
|
||||
t := time.NewTicker(400 * time.Millisecond)
|
||||
defer t.Stop()
|
||||
for {
|
||||
time.Sleep(400 * time.Millisecond)
|
||||
up := a.Mgr.Status().Connected
|
||||
if first || up != last {
|
||||
first = false
|
||||
last = up
|
||||
dockicon.SetConnected(up)
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
up := a.Mgr.Status().Connected
|
||||
if first || up != last {
|
||||
first = false
|
||||
last = up
|
||||
dockicon.SetConnected(up)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) watchdogLoop() {
|
||||
t := time.NewTicker(2 * time.Second)
|
||||
defer t.Stop()
|
||||
for {
|
||||
time.Sleep(2 * time.Second)
|
||||
if !a.Mgr.TakeUnexpectedDrop() {
|
||||
continue
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
if !a.Mgr.TakeUnexpectedDrop() {
|
||||
continue
|
||||
}
|
||||
prefs := a.Mgr.Prefs()
|
||||
if !prefs.AutoReconnect {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
case <-time.After(800 * time.Millisecond):
|
||||
}
|
||||
_ = a.Connect()
|
||||
}
|
||||
prefs := a.Mgr.Prefs()
|
||||
if !prefs.AutoReconnect {
|
||||
continue
|
||||
}
|
||||
time.Sleep(800 * time.Millisecond)
|
||||
_ = a.Connect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,7 +674,7 @@ func arg[T any](args []json.RawMessage, i int, def T) T {
|
||||
func (a *App) dispatch(name string, args []json.RawMessage) (any, error) {
|
||||
switch name {
|
||||
case "getState":
|
||||
return a.GetState()
|
||||
return a.GetStateSince(arg(args, 0, ""))
|
||||
case "getEditState":
|
||||
return a.GetEditState()
|
||||
case "connect":
|
||||
|
||||
Reference in New Issue
Block a user