7.5 KiB
API 일관성 개선
이 파일을 읽는 구현 에이전트에게
각 항목의 체크리스트를 완료하면서 [ ]를 [x]로 표시하라.
중간 검증 명령은 해당 항목 구현 직후 실행하고, 출력을 CODE_REVIEW.md의 검증 결과 섹션에 붙여넣어라.
최종 검증도 마찬가지로 실행 후 출력을 기록하라.
계획과 다르게 구현한 부분은 이유와 함께 CODE_REVIEW.md의 "계획 대비 변경 사항"에 기록하라.
배경
프로젝트 전반 평가에서 발견된 두 가지 API 비일관성을 수정한다.
| 항목 | 문제 | 영향 |
|---|---|---|
| IMPROVE-1 | Dart sendRequest에 timeout 파라미터 없음 |
응답 없으면 무한 대기 |
| IMPROVE-2 | TypeScript write queue 크기 제한 없음 | await 없이 대량 send 시 무한 증가 가능 |
Go/Kotlin/Python/TypeScript는 sendRequest에 timeout을 지원한다. Dart만 없다.
Go/Kotlin/Python은 write queue를 64개로 제한한다. TypeScript와 Dart는 제한이 없다.
Dart write queue는 Promise chaining 방식이라 구조상 추가가 어려우므로 이번 범위에서 제외한다.
의존 관계 및 구현 순서
IMPROVE-1과 IMPROVE-2는 독립적이다. 이 순서로 진행한다.
[IMPROVE-1] Dart sendRequest timeout 파라미터 추가
문제
dart/lib/src/communicator.dart — sendRequest에 timeout 파라미터가 없다.
Future<Res> sendRequest<Req extends GeneratedMessage, Res extends GeneratedMessage>(
Req data) async {
다른 언어 비교:
| 언어 | timeout 기본값 |
|---|---|
| Go | time.Duration, 기본 30s |
| Kotlin | timeoutMs: Long = 30_000L |
| Python | timeout: float = 30.0 |
| TypeScript | timeoutMs = 30000 |
| Dart | 없음 — 무한 대기 |
해결 방법
sendRequest에 named 파라미터 {Duration timeout = const Duration(seconds: 30)}를 추가한다.
응답 대기를 completer.future.timeout(timeout, onTimeout: ...) 으로 감싼다.
timeout 발생 시 _pendingRequests에서 해당 nonce를 제거하고 TimeoutException을 전파한다.
Before:
Future<Res> sendRequest<Req extends GeneratedMessage, Res extends GeneratedMessage>(
Req data) async {
if (!isAlive) return Future.error(StateError('not connected'));
final requestNonce = ++nonce;
// ...
return completer.future;
}
After:
Future<Res> sendRequest<Req extends GeneratedMessage, Res extends GeneratedMessage>(
Req data, {Duration timeout = const Duration(seconds: 30)}) async {
if (!isAlive) return Future.error(StateError('not connected'));
final requestNonce = ++nonce;
// ...
return completer.future.timeout(timeout, onTimeout: () {
_pendingRequests.remove(requestNonce);
throw TimeoutException(
'sendRequest timeout for nonce $requestNonce', timeout);
});
}
dart:async의 TimeoutException을 사용한다 (import 'dart:async'는 이미 있음).
수정 파일 및 체크리스트
dart/lib/src/communicator.dart—sendRequest에{Duration timeout = const Duration(seconds: 30)}named 파라미터 추가dart/lib/src/communicator.dart—completer.future.timeout(timeout, onTimeout: ...)적용, pending 정리 포함dart/test/communicator_test.dart—sendRequest가 timeout 내 응답 없으면 TimeoutException을 던진다테스트 추가
테스트 작성
- 파일:
dart/test/communicator_test.dart - 테스트명:
sendRequest가 timeout 내 응답 없으면 TimeoutException을 던진다 - 시나리오:
FakeTransport에 packet을 보내지만 응답을 보내지 않음,Duration(milliseconds: 50)timeout으로 호출,throwsA(isA<TimeoutException>())단언, timeout 후_pendingRequests가 비어있는지 확인
중간 검증
cd dart && dart test test/communicator_test.dart
# 새 timeout 테스트 포함 전체 PASS
cd dart && dart analyze
# No issues found
[IMPROVE-2] TypeScript write queue maxsize 제한
문제
typescript/src/communicator.ts — write queue가 크기 제한 없는 Array다.
private writeQueue: QueueItem[] = [];
Go/Kotlin/Python은 64개 제한 도달 시 큐에 빈 자리가 생길 때까지 발신을 블로킹한다.
TypeScript는 await하지 않고 대량 send()를 발행하면 큐가 무한 증가한다.
해결 방법
MAX_WRITE_QUEUE_SIZE = 64 상수를 추가한다.
큐가 가득 찼을 때 waiter Promise를 등록하고, 항목이 소비될 때마다 waiter를 깨운다 (Python의 asyncio.Queue와 동일한 시맨틱).
shutdown() 시 모든 waiter를 깨워 NotConnectedError로 종료한다.
추가할 멤버:
private static readonly MAX_WRITE_QUEUE_SIZE = 64;
private writeQueueWaiters: Array<() => void> = [];
queuePacket 변경:
async queuePacket(base: PacketBase): Promise<void> {
if (!this.isAliveFlag || this.transport === null) {
throw new NotConnectedError();
}
// 큐가 가득 찬 경우 빈 자리가 생길 때까지 대기
while (this.writeQueue.length >= Communicator.MAX_WRITE_QUEUE_SIZE) {
await new Promise<void>((resolve) => {
this.writeQueueWaiters.push(resolve);
});
if (!this.isAliveFlag) throw new NotConnectedError();
}
// 이하 기존 코드 동일
...
}
runWriteQueue에서 항목 소비 후 waiter 해제:
const item = this.writeQueue.shift();
if (this.writeQueueWaiters.length > 0) {
this.writeQueueWaiters.shift()?.();
}
shutdown()에서 waiters 플러시:
while (this.writeQueueWaiters.length > 0) {
this.writeQueueWaiters.shift()?.();
}
initialize()에서 writeQueueWaiters 초기화 추가:
this.writeQueueWaiters = [];
수정 파일 및 체크리스트
typescript/src/communicator.ts—MAX_WRITE_QUEUE_SIZE = 64상수 추가typescript/src/communicator.ts—writeQueueWaiters멤버 추가 및initialize()초기화typescript/src/communicator.ts—queuePacket()에 full 시 대기 로직 추가typescript/src/communicator.ts—runWriteQueue()에서 waiter 해제typescript/src/communicator.ts—shutdown()에서 waiters 플러시typescript/test/communicator.test.ts— 큐 full 시 대기 후 처리 테스트 추가
테스트 작성
- 파일:
typescript/test/communicator.test.ts - 테스트명:
write queue가 MAX_WRITE_QUEUE_SIZE 초과 시 대기 후 순서대로 처리된다 - 시나리오: FakeTransport에서 write를 인위적으로 지연, 65번째
queuePacket호출이 블로킹되다가 64번째가 처리되면 진행됨을 확인
중간 검증
cd typescript && npx tsc --noEmit
# 타입 오류 없음
cd typescript && npx vitest run test/communicator.test.ts
# 새 큐 테스트 포함 PASS
수정 파일 요약
| 파일 | 항목 |
|---|---|
dart/lib/src/communicator.dart |
IMPROVE-1 |
dart/test/communicator_test.dart |
IMPROVE-1 |
typescript/src/communicator.ts |
IMPROVE-2 |
typescript/test/communicator.test.ts |
IMPROVE-2 |
최종 검증
# Dart
cd dart && dart analyze
# No issues found
cd dart && dart test
# 전체 PASS (신규 timeout 테스트 포함)
# TypeScript
cd typescript && npx tsc --noEmit
# 타입 오류 없음
cd typescript && npx vitest run
# 전체 PASS (신규 큐 테스트 포함)