Bound the startup browser-dir probe and log startup phases
The relaunch restore probes the saved browser dir with os.ReadDir on the UI thread before the first frame. A FUSE stall on external storage there would hang the app on the splash screen (reported on device, intermittent; the 17:15 occurrence rotated out of logcat before it could be pulled and left no pad.pad ANR). Two changes: - dirReadable: run the probe in a helper goroutine with a 2 s deadline; a stalled read now costs at most 2 s and falls back to the startup directory instead of hanging the splash. - Phase logs (session load duration, restore, entering frame loop, first frame rendered) so the next hang bisects itself: the missing line names the blocked phase. Verified on device: full chain logs within 40 ms of process start.
This commit is contained in:
parent
6bd5588ade
commit
3afeb0f8d4
|
|
@ -117,7 +117,10 @@ func run(w *app.Window) error {
|
|||
// the browser at the saved directory.
|
||||
sessPath := sessionFilePath()
|
||||
logic.SetSessionSaver(newSessionSaver(sessPath))
|
||||
if sess, ok := loadSession(sessPath); ok {
|
||||
tSess := time.Now()
|
||||
sess, ok := loadSession(sessPath)
|
||||
log.Printf("startup: session load %s (%v)", map[bool]string{true: "restorable", false: "none"}[ok], time.Since(tSess))
|
||||
if ok {
|
||||
if sess.InBrowser {
|
||||
log.Printf("restoring session: browser path=%q", sess.BrowserPath)
|
||||
// The saved directory may have vanished (deleted, synced away) or
|
||||
|
|
@ -126,8 +129,11 @@ func run(w *app.Window) error {
|
|||
// media_rw:media_rw 0750; only /storage/emulated/0 is app-exposed).
|
||||
// Probe with a real directory read — the same operation the
|
||||
// browser's index does — rather than os.Stat, which passes for
|
||||
// unreadable dirs. Fall back to the startup directory otherwise.
|
||||
if _, err := os.ReadDir(sess.BrowserPath); err == nil {
|
||||
// unreadable dirs. The probe runs BOUNDED: it happens on the UI
|
||||
// thread before the first frame, and a FUSE stall on external
|
||||
// storage must not turn into a splash-screen hang — if the read
|
||||
// is not done in time, start in the startup directory instead.
|
||||
if dirReadable(sess.BrowserPath) {
|
||||
logic.BeginBrowserRestore(sess.BrowserPath)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -158,6 +164,10 @@ func run(w *app.Window) error {
|
|||
|
||||
go frameReceiver(w, &mu, &frame, logic.FrameChan())
|
||||
go logic.Run()
|
||||
log.Printf("startup: logic running, entering frame loop")
|
||||
// firstFrame logs the moment the UI actually drew something (splash
|
||||
// done): the anchor for bisecting startup hangs — the log line missing
|
||||
// after "entering frame loop" points at the blocked phase.
|
||||
|
||||
// lastExcl tracks the selection-handle grab boxes last sent to
|
||||
// SetGestureExclusions (Android: keeps the system back gesture from
|
||||
|
|
@ -176,6 +186,7 @@ func run(w *app.Window) error {
|
|||
// lastFrameW/H hold the previous FrameEvent's window size (px); 0 = no
|
||||
// frame yet. Used to spot shrink frames (see ZeroWheelScroll below).
|
||||
var lastFrameW, lastFrameH int
|
||||
var firstFrame bool
|
||||
|
||||
for {
|
||||
e := w.Event()
|
||||
|
|
@ -193,6 +204,10 @@ func run(w *app.Window) error {
|
|||
PixelHeight: e.Config.Size.Y,
|
||||
}
|
||||
case app.FrameEvent:
|
||||
if !firstFrame {
|
||||
firstFrame = true
|
||||
log.Printf("startup: first frame rendered")
|
||||
}
|
||||
if perfOn {
|
||||
presentN++
|
||||
if presentStart.IsZero() {
|
||||
|
|
@ -467,6 +482,29 @@ func run(w *app.Window) error {
|
|||
}
|
||||
}
|
||||
|
||||
// dirReadable reports whether path can be listed by this app, with a 2 s
|
||||
// deadline (see the startup probe in run): it runs os.ReadDir in a helper
|
||||
// goroutine so a FUSE stall on external storage cannot hold the UI thread
|
||||
// before the first frame (a splash-screen hang). The helper goroutine leaks
|
||||
// on timeout — a deliberate, one-per-launch, only-on-stall trade.
|
||||
func dirReadable(path string) bool {
|
||||
ch := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := os.ReadDir(path)
|
||||
ch <- err
|
||||
}()
|
||||
select {
|
||||
case err := <-ch:
|
||||
if err != nil {
|
||||
log.Printf("startup: browser dir %q not readable: %v (starting in the startup directory)", path, err)
|
||||
}
|
||||
return err == nil
|
||||
case <-time.After(2 * time.Second):
|
||||
log.Printf("startup: browser dir probe for %q timed out (starting in the startup directory)", path)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// exclEqual reports whether two exclusion-rect sets are identical.
|
||||
// newSessionSaver builds the relaunch-session file writer registered with
|
||||
// the logic goroutine (spec §7). The file is a tiny JSON snapshot; a torn
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user