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

[#58] 테스트 실행 중지 기능 추가하기 #89

Merged
merged 14 commits into from
Nov 21, 2023
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
58 changes: 58 additions & 0 deletions frontend/src/components/Simulation/SimulationInputList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { css } from '@style/css';

import type { ChangeEvent, HTMLAttributes } from 'react';

import type { SimulationInput } from '@/hooks/simulation';

interface Props extends HTMLAttributes<HTMLUListElement> {
inputList: SimulationInput[];
onChangeInput: (index: number, newInput: string) => void;
}

export function SimulationInputList(props: Props) {
const { inputList, onChangeInput, ...rest } = props;

const handleInputChange = (index: number, newInput: string) => {
onChangeInput(index, newInput);
};

return (
<ul {...rest}>
{inputList.map(({ input, id }, index) => (
<li key={id} className={listStyle}>
케이스 {index}:
<SimulationInput
value={input}
onChange={(newInput) => handleInputChange(id, newInput)}
></SimulationInput>
</li>
))}
</ul>
);
}

interface SimulationInputProps {
value: string;
onChange: (newInput: string) => void;
}

const SimulationInput = (props: SimulationInputProps) => {
const { value, onChange } = props;

const handleInput = (e: ChangeEvent<HTMLInputElement>) => {
const newInput = e.target.value;
if (onChange) {
onChange(newInput);
}
};

return <input value={value} className={inputStyle} onChange={handleInput}></input>;
};

const listStyle = css({
marginBottom: '0.25rem',
});

const inputStyle = css({
color: 'black',
});
25 changes: 25 additions & 0 deletions frontend/src/components/Simulation/SimulationResultList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { HTMLAttributes } from 'react';

import type { SimulationResult } from '@/hooks/simulation';

interface Props extends HTMLAttributes<HTMLUListElement> {
resultList: SimulationResult[];
}

export function SimulationResultList(props: Props) {
const { resultList = [] } = props;

return (
<ul>
{resultList.map((result) => (
<li key={result.id}>
<div>
<p>입력: {result.input}</p>
<p>결과: {String(result.output)}</p>
<hr />
</div>
</li>
))}
</ul>
);
}
30 changes: 0 additions & 30 deletions frontend/src/components/Simulation/Simulator.tsx

This file was deleted.

38 changes: 0 additions & 38 deletions frontend/src/components/Simulation/SimulatorList.tsx

This file was deleted.

4 changes: 0 additions & 4 deletions frontend/src/components/Simulation/types.ts

This file was deleted.

2 changes: 2 additions & 0 deletions frontend/src/hooks/simulation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './useSimulations';
export * from './types';
11 changes: 11 additions & 0 deletions frontend/src/hooks/simulation/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type SimulationInput = {
id: number;
input: string;
};

export type SimulationResult = {
id: number;
isDone: boolean;
input: string;
output: unknown;
};
100 changes: 100 additions & 0 deletions frontend/src/hooks/simulation/useSimulations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { useEffect, useMemo, useState } from 'react';

import evaluator from '@/modules/evaluator';

import type { SimulationInput, SimulationResult } from './types';

export const useSimulations = () => {
const [simulationInputs, setSimulationInputs] = useState<SimulationInput[]>([
{ id: 1, input: '' },
{ id: 2, input: '' },
{ id: 3, input: '' },
{ id: 4, input: '' },
{ id: 5, input: '' },
]);
const [simulationResults, setSimulationResults] = useState<SimulationResult[]>([
{ id: 1, isDone: true, input: '', output: '' },
{ id: 2, isDone: true, input: '', output: '' },
{ id: 3, isDone: true, input: '', output: '' },
{ id: 4, isDone: true, input: '', output: '' },
{ id: 5, isDone: true, input: '', output: '' },
]);
const isSimulating = useMemo(() => {
return simulationResults.some((result) => !result.isDone);
}, [simulationResults]);

useEffect(() => {
return evaluator.subscribe(({ result, error, task }) => {
if (!task) return;

setSimulationResults((simulations) => {
return simulations.map((simul) => {
if (simul.id !== task.clientId) return simul;

if (error) {
return {
...simul,
isDone: true,
output: `${error.name}: ${error.message} \n${error.stack}`,
};
}
return {
...simul,
isDone: true,
output: result,
};
});
});
});
}, []);

function runSimulation(code: string) {
const tasks = simulationInputs.map(({ id, input }) =>
evaluator.createEvalMessage(id, code, input),
);

const isRequestSuccess = evaluator.evaluate(tasks);

if (!isRequestSuccess) {
return;
}
Comment on lines +56 to +60
Copy link
Collaborator

Choose a reason for hiding this comment

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

isXXX : boolean;
if(!isXXX) return ;

이렇게 하니 가독성 좋네요


setSimulationResults((simulResults) => {
return simulResults
.map((simul, index) => ({
...simul,
input: simulationInputs[index].input,
}))
.map(toEvaluatingState);
});
}

function changeInput(targetId: number, newParam: string) {
const changedSimulation = simulationInputs.find(({ id }) => id === targetId);
if (changedSimulation) {
changedSimulation.input = newParam;
}
setSimulationInputs([...simulationInputs]);
}

function cancelSimulation() {
evaluator.cancelEvaluation();
}

return {
simulationInputs,
simulationResults,
isSimulating,
runSimulation,
cancelSimulation,
changeInput,
};
};

const toEvaluatingState = (simulation: SimulationResult) => {
return {
...simulation,
output: '계산중...',
isDone: false,
};
};
33 changes: 32 additions & 1 deletion frontend/src/modules/evaluator/EvalTaskManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export default class EvalTaskManager {
this.taskEndNotifier = taskEndNotifier;
this.evaluators = evaluators;

this.setupEvaluators();
}

setupEvaluators() {
this.evaluators.forEach((evaluator) => {
const { worker } = evaluator;
const handleWorkerMessage = ({ data }: MessageEvent<EvalResult>) => {
Expand All @@ -25,7 +29,10 @@ export default class EvalTaskManager {
this.queuedTasks.push(...tasks);
}
isWorking() {
return this.queuedTasks.length > 0;
const hasTask = this.queuedTasks.length > 0;
const hasWorkingEvaluator = this.evaluators.some((evaluator) => !evaluator.isIdle);

return hasTask || hasWorkingEvaluator;
}
popTask() {
return this.queuedTasks.shift();
Expand Down Expand Up @@ -56,4 +63,28 @@ export default class EvalTaskManager {
evaluator.currentTask = null;
this.deployTask();
}
notifyTaskCanceled(task: EvalMessage | null) {
this.taskEndNotifier.notify({
result: undefined,
error: {
name: 'Error',
message: '실행 중단',
stack: '',
},
task,
});
}
cancelTasks() {
const readyTasks = [...this.queuedTasks];
const workingTasks = this.evaluators.map(({ currentTask }) => currentTask);

[...readyTasks, ...workingTasks].forEach((task) => {
this.notifyTaskCanceled(task);
});
this.queuedTasks = [];
}
setNewWorkers(evaluators: Evaluator[]) {
this.evaluators = evaluators;
this.setupEvaluators();
}
}
11 changes: 11 additions & 0 deletions frontend/src/modules/evaluator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ function evaluate(tasks: EvalMessage[]) {

evalManager.queueTasks(tasks);
evalManager.deployTask();

return true;
}

function cancelEvaluation() {
evalWorkers.forEach(({ worker }) => worker.terminate());
evalManager.cancelTasks();

const newEvalWorkers = range(0, TOTAL_WORKERS).map(createEvaluator);
evalManager.setNewWorkers(newEvalWorkers);
Comment on lines +26 to +31
Copy link
Collaborator

Choose a reason for hiding this comment

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

네이밍을 잘 하니, 이 코드만 봐도 어떤 일들을 할 지 예상이 가네요. 진짜 좋습니다.
그리고, 워커는 재활용이 아닌가보네요?!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

네, while(true)로 한 스레드가 먹통이 되어버리면 그 스레드를 통째로 드러내야해서 cancel하면 워커를 종료시키고 새로 등록합니다.

좀 더 정밀하게 하자면 쉬고 있는 워커는 놔두고 일하고 있는 워커만 새로 만드는 방법도 있는데, 그건 그냥 귀찮아서... :)

}

function subscribe(listener: Listener<TaskEndMessage>) {
Expand All @@ -27,6 +37,7 @@ function subscribe(listener: Listener<TaskEndMessage>) {

export default {
evaluate,
cancelEvaluation,
subscribe,
createEvalMessage,
};
Expand Down
Loading
Loading