When an app fails: fault isolation and recovery
Since Basehim 1.2.71 one broken app can't take a site down. When an app can't load or start, Basehim switches that app off and keeps everything else running. The administrator gets a report and a safe way to turn the app back on once it's fixed.
This page explains what happens, what you as an app author should know, and where the limits are.
Why this exists
Active apps are loaded on every request: page views, the admin, the API and MCP. Before 1.2.71 some mistakes in an app weren't exceptions at all, but engine errors that stop PHP before any error handling can run. Two examples:
- a method that clashes with one in the
Appbase class, such as an app's ownprivate function schema()next to core'sprotected function schema(string $sql): void; - a class name core already uses.
The result was a 500 on every page, including the admin, and the only fix was editing the database or deleting the app's folder.
What happens when an app fails
| The app… | When it's caught | What visitors see | What happens to the app |
|---|---|---|---|
| has a syntax error | before any of its code is loaded | nothing: the page renders | switched off, report saved |
| declares a method its parent doesn't allow (narrower visibility, an incompatible signature, static vs. instance), reuses a core class name, or extends a class that doesn't exist | before loading | nothing | switched off |
throws from its constructor or boot() (any exception, a TypeError, a missing class) | while loading, in the same request | nothing: the page renders without the app | its routes, hooks, menu items, AJAX actions, widgets and content types are removed; switched off |
| hits an engine error the checks couldn't see (for example, a file edited in the last minute) | when PHP stops | that one request answers 503 and reloads itself after a second | switched off; the next request runs without it |
| runs out of memory or time | when PHP stops | that request fails | counted; switched off on the third time within ten minutes |
| fails because the database is unreachable | while loading | whatever core shows for a database outage | not switched off; skipped for that request only |
"Switched off" means three things:
- the app's status becomes
error; - a report is written to
storage/app-health/{slug}.json; - the failure is logged once, in the core log and in the app's own log.
The app's files, settings, tables and data are never touched. If the database can't be written at that moment, the report file alone keeps the app off, and the status is written once the database is back.
Checking code before it runs
Basehim checks an app in three steps:
- The manifest: the namespace, entry class and source folder make sense.
- Declarations, without running any code. Every PHP file is read with PHP's tokenizer, which also finds syntax errors. Each class is compared with its parent: core classes through reflection, the app's own classes through their source. Only what PHP itself would reject is reported, so a healthy app is never switched off by mistake.
- An isolated load test. A separate PHP process loads the app's classes against this version of core, with no database credentials and no
.env. If PHP refuses a class, only that process ends. The test needs the PHP command line on the server. Basehim finds it on cPanel, CloudLinux and most Linux hosts; setPHP_CLI_BINARYin.envto point at another one. Where there is none, steps 1 and 2 still run, and the report says the load test was skipped.
The checks run at these moments:
- when an app is installed or updated (upload, marketplace or upgrade);
- every time it's activated;
- when an administrator clicks Check again;
- before an active app is loaded, whenever its files, its
app.json, core or the PHP version have changed since it last passed.
Results are remembered by a fingerprint of those inputs, so an ordinary request does no checking at all. After a core update every active app is checked once, on the first request. The load tests run in parallel and take about 80 ms each.
The administrator's side
- Apps screen: the app's card turns red, "Disabled — error", with the reason, the time, View report and Retry.
- Banner: every admin page shows a banner to people who can manage apps, until the app is dealt with.
- The report (
/admin/apps/{slug}/health) shows:- what failed, when, at which stage and where;
- the request ID and the call stack;
- whether the database was updated;
- the last full check, the app's routes, and earlier failures.
manage_apps. Opening one of the app's own admin screens leads there too. - Check and reactivate runs the checks again and turns the app on only if they pass. If it then fails while starting, it is switched off again, and the earlier failure stays in its history.
- Keep it off deactivates it normally. Nothing requires editing the database or deleting files.
For app authors
- Don't reuse the names of the base class's methods, such as
get,post,route,table,schema,db,log,view,iconorsettings, unless you mean to override them with a compatible signature. Core itself no longer adds methods to the base class, for the same reason. - A
boot()that throws switches your app off. Before 1.2.71 the throw was only logged, and the app kept running half-registered. Catch what you expect to fail, such as a remote service or an optional file, and let real defects surface. - Keep class files free of code that runs when the file is included. The isolated load test includes them.
- Before you ship, open Apps → your app → Report → Check again on a test site.
Limits
- An engine error still ends the request it happens in. PHP has no way to continue after one. The checks exist so that this happens before the code is loaded. When it does slip through, it costs one self-reloading 503.
- Errors while serving a page (in a controller your app loads later) switch the app off only when they are declaration errors. Other errors give that route a 500 and aren't blamed on the app.
- This is fault isolation, not a sandbox. An app runs with core's rights. Install apps you trust, and review their permissions.
- On a site with several web servers, reports are kept per server, like the rest of
storage/.
Leave a comment