[EXT-035][실무] 느린 스트림을 시간 안에 안전하게 멈추기 > IT 기술 공유

본문 바로가기
사이트 내 전체검색

IT 기술 공유

[EXT-035][실무] 느린 스트림을 시간 안에 안전하게 멈추기

페이지 정보

profile_image
작성자 기술팀장
댓글 0건 조회 13회 작성일 26-09-09 01:37

본문

[이번 수업]

스트림이 끝없이 기다리면 연결과 메모리가 계속 묶일 수 있습니다. 이번에는 AbortController로 제한 시간이 지난 ReadableStream.pipeTo()를 멈추고 중단 이유와 자원 정리를 확인합니다.

[선수지식]

EXT-034의 ReadableStream, WritableStream, pipeThrough()와 역압을 알아야 합니다. Promise, try/catch, setTimeout을 이해하고 Node.js 파일을 실행할 수 있으면 됩니다.

[학습목표]

1. AbortController와 AbortSignal의 역할을 구분한다.
2. pipeTo()에 signal을 전달해 스트림을 중단한다.
3. cancel과 abort에서 원본 자원을 정리한다.

[핵심개념]

AbortController는 중단 버튼이고 controller.signal은 작업에 전달하는 신호입니다. controller.abort(reason)를 호출하면 signal이 중단 상태가 되고 reason을 보관합니다. 이유를 생략하면 AbortError가 사용됩니다.

ReadableStream의 pipeTo(destination, { signal })은 읽기 스트림을 쓰기 스트림에 연결합니다. 신호가 중단되면 기본적으로 목적지를 abort하고 원본을 cancel한 뒤 pipeTo()의 Promise를 같은 이유로 거부합니다. cancel에서 타이머, 파일, 소켓 같은 실제 원본을 닫아야 자원이 해제됩니다.

[따라하기]

아래 내용을 stream-timeout.mjs로 저장합니다. 원본은 200밀리초 뒤에 데이터를 만들지만 제한 시간은 30밀리초입니다.

```js
const controller = new AbortController();
let sourceTimer;

const source = new ReadableStream({
  start(streamController) {
    sourceTimer = setTimeout(() => {
      streamController.enqueue('늦은 조각');
      streamController.close();
    }, 200);
  },
  cancel(reason) {
    clearTimeout(sourceTimer);
    console.log(`source cancel=${reason.name}`);
  },
});

const sink = new WritableStream({
  write(chunk) { console.log(`write=${chunk}`); },
  abort(reason) { console.log(`sink abort=${reason.name}`); },
});

const timeout = setTimeout(() => {
  controller.abort(new DOMException('시간 제한', 'TimeoutError'));
}, 30);

try {
  await source.pipeTo(sink, { signal: controller.signal });
} catch (error) {
  console.log(`result=${error.name}`);
} finally {
  clearTimeout(timeout);
}
```

터미널에서 실행합니다.

```text
node stream-timeout.mjs
```

예상 결과입니다.

```text
sink abort=TimeoutError
source cancel=TimeoutError
result=TimeoutError
```

앞 두 줄은 환경에 따라 순서가 바뀔 수 있지만 write 줄은 나오지 않습니다. cancel이 원본 타이머를 지웠기 때문입니다. Windows, macOS, Linux의 명령은 같으며 `node --version`으로 버전을 확인할 수 있습니다.

[흔한 실수]

타이머에서 오류만 던지고 pipeTo()에 signal을 주지 않으면 작업은 계속됩니다. 반환된 Promise를 await하지 않으면 중단 오류를 놓칠 수 있습니다. cancel과 abort를 비워 두면 외부 연결이 남습니다. 정상 종료 뒤 타이머도 finally에서 정리하세요. 중단을 성공 완료로 기록해서도 안 됩니다.

[보안 주의]

시간뿐 아니라 청크 크기, 누적 바이트 수, 동시 스트림 수도 제한하세요. 연결이 끊기면 파일·DB·외부 요청에도 중단을 전달하되 저장 작업은 트랜잭션으로 보호합니다. 로그에는 중단 종류와 시간만 남기고 비밀 토큰이나 청크 본문을 기록하지 않습니다. 실습은 본인 소유의 로컬 스트림으로만 진행합니다.

[직접 해볼 과제]

원본 생성 시간을 10밀리초로 바꿔 제한 시간보다 먼저 끝내 보세요. write=늦은 조각이 나오고 중단 로그가 없는지 확인합니다. 이어 제한 시간을 5밀리초로 바꿔 TimeoutError가 다시 전파되는지 비교하세요.

[확인문제]

1. AbortController와 AbortSignal은 각각 어떤 역할을 하나요?
2. 기본 pipeTo()가 중단 신호를 받으면 원본과 목적지에 무엇을 요청하나요?
3. cancel에서 실제 파일이나 소켓을 닫아야 하는 이유는 무엇인가요?

[다음 학습]

다음에는 AbortSignal.timeout()과 AbortSignal.any()로 시간 제한과 사용자 취소를 합치고 어느 신호가 멈췄는지 구분합니다.

[공식 참고 자료]

WHATWG DOM Standard — AbortController
https://dom.spec.whatwg.org/#interface-abortcontroller

WHATWG DOM Standard — AbortSignal
https://dom.spec.whatwg.org/#interface-AbortSignal

WHATWG Streams Standard — pipeTo()
https://streams.spec.whatwg.org/#rs-pipe-to

댓글목록

등록된 댓글이 없습니다.

회원로그인

회원가입

사이트 정보

회사명 : 회사명 / 대표 : 대표자명
주소 : OO도 OO시 OO구 OO동 123-45
사업자 등록번호 : 123-45-67890
전화 : 02-123-4567 팩스 : 02-123-4568
통신판매업신고번호 : 제 OO구 - 123호
개인정보관리책임자 : 정보책임자명

접속자집계

오늘
3,945
어제
6,862
최대
16,772
전체
769,896
Copyright © 소유하신 도메인. All rights reserved.