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

[#61] 클라이언트에 js 엔진 올리기 #83

Merged
merged 4 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"codemirror": "^6.0.1",
"hast-util-to-jsx-runtime": "^2.2.0",
"marked": "^2.1.3",
"quickjs-emscripten": "^0.23.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^9.0.1",
Expand Down
7 changes: 7 additions & 0 deletions frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions frontend/src/modules/evaluator/eval.worker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import evalCode from './evalCode';
import * as QuickJS from './quickjs';
import type { EvalMessage } from './types';

type MessageEvent = {
Expand All @@ -10,7 +10,7 @@ self.addEventListener('message', async function (e: MessageEvent) {

try {
const { code, param } = message;
const result = evalCode(code, param);
const result = await QuickJS.evaluate(code, param);
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍🏻 완벽한 모듈화네요


self.postMessage(result);
} catch (err) {
Expand Down
12 changes: 0 additions & 12 deletions frontend/src/modules/evaluator/evalCode.ts

This file was deleted.

48 changes: 48 additions & 0 deletions frontend/src/modules/evaluator/quickjs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { QuickJSContext, QuickJSWASMModule } from 'quickjs-emscripten';
import { getQuickJS } from 'quickjs-emscripten';

const MEM_LIMIT = 1024 * 1024; // 1GB
const STACK_LIMIT = 1024 * 512; // 500MB

export async function evaluate(code: string, params: string) {
const QuickJS = await getQuickJS();
const runtime = createRuntime(QuickJS);
const vm = runtime.newContext();

try {
return evalCode(vm, code, params);
} finally {
vm.dispose();
runtime.dispose();
}
}
Comment on lines +12 to +18
Copy link
Collaborator

Choose a reason for hiding this comment

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

evalCode가 정상작동하면 return하고 아니면 finally가 돌아가는 거 같은데,
에러가 발생했을 때 return이 안되고 finally가 돌아가서 메모리 비워주는 건가요??
정상작동 한다면 try 안에 return이 있어 finally가 호출이 안 되는 거 아닌가요??

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

image

try catch finally에서 finally는 try가 return하기 전에 수행이 됩니다. (신기하죠?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

image

이는 catch에서도 동일합니다.
어쨌든 finally는 무조건 try-catch문 종료 전에 수행됨을 보장해줍니다.

그런 연유로 finally를 사용했어요 :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

와우 😎


const createRuntime = (quickjs: QuickJSWASMModule) => {
const runtime = quickjs.newRuntime();
runtime.setMemoryLimit(MEM_LIMIT);
runtime.setMaxStackSize(STACK_LIMIT);

return runtime;
};

const evalCode = (vm: QuickJSContext, code: string, params: string) => {
const script = toRunableScript(code, params);

const startTime = performance.now();
Comment on lines +28 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.

별 게 아니긴 한데 궁금해서요! 29번째 줄과 31번째 줄 사이에 공백을 넣으신 이유가 있나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

가독성을 위해 논리가 바뀌는 영역을 분리한거에요.

동작시킬 스크립트 생성

스크립트 실행 및 성능측정

결과 반환

순서로 분리된 겁니다.
제 개인적인 분류 기준이라고 보면 됩니다. (사실 그래서 규칙성이 떨어져요)

const result = vm.unwrapResult(vm.evalCode(script));
const endTime = performance.now();
const value = vm.dump(result);
result.dispose();
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.

네 그렇게 생각했는데 다시 보니까 엣지케이스가 좀 있네요

function solution() {
   throw Error("err");
}

위와 같은 코드가 들어오면 dispose를 거치지 않게 되고 있습니다. 우선은 다른 메모리 할당해제가 잘 일어나서 누수가 없지 않을까? 생각되긴 하는데, 이건 그냥 추측이구요.

이런 케이스를 보완할 수 있는 방법을 좀 찾아보고 추가해볼게요


return {
time: endTime - startTime,
result: value,
};
};

const toRunableScript = (code: string, params: string) => {
return `
${code}\n
solution(${params});
`;
};