Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -459,15 +459,15 @@ class JSCRuntime private constructor(private val appContext: Context) {
*/
fun dispatchToJs(packageName: String, envelopeJson: String) {
val record = contexts[packageName] ?: return
// Steady-state NACK — re-arm so a wedged QuickJS context surfaces
// an __error/ready_nack frame after 3s instead of silently
// swallowing the delivery. Skipped during cold-start (timer still
// ticking from spawn).
if (record.readyAcked) {
armReadyNackTimer(record, STEADY_STATE_NACK_TIMEOUT_MS, cold = false)
}
try {
record.executor.submit {
// Steady-state NACK — re-arm inside the per-context executor so
// the timer measures the actual __deliver call, not time spent
// waiting behind earlier queued work. Skipped during cold-start
// (timer still ticking from spawn).
if (record.readyAcked) {
armReadyNackTimer(record, STEADY_STATE_NACK_TIMEOUT_MS, cold = false)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve NACK coverage for executor starvation

When the Android JS executor is already stuck before this submitted task starts—for example, a miniapp timer callback scheduled by scheduleTimer() enters a long/infinite CPU loop, and those timer callbacks do not have the soft watchdog—this line is never reached, so the 3s steady-state ready_nack is never armed for later host deliveries or pings. Previously dispatchToJs armed the timer before enqueueing, which still surfaced a dead context; now that case stays silent until the slower ping/foreground liveness path notices it. Please keep some starvation watchdog outside the executor or add equivalent watchdog coverage to queued timer work.

Useful? React with 👍 / 👎.

}
// Soft watchdog around evaluate: warn at 5s, kill at 30s.
val warn = timerScheduler.schedule({
Log.i(TAG, "watchdog: $packageName __deliver blocked >${WATCHDOG_WARN_MS}ms")
Expand Down
6 changes: 5 additions & 1 deletion mobile/modules/island/src/services/MentraJSRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,11 @@ export class MentraJSRouter {
// dead and let the host surface a "tap to retry" banner.
if (iface === "__error") {
const payload = this.tryParseArgs(msg.argsJson)
this.logger.error(`[${packageName}] ${method}`, payload)
if (method === "ready_nack") {
this.logger.warn(`[${packageName}] ${method}`, payload)
} else {
this.logger.error(`[${packageName}] ${method}`, payload)
}
if (this.crashController) {
// Only treat "exception" + "unhandledRejection" + "uncaught" as
// crash signals. `console.error` calls also flow through the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,18 @@ describe("MentraJSRouter", () => {
expect(logger.error).toHaveBeenCalledTimes(1)
})

test("ready_nack logs as warning instead of dev-overlay error", () => {
router.start()
crust.emit("mentrajs_message", {
packageName: "com.foo",
iface: "__error",
method: "ready_nack",
argsJson: '{"phase":"steady-state","timeoutMs":3000}',
})
expect(logger.warn).toHaveBeenCalledTimes(1)
expect(logger.error).not.toHaveBeenCalled()
})

test("unknown iface logs a debug line but does NOT crash", () => {
router.start()
crust.emit("mentrajs_message", {
Expand Down
Loading