Skip to content

Commit d19082f

Browse files
committed
feat: prevent Electron versions that are in use by some window from being removed
1 parent 8584926 commit d19082f

File tree

2 files changed

+92
-2
lines changed

2 files changed

+92
-2
lines changed

src/renderer/components/settings-electron.tsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,39 @@ export const ElectronSettings = observer(
5252
this.handleStateChange = this.handleStateChange.bind(this);
5353
}
5454

55+
/**
56+
* Queries the currently active versions and update the local state.
57+
*
58+
* This currently gives a warning/error in development mode when the ElectronSettings component is unmounted
59+
* ("Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak
60+
* in your application"). This is a false positive, the warning has been removed in React 18+ (see
61+
* https://github.com/facebook/react/pull/22114).
62+
*
63+
* @TODO upgrade to React 18
64+
*/
65+
updateActiveVersions = () => {
66+
this.props.appState
67+
.getActiveVersions()
68+
.then((activeVersions) =>
69+
this.setState({ ...this.state, activeVersions }),
70+
)
71+
.catch((err) => {
72+
console.error(
73+
'Error updating the currently active Electron versions:',
74+
);
75+
console.error(err);
76+
});
77+
};
78+
79+
public componentDidMount() {
80+
this.updateActiveVersions();
81+
}
82+
83+
// Fired when other windows change their active Electron version
84+
public componentDidUpdate() {
85+
this.updateActiveVersions();
86+
}
87+
5588
public handleUpdateElectronVersions() {
5689
this.props.appState.updateElectronVersions();
5790
}
@@ -215,6 +248,7 @@ export const ElectronSettings = observer(
215248
</ButtonGroup>
216249
);
217250
}
251+
218252
private filterSection(): JSX.Element {
219253
const { appState } = this.props;
220254
return (
@@ -401,7 +435,7 @@ export const ElectronSettings = observer(
401435
break;
402436
}
403437

404-
if (version === appState.currentElectronVersion.version) {
438+
if (this.state.activeVersions.has(appState.getVersionLockName(version))) {
405439
return (
406440
<Tooltip
407441
position="auto"

src/renderer/state.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,34 @@ export class AppState {
225225
});
226226
}
227227

228+
// Lock on the active Electron version that prevents other windows from removing it
229+
private versionLock: Lock | null = null;
230+
231+
// Used to release the lock when the current window switches Electron versions
232+
private versionLockController = new AbortController();
233+
234+
private static versionLockNamePrefix = 'version:';
235+
236+
public getVersionLockName(ver: string) {
237+
return [AppState.versionLockNamePrefix, ver].join('');
238+
}
239+
240+
/**
241+
* Retrieves all Electron versions that are currently active in some window.
242+
*/
243+
public async getActiveVersions(): Promise<Set<string>> {
244+
return ((await navigator.locks.query()).held || []).reduce<Set<string>>(
245+
(acc, item) => {
246+
if (item.name?.startsWith(AppState.versionLockNamePrefix)) {
247+
acc.add(item.name);
248+
}
249+
250+
return acc;
251+
},
252+
new Set(),
253+
);
254+
}
255+
228256
constructor(versions: RunnableVersion[]) {
229257
makeObservable<AppState, 'setPageHash' | 'setVersionStates'>(this, {
230258
Bisector: observable,
@@ -795,7 +823,9 @@ export class AppState {
795823
public async removeVersion(ver: RunnableVersion): Promise<void> {
796824
const { version, state, source } = ver;
797825

798-
if (ver === this.currentElectronVersion) {
826+
const activeVersions = await this.getActiveVersions();
827+
828+
if (activeVersions.has(this.getVersionLockName(ver.version))) {
799829
console.log(`State: Not removing active version ${version}`);
800830
return;
801831
}
@@ -954,10 +984,36 @@ export class AppState {
954984
return;
955985
}
956986

987+
if (this.versionLock) {
988+
console.log(`Releasing lock on version ${this.version}`);
989+
990+
// release the lock on the previous version
991+
this.versionLockController.abort();
992+
993+
// replace the spent AbortController
994+
this.versionLockController = new AbortController();
995+
}
996+
957997
const { version } = ver;
958998
console.log(`State: Switching to Electron ${version}`);
959999
this.version = version;
9601000

1001+
navigator.locks.request(
1002+
this.getVersionLockName(version),
1003+
{ mode: 'shared' },
1004+
(lock) => {
1005+
this.versionLock = lock;
1006+
1007+
/**
1008+
* The lock is released when this promise resolves, so we keep it in the
1009+
* pending state until our AbortController is aborted.
1010+
*/
1011+
return new Promise<void>((resolve) => {
1012+
this.versionLockController.signal.onabort = () => resolve();
1013+
});
1014+
},
1015+
);
1016+
9611017
// If there's no current fiddle,
9621018
// or if the current fiddle is the previous version's template,
9631019
// then load the new version's template.

0 commit comments

Comments
 (0)