mirror of
https://github.com/edu4rdshl/Strata.git
synced 2026-07-17 23:24:46 +00:00
fix: extension supervisor + panel hardening
- Daemon supervisor: a _spawnPending guard so an in-flight GetNameOwner check plus a firing backoff timer can't spawn two daemons; wait_async now binds the subprocess and _onDaemonExited ignores foreign/stale exits, so backoff accounting can't be corrupted by an overlapping spawn. - Panel: clearItems() invalidates the search snapshot (_resultsEpoch = -1) so a scroll after HistoryCleared can't render stale rows; _ensureVisible's idle callback null-guards the overlay like the other idle callbacks. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
aa56cac958
commit
107de1c3ff
2 changed files with 20 additions and 4 deletions
|
|
@ -21,6 +21,10 @@ export default class StrataExtension extends Extension {
|
||||||
_daemonRestartAttempts = 0;
|
_daemonRestartAttempts = 0;
|
||||||
_shuttingDown = false;
|
_shuttingDown = false;
|
||||||
_daemonRestartTimerId = null;
|
_daemonRestartTimerId = null;
|
||||||
|
_daemonKillTimerId = null;
|
||||||
|
/** True while a GetNameOwner check before spawning is in flight, so an
|
||||||
|
* overlapping respawn attempt can't spawn a second daemon. */
|
||||||
|
_spawnPending = false;
|
||||||
|
|
||||||
/** @type {StrataPanel | null} */
|
/** @type {StrataPanel | null} */
|
||||||
_panel = null;
|
_panel = null;
|
||||||
|
|
@ -266,6 +270,11 @@ export default class StrataExtension extends Extension {
|
||||||
|
|
||||||
_spawnDaemon() {
|
_spawnDaemon() {
|
||||||
if (this._shuttingDown) return;
|
if (this._shuttingDown) return;
|
||||||
|
// Coalesce overlapping spawn attempts: the async GetNameOwner check below
|
||||||
|
// can still be in flight when the backoff timer fires _spawnDaemon again.
|
||||||
|
// Without this guard both could call _doSpawnDaemon and spawn two daemons.
|
||||||
|
if (this._spawnPending) return;
|
||||||
|
this._spawnPending = true;
|
||||||
|
|
||||||
// Check if a daemon is already running (e.g. via systemd user service).
|
// Check if a daemon is already running (e.g. via systemd user service).
|
||||||
// If the D-Bus name is already owned we must not spawn a second instance.
|
// If the D-Bus name is already owned we must not spawn a second instance.
|
||||||
|
|
@ -275,6 +284,8 @@ export default class StrataExtension extends Extension {
|
||||||
new GLib.Variant('(s)', [BUS_NAME]),
|
new GLib.Variant('(s)', [BUS_NAME]),
|
||||||
null, Gio.DBusCallFlags.NONE, 2000, null,
|
null, Gio.DBusCallFlags.NONE, 2000, null,
|
||||||
(_conn, result) => {
|
(_conn, result) => {
|
||||||
|
this._spawnPending = false;
|
||||||
|
if (this._shuttingDown) return;
|
||||||
try {
|
try {
|
||||||
_conn.call_finish(result);
|
_conn.call_finish(result);
|
||||||
// Name already owned - daemon managed externally (systemd etc).
|
// Name already owned - daemon managed externally (systemd etc).
|
||||||
|
|
@ -305,7 +316,7 @@ export default class StrataExtension extends Extension {
|
||||||
});
|
});
|
||||||
this._daemon.init(null);
|
this._daemon.init(null);
|
||||||
this._daemonSpawnTime = GLib.get_monotonic_time() / 1000; // ms
|
this._daemonSpawnTime = GLib.get_monotonic_time() / 1000; // ms
|
||||||
this._daemon.wait_async(null, () => this._onDaemonExited());
|
this._daemon.wait_async(null, (proc) => this._onDaemonExited(proc));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[Strata] Failed to spawn daemon:', e);
|
console.error('[Strata] Failed to spawn daemon:', e);
|
||||||
this._scheduleDaemonRestart();
|
this._scheduleDaemonRestart();
|
||||||
|
|
@ -331,9 +342,12 @@ export default class StrataExtension extends Extension {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDaemonExited() {
|
_onDaemonExited(proc) {
|
||||||
if (!this._daemon) return; // shutdown initiated by _stopDaemon
|
// Ignore exits from anything that is not the current daemon: a stop
|
||||||
const exit = this._daemon.get_exit_status();
|
// initiated by _stopDaemon (which nulls _daemon), or a stray subprocess
|
||||||
|
// from an overlapping spawn. Otherwise we'd misattribute its exit.
|
||||||
|
if (!this._daemon || proc !== this._daemon) return;
|
||||||
|
const exit = proc.get_exit_status();
|
||||||
const lifetimeMs = (GLib.get_monotonic_time() / 1000) - this._daemonSpawnTime;
|
const lifetimeMs = (GLib.get_monotonic_time() / 1000) - this._daemonSpawnTime;
|
||||||
this._daemon = null;
|
this._daemon = null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -347,6 +347,7 @@ export class StrataPanel {
|
||||||
this._items = [];
|
this._items = [];
|
||||||
this._searchResults = [];
|
this._searchResults = [];
|
||||||
this._searchRendered = 0;
|
this._searchRendered = 0;
|
||||||
|
this._resultsEpoch = -1; // invalidate the search snapshot so a scroll can't render stale rows
|
||||||
this._hoveredWidget = null;
|
this._hoveredWidget = null;
|
||||||
this._activeWidget = null;
|
this._activeWidget = null;
|
||||||
this._widgets.clear();
|
this._widgets.clear();
|
||||||
|
|
@ -558,6 +559,7 @@ export class StrataPanel {
|
||||||
|
|
||||||
_ensureVisible(widget) {
|
_ensureVisible(widget) {
|
||||||
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
|
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
|
||||||
|
if (!this._overlay) return GLib.SOURCE_REMOVE; // panel destroyed before idle ran
|
||||||
try {
|
try {
|
||||||
const adj = this._scrollView.get_vadjustment();
|
const adj = this._scrollView.get_vadjustment();
|
||||||
if (!adj || adj.page_size === 0) return GLib.SOURCE_REMOVE;
|
if (!adj || adj.page_size === 0) return GLib.SOURCE_REMOVE;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue