Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically restart backend on deps changes #1348

Merged
merged 5 commits into from
Dec 5, 2022
Merged
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
1 change: 0 additions & 1 deletion src/common/safeIpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export interface InvokeChannels {
>;
'get-cli-open': ChannelInfo<FileOpenResult<ParsedSaveData> | undefined>;
'owns-backend': ChannelInfo<boolean>;
'kill-backend': ChannelInfo<void>;
'restart-backend': ChannelInfo<void>;
'relaunch-application': ChannelInfo<void>;
'quit-application': ChannelInfo<void>;
Expand Down
61 changes: 17 additions & 44 deletions src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,8 +478,8 @@ const spawnBackend = (port: number, pythonInfo: PythonInfo, ffmpegInfo: FfmpegIn
return;
}

log.info('Attempting to spawn backend...');
try {
const spawnBackendProcess = () => {
log.info('Attempting to spawn backend...');
const backendPath = app.isPackaged
? path.join(process.resourcesPath, 'src', 'run.py')
: './backend/src/run.py';
Expand Down Expand Up @@ -520,65 +520,38 @@ const spawnBackend = (port: number, pythonInfo: PythonInfo, ffmpegInfo: FfmpegIn
);
});

ipcMain.handle('relaunch-application', () => {
return backend;
};

try {
let backend = spawnBackendProcess();

const tryKill = () => {
log.info('Attempting to kill backend...');
try {
const success = backend.kill();
if (success) {
log.error('Successfully killed backend.');
log.info('Successfully killed backend.');
} else {
log.error('Error killing backend.');
}
} catch (error) {
log.error('Error killing backend.', error);
}
};

ipcMain.handle('relaunch-application', () => {
tryKill();
app.relaunch();
app.exit();
});

ipcMain.handle('kill-backend', () => {
log.info('Attempting to kill backend...');
try {
const success = backend.kill();
if (success) {
log.error('Successfully killed backend.');
} else {
log.error('Error killing backend.');
}
} catch (error) {
log.error('Error killing backend.', error);
}
});

ipcMain.handle('restart-backend', () => {
log.info('Attempting to kill backend...');
try {
const success = backend.kill();
if (success) {
log.error('Successfully killed backend to restart it.');
} else {
log.error('Error killing backend.');
}
ipcMain.removeHandler('kill-backend');
spawnBackend(port, pythonInfo, ffmpegInfo);
} catch (error) {
log.error('Error restarting backend.', error);
}
tryKill();
backend = spawnBackendProcess();
});

app.on('before-quit', () => {
log.info('Attempting to kill backend...');
try {
const success = backend.kill();
if (success) {
log.error('Successfully killed backend.');
} else {
log.error('Error killing backend.');
}
} catch (error) {
log.error('Error killing backend.');
}
});
app.on('before-quit', tryKill);

log.info('Successfully spawned backend.');
} catch (error) {
Expand Down
7 changes: 4 additions & 3 deletions src/renderer/components/CustomEdge/CustomEdge.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { NeverType } from '@chainner/navi';
import { Center, Icon, IconButton } from '@chakra-ui/react';
import { memo, useEffect, useMemo, useState } from 'react';
import { TbUnlink } from 'react-icons/tb';
Expand Down Expand Up @@ -65,9 +66,9 @@ export const CustomEdge = memo(
const [isHovered, setIsHovered] = useState(false);

const { outputId } = useMemo(() => parseSourceHandle(sourceHandleId!), [sourceHandleId]);
const definitionType = functionDefinitions
.get(parentNode.data.schemaId)!
.outputDefaults.get(outputId)!;
const definitionType =
functionDefinitions.get(parentNode.data.schemaId)?.outputDefaults.get(outputId) ??
NeverType.instance;
const type = useContextSelector(GlobalVolatileContext, (c) =>
c.typeState.functions.get(source)?.outputs.get(outputId)
);
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/components/node/NodeOutputs.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable react/jsx-props-no-spreading */

import { Type } from '@chainner/navi';
import { NeverType, Type } from '@chainner/navi';
import { memo, useCallback } from 'react';
import { useContext, useContextSelector } from 'use-context-selector';
import { Output, OutputId, OutputKind, SchemaId } from '../../../common/common-types';
Expand Down Expand Up @@ -88,7 +88,7 @@ export const NodeOutputs = memo(({ outputs, id, schemaId, animated = false }: No
[outputDataEntry, inputHash]
);

const functions = functionDefinitions.get(schemaId)!.outputDefaults;
const functions = functionDefinitions.get(schemaId)?.outputDefaults;
return (
<>
{outputs.map((output) => {
Expand All @@ -99,7 +99,7 @@ export const NodeOutputs = memo(({ outputs, id, schemaId, animated = false }: No
useOutputData,
kind: output.kind,
schemaId,
definitionType: functions.get(output.id)!,
definitionType: functions?.get(output.id) ?? NeverType.instance,
hasHandle: output.hasHandle,
animated,
};
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/contexts/AlertBoxContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,13 +252,13 @@ export const AlertBoxProvider = memo(({ children }: React.PropsWithChildren<unkn

const onClose = useCallback(
(button: number) => {
current?.resolve(button);
setQueue((q) => q.slice(1));
setDone((prev) => prev + 1);
if (isLast) {
onDisclosureClose();
setHotkeysEnabled(true);
}
current?.resolve(button);
},
[current, isLast, setQueue, onDisclosureClose, setHotkeysEnabled]
);
Expand Down
67 changes: 66 additions & 1 deletion src/renderer/contexts/BackendContext.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import React, { memo, useMemo } from 'react';
import React, { memo, useCallback, useMemo, useRef, useState } from 'react';
import { createContext } from 'use-context-selector';
import { Backend, getBackend } from '../../common/Backend';
import { Category, PythonInfo, SchemaId } from '../../common/common-types';
import { ipcRenderer } from '../../common/safeIpc';
import { SchemaInputsMap } from '../../common/SchemaInputsMap';
import { SchemaMap } from '../../common/SchemaMap';
import { FunctionDefinition } from '../../common/types/function';
import { useAsyncEffect } from '../hooks/useAsyncEffect';
import { useMemoObject } from '../hooks/useMemo';

interface BackendContextState {
port: number;
backend: Backend;
ownsBackend: boolean;
schemata: SchemaMap;
schemaInputs: SchemaInputsMap;
pythonInfo: PythonInfo;
Expand All @@ -21,6 +24,8 @@ interface BackendContextState {
categories: Category[];
categoriesMissingNodes: string[];
functionDefinitions: Map<SchemaId, FunctionDefinition>;
restartingRef: Readonly<React.MutableRefObject<boolean>>;
restart: () => Promise<void>;
}

export const BackendContext = createContext<Readonly<BackendContextState>>(
Expand All @@ -34,6 +39,7 @@ interface BackendProviderProps {
categories: Category[];
categoriesMissingNodes: string[];
functionDefinitions: Map<SchemaId, FunctionDefinition>;
refreshNodes: () => void;
}

export const BackendProvider = memo(
Expand All @@ -44,21 +50,80 @@ export const BackendProvider = memo(
categories,
categoriesMissingNodes,
functionDefinitions,
refreshNodes,
children,
}: React.PropsWithChildren<BackendProviderProps>) => {
const backend = getBackend(port);

const [ownsBackend, setOwnsBackend] = useState<boolean>(false);
const ownsBackendRef = useRef(ownsBackend);
useAsyncEffect(
() => ({
supplier: () => ipcRenderer.invoke('owns-backend'),
successEffect: (value) => {
setOwnsBackend(value);
ownsBackendRef.current = value;
},
}),
[]
);

const schemaInputs = useMemo(() => new SchemaInputsMap(schemata.schemata), [schemata]);

const restartingRef = useRef(false);
const restartPromiseRef = useRef<Promise<void>>();
const needsNewRestartRef = useRef(false);
const restart = useCallback((): Promise<void> => {
if (!ownsBackendRef.current) {
// we don't own the backend, so we can't restart it
return Promise.resolve();
}

if (restartPromiseRef.current) {
// another promise is currently restarting the backend, so we just request another restart
needsNewRestartRef.current = true;
return restartPromiseRef.current;
}

restartingRef.current = true;
restartPromiseRef.current = (async () => {
let error;
do {
needsNewRestartRef.current = false;
try {
// eslint-disable-next-line no-await-in-loop
await ipcRenderer.invoke('restart-backend');
error = null;
} catch (e) {
error = e;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} while (needsNewRestartRef.current);

// Done. At this point, the backend either restarted or failed trying
restartingRef.current = false;
restartPromiseRef.current = undefined;
refreshNodes();

if (error !== null) {
throw error;
}
})();
return restartPromiseRef.current;
}, [refreshNodes]);

const value = useMemoObject<BackendContextState>({
port,
backend,
ownsBackend,
schemata,
schemaInputs,
pythonInfo,
categories,
categoriesMissingNodes,
functionDefinitions,
restartingRef,
restart,
});

return <BackendContext.Provider value={value}>{children}</BackendContext.Provider>;
Expand Down
Loading