chore: update stress test scripts and archive slow_mix task files

This commit is contained in:
toki 2026-06-03 13:10:48 +09:00
parent de832efac6
commit 09041950e5
12 changed files with 1103 additions and 121 deletions

View file

@ -31,6 +31,8 @@ Profiles (comma separated, default: per-language all):
(quick: 2s, full: 30s/5m/30m candidates)
parallel multi-connection FIFO / nonce isolation
(quick: 4 clients, full: 16/128/512/1024 client candidates)
slow-mix mixed fast/slow handler load, same-connection delay and cross-connection isolation
(quick: 4 clients, full: 16 clients)
payload payload size matrix request-response (quick: 1KB/64KB, full: 1KB/64KB/1MB)
with per-payload latency, throughput, peak memory, stability counters
cross cross-language request-response correctness / stability adapter

View file

@ -0,0 +1,209 @@
<!-- task=m-high-performance-parallel-operations/05+04_slow_mix plan=0 tag=TEST -->
# Code Review Reference - TEST
> **[IMPLEMENTING AGENT - READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only.
## 개요
date=2026-06-03
task=m-high-performance-parallel-operations/05+04_slow_mix, plan=0, tag=TEST
## Roadmap Targets
- Milestone: `agent-roadmap/milestones/high-performance-parallel-operations.md`
- Task ids:
- `slow-mix`: 빠른 handler와 느린 handler가 섞인 혼합 부하 추가
- Completion mode: check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [TEST-1] Runner profile contract | [x] |
| [TEST-2] Slow mix harness | [x] |
| [TEST-3] Verification | [x] |
## 구현 체크리스트
- [x] [TEST-1] `run_stress.sh` usage와 result contract에 `slow-mix` profile을 추가한다.
- [x] [TEST-2] Dart/Go/Kotlin/Python/TypeScript harness에 `slow-mix` profile을 추가한다. 검증: 느린 handler가 같은 connection FIFO를 계약대로 지연시키되 다른 connection의 nonce/FIFO 상태를 오염시키지 않는다.
- [x] [TEST-3] focused slow-mix stress, TypeScript smoke, 전체 matrix를 실행하고 결과 파일 경로를 review stub에 기록한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G08_0.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G08_0.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하여 plan/review/archive 산출물이 추적 가능한지 확인한다.
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [x] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G08.md`와 `CODE_REVIEW-cloud-G08.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
## 계획 대비 변경 사항
- Kotlin `slow-mix` quick WS 실행에서 기존 WS inbound launch-per-frame FIFO 한계가 같은 방식으로 드러났다. 계획의 "기존 deferred bottleneck 정책 보존"에 맞춰 `burst`와 같은 required SKIP 근거를 `slow-mix`에도 적용했다.
- Go는 public request API가 send와 response wait를 한 호출로 묶고 있어 같은 connection 안에서는 순차 request 루프를 사용하고, 여러 connection을 동시에 실행해 느린 handler 지연과 connection 간 nonce/type 격리를 기록했다.
## 주요 설계 결정
- `slow-mix` 축은 quick `clients=4, perClient=20, slowEvery=5, delayMs=50`, full `clients=16, perClient=200, slowEvery=10, delayMs=100`으로 추가했다.
- 서버 handler는 `(index + 1) % slowEvery == 0`인 request만 지연하고, 응답은 `index * 2`와 `echo:<message>`로 반환해 기존 nonce/type 검증 패턴을 유지했다.
- TypeScript/Dart/Python/Kotlin runnable 경로는 같은 connection 안의 request completion order를 `fifoViolations`에 반영한다. Kotlin TCP와 Kotlin WS slow-mix는 기존/동일 계열 병목으로 required SKIP 처리한다.
- result row axis는 `clients=<n>,slowEvery=<n>,delayMs=<n>`로 통일했고, `run_stress.sh --help` usage에 `slow-mix` 설명을 추가했다.
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- `04+01_load_profile_scales` dependency가 complete log로 충족된 뒤 구현됐는지 확인한다.
- slow handler가 same-connection delay는 허용하되 cross-connection nonce/FIFO counters를 오염시키지 않는지 확인한다.
- 새 profile이 unknown/optional SKIP으로 빠지지 않고 실제 row 또는 명확한 required SKIP을 남기는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
### TEST-1 중간 검증
```text
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --help
Profiles (comma separated, default: per-language all):
roundtrip request-response latency at concurrency 1/16/64/256
burst single-connection fire-and-forget burst ordering / leak check
(quick: 200, full: 1k/10k/100k)
sustained time-boxed sustained request-response with peak memory
(quick: 2s, full: 30s/5m/30m candidates)
parallel multi-connection FIFO / nonce isolation
(quick: 4 clients, full: 16/128/512/1024 client candidates)
slow-mix mixed fast/slow handler load, same-connection delay and cross-connection isolation
(quick: 4 clients, full: 16 clients)
payload payload size matrix request-response (quick: 1KB/64KB, full: 1KB/64KB/1MB)
with per-payload latency, throughput, peak memory, stability counters
```
### TEST-2 중간 검증
```text
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang typescript --transport tcp
RUN stress lang=typescript mode=quick transports=tcp profiles=slow-mix
INFO stress harness language=TypeScript mode=quick transports=tcp profiles=slow-mix typeName=TestData
[slow-mix] transport=tcp mode=quick clients=4,slowEvery=5,delayMs=50 perClient=20
[slow-mix] transport=tcp clients=4 done peakMemMb=80.8 violations=0
DONE typescript: PASS (rows=1 skips=0 required_skips=0 violations=0)
전체 결과값: PASS
결과 기록 파일: `agent-test/runs/20260603-031533-proto-socket-stress-quick.md`
```
### 최종 검증
```text
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang typescript --transport tcp
RUN stress lang=typescript mode=quick transports=tcp profiles=slow-mix
INFO stress harness language=TypeScript mode=quick transports=tcp profiles=slow-mix typeName=TestData
[slow-mix] transport=tcp mode=quick clients=4,slowEvery=5,delayMs=50 perClient=20
[slow-mix] transport=tcp clients=4 done peakMemMb=80.8 violations=0
DONE typescript: PASS (rows=1 skips=0 required_skips=0 violations=0)
전체 결과값: PASS
결과 기록 파일: `agent-test/runs/20260603-031533-proto-socket-stress-quick.md`
$ cd typescript && npm run check && npm test
(stdout/stderr)
> proto-socket@1.0.5 check
> tsc --noEmit
> proto-socket@1.0.5 test
> vitest run
Test Files 5 passed (5)
Tests 50 passed (50)
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang dart,go,python,typescript --transport tcp
RUN stress lang=dart mode=quick transports=tcp profiles=slow-mix
DONE dart: PASS (rows=1 skips=0 required_skips=0 violations=0)
RUN stress lang=go mode=quick transports=tcp profiles=slow-mix
DONE go: PASS (rows=1 skips=0 required_skips=0 violations=0)
RUN stress lang=python mode=quick transports=tcp profiles=slow-mix
DONE python: PASS (rows=1 skips=0 required_skips=0 violations=0)
RUN stress lang=typescript mode=quick transports=tcp profiles=slow-mix
DONE typescript: PASS (rows=1 skips=0 required_skips=0 violations=0)
전체 결과값: PASS
결과 기록 파일: `agent-test/runs/20260603-031543-proto-socket-stress-quick.md`
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang kotlin --transport tcp,ws
INFO stress harness language=Kotlin mode=quick transports=tcp,ws profiles=slow-mix typeName=TestData
[slow-mix] transport=tcp mode=quick clients=4,slowEvery=5,delayMs=50 perClient=20
[slow-mix] transport=ws mode=quick clients=4,slowEvery=5,delayMs=50 perClient=20
DONE kotlin: INCOMPLETE (rows=0 skips=2 required_skips=2 violations=0)
전체 결과값: INCOMPLETE
결과 기록 파일: `agent-test/runs/20260603-031624-proto-socket-stress-quick.md`
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all
(stdout/stderr summary)
**Proto 동기화**
| schema sync | `tools/check_proto_sync.sh` | PASS |
**동일언어**
| Dart | PASS |
| Go | PASS |
| Kotlin | PASS |
| Python | PASS |
| TypeScript | PASS |
**언어 PASS 매트릭스**
| Dart.io | all clients PASS |
| Go | all clients PASS |
| Kotlin | all clients PASS |
| Python | all clients PASS |
| TypeScript | all clients PASS |
**크로스테스트 상세**
all listed directions PASS, PASS scenarios match Expected, FAIL lines 0
로그 디렉터리: `/tmp/proto-socket-matrix.URBBqh`
결과 기록 파일: `agent-test/runs/20260603-031633-proto-socket-full-matrix.md`
$ git diff --check
(no output)
```
> **[IMPLEMENTING AGENT - BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
## 코드리뷰 결과
- 종합 판정: FAIL
- 차원별 평가
- correctness: Fail
- completeness: Fail
- test coverage: Fail
- API contract: Pass
- code quality: Pass
- plan deviation: Fail
- verification trust: Fail
- 발견된 문제
- Required: `typescript/bench/stress.ts:644`, `dart/bench/stress.dart:625`, `go/bench/stress.go:734`, `python/bench/stress.py:554`, `kotlin/crosstest/stress.kt:583`에서 `slow-mix` row를 emit한 뒤에야 client close와 `pendingLeak` 검사를 수행한다. Dart/Go/Python/Kotlin은 `emitRow` 시점에 total violation을 이미 확정하므로 close 후 pending leak이 생겨도 SUMMARY가 PASS로 남고, TypeScript도 출력된 ROW의 `PendLeak`/status가 cleanup 전 값으로 기록된다. Stability hard gate의 `pending-queue leak 0` 계약을 지키려면 모든 slow-mix 구현에서 close와 leak 검사까지 끝낸 뒤 row/SUMMARY에 반영되도록 emit 순서를 옮기거나 cleanup 전용 row 값을 재계산해야 한다.
- Required: `go/bench/stress.go:693`의 Go slow-mix는 client별 goroutine 안에서 `for index := 0; index < perClient; index++`로 `SendRequestTyped`를 순차 호출한다. 따라서 같은 connection 안에 느린 request 뒤의 빠른 request가 동시에 pending 되는 상황이 없어서, 계획의 "느린 handler가 같은 connection FIFO를 계약대로 지연"시키는 축을 Go row가 검증하지 못한 채 PASS를 낼 수 있다. Go에서 결정적 same-connection in-flight 요청을 만들 수 있게 보강하거나, public request API 제약으로 불가능하면 Go slow-mix를 required SKIP/INCOMPLETE로 기록해야 한다.
- 다음 단계: FAIL 후속 PLAN/CODE_REVIEW를 작성한다. USER_REVIEW gate는 트리거하지 않는다.

View file

@ -0,0 +1,221 @@
<!-- task=m-high-performance-parallel-operations/05+04_slow_mix plan=1 tag=REVIEW_TEST -->
# Code Review Reference - REVIEW_TEST
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.
## 개요
date=2026-06-03
task=m-high-performance-parallel-operations/05+04_slow_mix, plan=1, tag=REVIEW_TEST
## Roadmap Targets
- Milestone: `agent-roadmap/milestones/high-performance-parallel-operations.md`
- Task ids:
- `slow-mix`: 빠른 handler와 느린 handler가 섞인 혼합 부하 추가
- Completion mode: check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
1. 판정을 append한다.
2. `CODE_REVIEW-{review_lane}-GNN.md` → `code_review_{review_lane}_GNN_N.log`, `PLAN-{build_lane}-GNN.md` → `plan_{build_lane}_GNN_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다.
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_TEST-1] Cleanup gate before row | [x] |
| [REVIEW_TEST-2] Go same-connection slow/fast verification | [x] |
## 구현 체크리스트
- [x] [REVIEW_TEST-1] TypeScript/Dart/Go/Python/Kotlin `slow-mix`에서 client close와 pending leak 검사를 row/SUMMARY emission 전에 반영한다.
- [x] [REVIEW_TEST-2] Go `slow-mix`가 같은 connection의 in-flight slow/fast request FIFO 지연을 실제로 검증하도록 보강한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G08_1.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G08_1.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하여 plan/review/archive 산출물이 추적 가능한지 확인한다.
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
## 계획 대비 변경 사항
- Go `slow-mix`는 deterministic PASS row 대신 required SKIP/INCOMPLETE로 전환했다. public `SendRequestTyped`는 request enqueue 완료 시점을 노출하지 않고, concurrent request completion order는 goroutine scheduling 영향을 받는다. 서버가 connection별 arrival `seq`를 응답에 넣는 실험에서도 completion order false positive가 발생해 deterministic response-arrival FIFO 검증 근거로 쓸 수 없었다.
- TypeScript focused와 Go focused를 최초 병렬 실행하면서 timestamp가 같은 결과 파일명이 충돌했다. TypeScript focused는 단독 재실행해 `agent-test/runs/20260603-035913-proto-socket-stress-quick.md`로 보존했다.
## 주요 설계 결정
- TypeScript/Dart/Python/Kotlin의 executable `slow-mix` row는 client close와 pending leak 검사 이후에 emit하도록 순서를 보정했다. 따라서 ROW와 SUMMARY 모두 cleanup 이후 stability 값을 기준으로 한다.
- Go는 `slow-mix` profile name과 axis를 유지하되 required SKIP을 emit한다. 이는 PASS 위장이 아니라 현재 public API로 deterministic same-connection in-flight slow/fast 검증을 만들 수 없다는 명시적 baseline 미완결 신호다.
- Kotlin TCP/WS deferred bottleneck 정책은 유지했다. Kotlin `slow-mix`는 기존과 같이 required SKIP 2개와 INCOMPLETE를 기록한다.
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- `slow-mix` row/SUMMARY가 client close와 pending leak 검사 이후의 stability 값을 기록하는지 확인한다.
- Go `slow-mix`가 같은 connection 안에 slow/fast request가 겹친 검증 근거 없이 PASS row를 내지 않는지 확인한다.
- Kotlin TCP/WS deferred bottleneck은 기존 required SKIP/INCOMPLETE 정책을 유지하는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.
### REVIEW_TEST-1 중간 검증
```text
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang typescript --transport tcp
RUN stress lang=typescript mode=quick transports=tcp profiles=slow-mix
INFO stress harness language=TypeScript mode=quick transports=tcp profiles=slow-mix typeName=TestData
[slow-mix] transport=tcp mode=quick clients=4,slowEvery=5,delayMs=50 perClient=20
[slow-mix] transport=tcp clients=4 done peakMemMb=83.9 violations=0
DONE typescript: PASS (rows=1 skips=0 required_skips=0 violations=0)
전체 결과값: PASS
결과 기록 파일: `agent-test/runs/20260603-035913-proto-socket-stress-quick.md`
```
### REVIEW_TEST-2 중간 검증
```text
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang go --transport tcp
RUN stress lang=go mode=quick transports=tcp profiles=slow-mix
INFO stress harness language=Go mode=quick transports=tcp profiles=slow-mix typeName=TestData
[slow-mix] transport=tcp mode=quick clients=4,slowEvery=5,delayMs=50 perClient=20
[slow-mix] transport=tcp deferred=deferred: optimize Epic — Go public request API does not expose deterministic same-connection in-flight slow/fast response-arrival order; concurrent SendRequestTyped completion order is goroutine-scheduling dependent
DONE go: INCOMPLETE (rows=0 skips=1 required_skips=1 violations=0)
전체 결과값: INCOMPLETE
결과 기록 파일: `agent-test/runs/20260603-035334-proto-socket-stress-quick.md`
```
### 최종 검증
```text
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang typescript --transport tcp
RUN stress lang=typescript mode=quick transports=tcp profiles=slow-mix
INFO stress harness language=TypeScript mode=quick transports=tcp profiles=slow-mix typeName=TestData
[slow-mix] transport=tcp mode=quick clients=4,slowEvery=5,delayMs=50 perClient=20
[slow-mix] transport=tcp clients=4 done peakMemMb=83.9 violations=0
DONE typescript: PASS (rows=1 skips=0 required_skips=0 violations=0)
전체 결과값: PASS
결과 기록 파일: `agent-test/runs/20260603-035913-proto-socket-stress-quick.md`
$ cd typescript && npm run check && npm test
(stdout/stderr)
> proto-socket@1.0.5 check
> tsc --noEmit
> proto-socket@1.0.5 test
> vitest run
Test Files 5 passed (5)
Tests 50 passed (50)
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang dart,go,python,typescript --transport tcp
RUN stress lang=dart mode=quick transports=tcp profiles=slow-mix
DONE dart: PASS (rows=1 skips=0 required_skips=0 violations=0)
RUN stress lang=go mode=quick transports=tcp profiles=slow-mix
DONE go: INCOMPLETE (rows=0 skips=1 required_skips=1 violations=0)
RUN stress lang=python mode=quick transports=tcp profiles=slow-mix
DONE python: PASS (rows=1 skips=0 required_skips=0 violations=0)
RUN stress lang=typescript mode=quick transports=tcp profiles=slow-mix
DONE typescript: PASS (rows=1 skips=0 required_skips=0 violations=0)
전체 결과값: INCOMPLETE
결과 기록 파일: `agent-test/runs/20260603-035342-proto-socket-stress-quick.md`
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang kotlin --transport tcp,ws
INFO stress harness language=Kotlin mode=quick transports=tcp,ws profiles=slow-mix typeName=TestData
[slow-mix] transport=tcp mode=quick clients=4,slowEvery=5,delayMs=50 perClient=20
[slow-mix] transport=ws mode=quick clients=4,slowEvery=5,delayMs=50 perClient=20
DONE kotlin: INCOMPLETE (rows=0 skips=2 required_skips=2 violations=0)
전체 결과값: INCOMPLETE
결과 기록 파일: `agent-test/runs/20260603-035352-proto-socket-stress-quick.md`
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all
(stdout/stderr summary)
**Proto 동기화**
| schema sync | `tools/check_proto_sync.sh` | PASS |
**동일언어**
| Dart | PASS |
| Go | PASS |
| Kotlin | PASS |
| Python | PASS |
| TypeScript | PASS |
**언어 PASS 매트릭스**
| Dart.io | all clients PASS |
| Go | all clients PASS |
| Kotlin | all clients PASS |
| Python | all clients PASS |
| TypeScript | all clients PASS |
**크로스테스트 상세**
all listed directions PASS, PASS scenarios match Expected, FAIL lines 0
로그 디렉터리: `/tmp/proto-socket-matrix.TlRMyN`
결과 기록 파일: `agent-test/runs/20260603-035403-proto-socket-full-matrix.md`
$ git diff --check
(no output)
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.
## 코드리뷰 결과
- 종합 판정: PASS
- 차원별 평가
- correctness: Pass
- completeness: Pass
- test coverage: Pass
- API contract: Pass
- code quality: Pass
- plan deviation: Pass
- verification trust: Pass
- 발견된 문제: 없음
- 다음 단계: PASS이므로 `complete.log`를 작성하고 task directory를 archive로 이동한다.

View file

@ -0,0 +1,47 @@
# Complete - m-high-performance-parallel-operations/05+04_slow_mix
## 완료 일시
2026-06-03
## 요약
slow-mix profile 추가와 follow-up 검증 신뢰 보강을 2회 리뷰 루프로 완료했다. 최종 판정은 PASS다.
## 루프 이력
| Plan | Review | Verdict | 메모 |
|------|--------|---------|------|
| `plan_cloud_G08_0.log` | `code_review_cloud_G08_0.log` | FAIL | cleanup 전 row emit으로 pendingLeak hard gate가 거짓 PASS를 낼 수 있었고, Go sequential loop가 same-connection in-flight slow/fast 축을 검증하지 못해 follow-up 생성 |
| `plan_cloud_G08_1.log` | `code_review_cloud_G08_1.log` | PASS | cleanup 이후 row/SUMMARY emission으로 보정하고, Go slow-mix를 deterministic 검증 불가 required SKIP/INCOMPLETE로 전환해 PASS 위장을 제거 |
## 구현/정리 내용
- `slow-mix` profile을 TypeScript, Dart, Go, Python, Kotlin stress harness와 `run_stress.sh --help` usage에 추가했다.
- TypeScript/Dart/Python executable slow-mix row는 mixed fast/slow handler load에서 cleanup 이후 stability counters를 기록한다.
- Go slow-mix는 public request API로 deterministic same-connection in-flight slow/fast 검증을 만들 수 없어 required SKIP/INCOMPLETE로 기록한다.
- Kotlin slow-mix는 기존 TCP delayed-ACK/WS inbound FIFO deferred bottleneck 정책을 보존해 required SKIP/INCOMPLETE로 기록한다.
## 최종 검증
- `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang typescript --transport tcp` - PASS; result=`agent-test/runs/20260603-035913-proto-socket-stress-quick.md`; TypeScript slow-mix TCP row PASS, stability violations 0.
- `cd typescript && npm run check && npm test` - PASS; `tsc --noEmit`, Vitest 5 files / 50 tests passed.
- `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang dart,go,python,typescript --transport tcp` - INCOMPLETE by intended Go required SKIP; result=`agent-test/runs/20260603-035342-proto-socket-stress-quick.md`; Dart/Python/TypeScript rows PASS, Go required SKIP 1, stability violations 0.
- `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang kotlin --transport tcp,ws` - INCOMPLETE by existing Kotlin required SKIP; result=`agent-test/runs/20260603-035352-proto-socket-stress-quick.md`; required SKIP 2, stability violations 0.
- `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all` - PASS; result=`agent-test/runs/20260603-035403-proto-socket-full-matrix.md`; proto sync, same-language tests, language PASS matrix, and crosstest details all PASS.
- `git diff --check` - PASS; no output.
## Roadmap Completion
- Milestone: `agent-roadmap/milestones/high-performance-parallel-operations.md`
- Completed task ids:
- `slow-mix`: PASS; evidence=`plan_cloud_G08_1.log`, `code_review_cloud_G08_1.log`; verification=`agent-test/runs/20260603-035913-proto-socket-stress-quick.md`, `agent-test/runs/20260603-035342-proto-socket-stress-quick.md`, `agent-test/runs/20260603-035352-proto-socket-stress-quick.md`, `agent-test/runs/20260603-035403-proto-socket-full-matrix.md`
- Not completed task ids: 없음
## 잔여 Nit
- 없음
## 후속 작업
- Go/Kotlin executable slow-mix baseline은 optimize Epic의 deferred bottleneck 해소 후 required SKIP을 실제 row로 전환할 수 있다.

View file

@ -0,0 +1,194 @@
<!-- task=m-high-performance-parallel-operations/05+04_slow_mix plan=1 tag=REVIEW_TEST -->
# Plan - REVIEW_TEST
## 이 파일을 읽는 구현 에이전트에게
`CODE_REVIEW-cloud-G08.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 검증 명령, stdout/stderr로 채우는 것이 구현의 마지막 단계다. 이전 리뷰는 `code_review_cloud_G08_0.log`이며, Required 2건만 해결한다. archive, `complete.log`, 최종 판정은 code-review 전용이다.
## 배경
첫 리뷰에서 `slow-mix` row가 cleanup 전에 emit되어 `pendingLeak` hard gate가 결과에 반영되지 않는 문제가 발견됐다. 또한 Go `slow-mix`는 같은 connection 안에서 요청을 순차로만 보내므로 느린 handler 뒤에 빠른 요청이 pending 되는 FIFO 지연 축을 검증하지 못한다. 이번 후속은 새 profile의 검증 신뢰를 회복하는 데만 집중한다.
## 사용자 리뷰 요청 흐름
구현 중 blocker는 active `CODE_REVIEW-cloud-G08.md`의 `사용자 리뷰 요청` 섹션에 기록한다. code-review가 그 요청을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 판단한다.
## Roadmap Targets
- Milestone: `agent-roadmap/milestones/high-performance-parallel-operations.md`
- Task ids:
- `slow-mix`: 빠른 handler와 느린 handler가 섞인 혼합 부하 추가
- Completion mode: check-on-pass
## 분석 결과
### 읽은 파일
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-roadmap/current.md`
- `agent-roadmap/milestones/high-performance-parallel-operations.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-test/local/rules.md`
- `agent-test/local/proto-socket-full-matrix.md`
- `agent-test/local/typescript-smoke.md`
- `agent-task/m-high-performance-parallel-operations/05+04_slow_mix/plan_cloud_G08_0.log`
- `agent-task/m-high-performance-parallel-operations/05+04_slow_mix/code_review_cloud_G08_0.log`
- `agent-task/archive/2026/06/m-high-performance-parallel-operations/04+01_load_profile_scales/complete.log`
- `agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh`
- `typescript/bench/stress.ts`
- `dart/bench/stress.dart`
- `go/bench/stress.go`
- `python/bench/stress.py`
- `kotlin/crosstest/stress.kt`
- `typescript/src/communicator.ts`
- `dart/lib/src/communicator.dart`
- `go/communicator.go`
- `python/proto_socket/communicator.py`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/Communicator.kt`
- `PROTOCOL.md`
- `agent-test/runs/20260603-031533-proto-socket-stress-quick.md`
- `agent-test/runs/20260603-031543-proto-socket-stress-quick.md`
- `agent-test/runs/20260603-031624-proto-socket-stress-quick.md`
- `agent-test/runs/20260603-031633-proto-socket-full-matrix.md`
### 테스트 환경 규칙
`test_env=local`. `agent-test/local/rules.md`를 읽었고 project-wide/full-cycle은 `agent-test/local/proto-socket-full-matrix.md`, TypeScript 변경은 `agent-test/local/typescript-smoke.md`가 매칭된다. 후속 검증은 focused `run_stress.sh --profile slow-mix`, TypeScript smoke, 전체 matrix, `git diff --check`를 사용한다.
### 테스트 커버리지 공백
- `slow-mix` cleanup leak: 현재 row emit이 close/leak 검사보다 먼저라 close 후 leak을 결과 row와 일부 SUMMARY가 보지 못한다.
- Go same-connection FIFO delay: 현재 Go row는 client별 순차 `SendRequestTyped` 루프라 같은 connection의 in-flight slow/fast mix를 검증하지 못한다.
### 심볼 참조
renamed/removed symbol 없음.
### 분할 판단
split policy를 재평가했다. 이 작업은 기존 `05+04_slow_mix` 첫 리뷰의 Required 후속이고, 같은 파일 집합 안에서 row contract를 보강하는 좁은 수정이다. 별도 subtask로 나누면 같은 profile 계약을 둘로 쪼개 검증이 중복되므로 단일 follow-up plan으로 유지한다.
### 범위 결정 근거
wire format, proto schema, nonce 의미, production communicator API 변경은 제외한다. Kotlin deferred bottleneck 자체 수정은 optimize Epic 범위라 이번 후속에서 해결하지 않는다. `slow-mix` profile의 결과 신뢰와 Go stress harness 보강만 다룬다.
### 빌드 등급
cloud-G08. concurrency stress harness와 verification trust 회복이 핵심이고, 첫 리뷰 Required가 테스트 결과의 PASS 신뢰성에 직접 닿는다.
## 구현 체크리스트
- [ ] [REVIEW_TEST-1] TypeScript/Dart/Go/Python/Kotlin `slow-mix`에서 client close와 pending leak 검사를 row/SUMMARY emission 전에 반영한다.
- [ ] [REVIEW_TEST-2] Go `slow-mix`가 같은 connection의 in-flight slow/fast request FIFO 지연을 실제로 검증하도록 보강한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## [REVIEW_TEST-1] Cleanup Gate Before Row
### 문제
`slow-mix` row emission이 cleanup보다 먼저 실행된다.
Before:
```ts
// typescript/bench/stress.ts:644
summarizeLatencies("slow-mix", axis, latencies, performance.now() - started, stability, "off", peakMem, {
transport: transportName(transport),
payloadBytes: testDataPayloadBytes("sm-0-0"),
clientCount,
});
// ...
if (!leakClear(client)) {
stability.pendingLeak += 1;
}
```
같은 패턴이 `dart/bench/stress.dart:625`, `go/bench/stress.go:734`, `python/bench/stress.py:554`, `kotlin/crosstest/stress.kt:583`에도 있다.
### 해결 방법
각 `slow-mix` 구현에서 요청 완료 후 바로 row를 emit하지 말고, client close와 `pendingLeak` 검사까지 끝낸 뒤 row를 emit한다. TypeScript는 printed ROW와 SUMMARY가 같은 최종 stability 값을 보도록 `summarizeLatencies` 호출을 cleanup 이후로 옮긴다. Dart/Go/Python/Kotlin은 `_totalViolations`/`totalViol` 누적이 emit 시점 값이므로 반드시 cleanup 이후에 `_emitRow`/`emitRow`가 호출되어야 한다.
### 수정 파일 및 체크리스트
- [ ] `typescript/bench/stress.ts` slow-mix cleanup 순서를 보정한다.
- [ ] `dart/bench/stress.dart` slow-mix cleanup 순서를 보정한다.
- [ ] `go/bench/stress.go` slow-mix cleanup 순서를 보정한다.
- [ ] `python/bench/stress.py` slow-mix cleanup 순서를 보정한다.
- [ ] `kotlin/crosstest/stress.kt` slow-mix cleanup 순서를 보정한다.
### 테스트 작성
별도 unit test는 작성하지 않는다. 이 파일들이 stress result producer 자체이므로 focused stress 결과 row가 regression artifact다.
### 중간 검증
```bash
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang typescript --transport tcp
```
기대: TypeScript slow-mix row PASS, `PendLeak` 0, SUMMARY `stability_violations=0`, 결과 파일 기록.
## [REVIEW_TEST-2] Go Same-Connection Slow/Fast Verification
### 문제
[go/bench/stress.go](/config/workspace/proto-socket/go/bench/stress.go:693)의 Go slow-mix는 client별 goroutine 안에서 request를 하나 보내고 응답을 기다린 뒤 다음 request를 보낸다. 이 방식은 cross-connection concurrency는 만들지만, 같은 connection 안에서 느린 request 뒤 빠른 request가 이미 pending인 상황을 만들지 못한다.
### 해결 방법
Go slow-mix가 PASS row를 낼 때는 같은 connection에 slow/fast 요청이 겹친 근거가 있어야 한다. 선호 구현은 bench-only helper를 추가해 한 connection에서 여러 request를 in-flight로 만들고, 느린 index 뒤 fast index가 먼저 완료되면 `fifoViolations`를 증가시키는 방식이다. public request API만으로 deterministic send order를 만들 수 없다는 것이 구현 중 확인되면, Go slow-mix는 PASS row 대신 required SKIP/INCOMPLETE를 emit하고 `계획 대비 변경 사항`에 public API 제약과 재개 조건을 남긴다. 단, sequential per-client request loop를 PASS로 유지하면 안 된다.
### 수정 파일 및 체크리스트
- [ ] `go/bench/stress.go` slow-mix가 same-connection in-flight slow/fast 축을 검증하도록 보강한다.
- [ ] deterministic in-flight 검증이 불가능하면 Go slow-mix를 required SKIP으로 바꾸고, runner 결과가 PASS가 아닌 INCOMPLETE로 남는지 확인한다.
- [ ] Dart/TypeScript/Python/Kotlin의 slow-mix semantics와 result axis는 변경하지 않는다.
### 테스트 작성
별도 unit test는 작성하지 않는다. Go stress harness의 focused `slow-mix` row 또는 required SKIP 결과를 검증 증거로 사용한다.
### 중간 검증
```bash
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang go --transport tcp
```
기대: Go가 PASS row를 내는 경우 same-connection in-flight FIFO 검증이 실제 수행되고 stability counters가 0이다. deterministic 검증 불가로 required SKIP을 선택한 경우 전체 결과는 INCOMPLETE이며 PASS로 기록하지 않는다.
## 의존 관계 및 구현 순서
1. REVIEW_TEST-1을 먼저 적용해 result contract 신뢰를 회복한다.
2. REVIEW_TEST-2로 Go slow-mix의 PASS 의미를 보강한다.
3. 최종 검증을 실행하고 active review stub에 실제 stdout/stderr와 결과 파일 경로를 기록한다.
## 수정 파일 요약
| 파일 | 항목 |
|---|---|
| `typescript/bench/stress.ts` | REVIEW_TEST-1 |
| `dart/bench/stress.dart` | REVIEW_TEST-1 |
| `go/bench/stress.go` | REVIEW_TEST-1, REVIEW_TEST-2 |
| `python/bench/stress.py` | REVIEW_TEST-1 |
| `kotlin/crosstest/stress.kt` | REVIEW_TEST-1 |
## 최종 검증
```bash
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang typescript --transport tcp
cd typescript && npm run check && npm test
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang dart,go,python,typescript --transport tcp
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang kotlin --transport tcp,ws
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all
git diff --check
```
기대: focused TypeScript slow-mix PASS, TypeScript smoke PASS, Dart/Go/Python/TypeScript quick TCP slow-mix PASS 또는 Go가 deterministic 검증 불가를 증명한 required SKIP/INCOMPLETE, Kotlin 기존 required SKIP/INCOMPLETE 유지, full matrix PASS, whitespace 오류 없음.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -1,113 +0,0 @@
<!-- task=m-high-performance-parallel-operations/05+04_slow_mix plan=0 tag=TEST -->
# Code Review Reference - TEST
> **[IMPLEMENTING AGENT - READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only.
## 개요
date=2026-06-03
task=m-high-performance-parallel-operations/05+04_slow_mix, plan=0, tag=TEST
## Roadmap Targets
- Milestone: `agent-roadmap/milestones/high-performance-parallel-operations.md`
- Task ids:
- `slow-mix`: 빠른 handler와 느린 handler가 섞인 혼합 부하 추가
- Completion mode: check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [TEST-1] Runner profile contract | [ ] |
| [TEST-2] Slow mix harness | [ ] |
| [TEST-3] Verification | [ ] |
## 구현 체크리스트
- [ ] [TEST-1] `run_stress.sh` usage와 result contract에 `slow-mix` profile을 추가한다.
- [ ] [TEST-2] Dart/Go/Kotlin/Python/TypeScript harness에 `slow-mix` profile을 추가한다. 검증: 느린 handler가 같은 connection FIFO를 계약대로 지연시키되 다른 connection의 nonce/FIFO 상태를 오염시키지 않는다.
- [ ] [TEST-3] focused slow-mix stress, TypeScript smoke, 전체 matrix를 실행하고 결과 파일 경로를 review stub에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
- [ ] `코드리뷰 결과``PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] active review/plan을 `.log`로 아카이브한다.
- [ ] PASS이면 `complete.log`를 작성하고 task directory를 archive로 이동한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- `04+01_load_profile_scales` dependency가 complete log로 충족된 뒤 구현됐는지 확인한다.
- slow handler가 same-connection delay는 허용하되 cross-connection nonce/FIFO counters를 오염시키지 않는지 확인한다.
- 새 profile이 unknown/optional SKIP으로 빠지지 않고 실제 row 또는 명확한 required SKIP을 남기는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
### TEST-1 중간 검증
```text
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --help
(output)
```
### TEST-2 중간 검증
```text
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang typescript --transport tcp
(output)
```
### 최종 검증
```text
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang typescript --transport tcp
(output)
$ cd typescript && npm run check && npm test
(output)
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --profile slow-mix --lang dart,go,python,typescript --transport tcp
(output)
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all
(output)
$ git diff --check
(output)
```
> **[IMPLEMENTING AGENT - BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**

View file

@ -18,7 +18,14 @@ import 'package:proto_socket/proto_socket.dart';
const _host = '127.0.0.1';
const _language = 'Dart';
const _wsPath = '/';
const _allProfiles = ['roundtrip', 'burst', 'sustained', 'parallel', 'payload'];
const _allProfiles = [
'roundtrip',
'burst',
'sustained',
'parallel',
'slow-mix',
'payload'
];
// payload size matrix . quick은 (1KB/64KB), full은 Milestone (1KB/64KB/1MB) .
const _payloadSeed = '0123456789abcdef';
@ -275,6 +282,20 @@ _ServerHandle _makeEchoServer(String transport, int port) {
});
}
_ServerHandle _makeSlowMixServer(
String transport, int port, int slowEvery, Duration delay) {
return _makeServer(transport, port, (comm) {
comm.addRequestListener<TestData, TestData>((req) async {
if ((req.index + 1) % slowEvery == 0) {
await Future<void>.delayed(delay);
}
return TestData()
..index = req.index * 2
..message = 'echo:${req.message}';
});
});
}
Future<_ClientHandle> _dial(String transport, int port) async {
if (transport == 'tcp') {
final socket = await ProtobufClient.connect(_host, port)
@ -540,6 +561,94 @@ Future<void> _profileParallel(String transport, String mode) async {
}
}
/// / request handler를 same-connection FIFO delay와 connection nonce .
Future<void> _profileSlowMix(String transport, String mode) async {
final clientCount = mode == 'quick' ? 4 : 16;
final perClient = mode == 'quick' ? 20 : 200;
final slowEvery = mode == 'quick' ? 5 : 10;
final delayMs = mode == 'quick' ? 50 : 100;
final timeout = Duration(seconds: mode == 'quick' ? 10 : 30);
final axis = 'clients=$clientCount,slowEvery=$slowEvery,delayMs=$delayMs';
final s = _Stability();
var peak = _memMb();
final latencies = <double>[];
var elapsedMs = 0.0;
_log('[slow-mix] transport=$transport mode=$mode $axis perClient=$perClient');
final port = await _freePort();
final server = _makeSlowMixServer(
transport, port, slowEvery, Duration(milliseconds: delayMs));
await server.start();
final handles = <_ClientHandle>[];
final clientIds = <int>[];
try {
for (var clientId = 0; clientId < clientCount; clientId++) {
try {
handles.add(await _dialWithRetry(transport, port));
clientIds.add(clientId);
} catch (error) {
s.timeouts += 1;
_log('[slow-mix] client=$clientId dial error=$error');
}
}
final completedByClient = List<int>.filled(clientCount, -1);
final sw = Stopwatch()..start();
await Future.wait(List.generate(handles.length, (slot) async {
final handle = handles[slot];
final clientId = clientIds[slot];
await Future.wait(List.generate(perClient, (index) async {
final message = 'sm-$clientId-$index';
final reqSw = Stopwatch()..start();
try {
final res = await handle.comm
.sendRequest<TestData, TestData>(TestData()
..index = index
..message = message)
.timeout(timeout);
latencies.add(reqSw.elapsedMicroseconds / 1000.0);
if (res.index != index * 2 || res.message != 'echo:$message') {
s.nonceMismatch += 1;
}
if (index <= completedByClient[clientId]) {
s.fifoViolations += 1;
}
completedByClient[clientId] = index;
} catch (error) {
_classifyRequestError(error.toString(), s);
}
}));
}));
final cur = _memMb();
if (cur > peak) peak = cur;
elapsedMs = sw.elapsedMicroseconds / 1000.0;
} finally {
for (final handle in handles) {
await handle.close();
if (handle.comm.isAlive) s.pendingLeak += 1;
}
await server.stop();
}
final sorted = List<double>.from(latencies)..sort();
final throughput =
elapsedMs > 0 ? latencies.length / elapsedMs * 1000.0 : 0.0;
_emitRow(
profile: 'slow-mix',
axis: axis,
transport: transport,
payloadBytes: _testDataPayloadBytes('sm-0-0'),
clientCount: clientCount,
requests: latencies.length,
throughput: throughput,
p50: _percentile(sorted, 50),
p95: _percentile(sorted, 95),
p99: _percentile(sorted, 99),
mem: peak,
s: s,
);
_log('[slow-mix] transport=$transport clients=$clientCount done '
'peakMemMb=${peak.toStringAsFixed(1)} violations=${s.violations()}');
}
/// payload size matrix. connection request-response를 1KB/64KB/1MB payload에서 .
/// message field를 deterministic filler로 serialized payload bytes, payload별
/// p50/p95/p99 latency, throughput, peak RSS, stability counters를 row에 .
@ -648,6 +757,7 @@ final _runners = <String, Future<void> Function(String, String)>{
'burst': _profileBurst,
'sustained': _profileSustained,
'parallel': _profileParallel,
'slow-mix': _profileSlowMix,
'payload': _profilePayload,
};

View file

@ -38,7 +38,7 @@ const (
transWS = "ws"
)
var allProfiles = []string{"roundtrip", "burst", "sustained", "parallel", "payload"}
var allProfiles = []string{"roundtrip", "burst", "sustained", "parallel", "slow-mix", "payload"}
// payload size matrix 축. quick은 작은 샘플(1KB/64KB), full은 Milestone 기준(1KB/64KB/1MB)을 측정한다.
const payloadSeed = "0123456789abcdef"
@ -623,6 +623,24 @@ func profileParallel(transport string, mode string) {
}
}
func profileSlowMix(transport string, mode string) {
clientCount := 4
perClient := 20
slowEvery := 5
delayMs := 50
if mode == "full" {
clientCount = 16
perClient = 200
slowEvery = 10
delayMs = 100
}
axis := fmt.Sprintf("clients=%d,slowEvery=%d,delayMs=%d", clientCount, slowEvery, delayMs)
logf("[slow-mix] transport=%s mode=%s %s perClient=%d", transport, mode, axis, perClient)
reason := "deferred: optimize Epic — Go public request API does not expose deterministic same-connection in-flight slow/fast response-arrival order; concurrent SendRequestTyped completion order is goroutine-scheduling dependent"
emitSkip("slow-mix", axis, transport, reason)
logf("[slow-mix] transport=%s deferred=%s", transport, reason)
}
// profilePayload는 payload size matrix를 측정한다. 동일 connection request-response를 1KB/64KB/1MB
// payload에서 돌리고, message field를 deterministic filler로 채워 실제 serialized payload bytes,
// payload별 p50/p95/p99 latency, throughput, peak heap, stability counters를 결과 row에 남긴다.
@ -801,6 +819,7 @@ func main() {
"burst": profileBurst,
"sustained": profileSustained,
"parallel": profileParallel,
"slow-mix": profileSlowMix,
"payload": profilePayload,
}

View file

@ -27,6 +27,7 @@ import com.tokilabs.proto_socket.typeNameOf
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
@ -41,7 +42,7 @@ import kotlin.system.exitProcess
private const val HOST = "127.0.0.1"
private const val LANGUAGE = "Kotlin"
private const val WS_PATH = "/"
private val ALL_PROFILES = listOf("roundtrip", "burst", "sustained", "parallel", "payload")
private val ALL_PROFILES = listOf("roundtrip", "burst", "sustained", "parallel", "slow-mix", "payload")
// payload size matrix 축. quick은 작은 샘플(1KB/64KB), full은 Milestone 기준(1KB/64KB/1MB)을 측정한다.
private const val PAYLOAD_SEED = "0123456789abcdef"
@ -172,8 +173,8 @@ private fun deferredBottleneck(transport: String, profile: String, concurrency:
// stability hard gate를 안정적으로 통과하지 못하므로 lang-baseline에서는 deferred로 기록한다.
return "deferred: optimize Epic — Kotlin TcpClient without TCP_NODELAY is delayed-ACK latency-bound and intermittently exceeds the stability timeout under request/response concurrency"
}
if (transport == "ws" && profile == "burst") {
return "deferred: optimize Epic — WS inbound launch-per-frame on Dispatchers.IO breaks per-connection FIFO under burst"
if (transport == "ws" && (profile == "burst" || profile == "slow-mix")) {
return "deferred: optimize Epic — WS inbound launch-per-frame on Dispatchers.IO breaks per-connection FIFO under burst or mixed slow handlers"
}
if (transport == "ws" && concurrency != null && concurrency > 64) {
return "deferred: optimize Epic — WS inbound staging cap (64) disconnects at concurrency>64"
@ -242,6 +243,14 @@ private fun startEchoServer(transport: String, port: Int): StressServerHandle =
}
}
private fun startSlowMixServer(transport: String, port: Int, slowEvery: Int, delayMs: Long): StressServerHandle =
startServer(transport, port) { comm ->
addRequestListenerTyped<TestData, TestData>(comm) { req ->
if ((req.index + 1) % slowEvery == 0) delay(delayMs)
TestData.newBuilder().setIndex(req.index * 2).setMessage("echo:" + req.message).build()
}
}
private suspend fun dial(transport: String, port: Int): StressClientHandle = when (transport) {
"tcp" -> {
val c = DialTcp(HOST, port, 0, 0, parserMap())
@ -306,6 +315,34 @@ private suspend fun sendPayloadOne(
}
}
private suspend fun sendSlowMixOne(
comm: Communicator,
clientId: Int,
index: Int,
timeoutMs: Long,
completedByClient: Array<AtomicInteger>,
latencies: ConcurrentLinkedQueue<Double>,
s: StressStability,
) {
val message = "sm-$clientId-$index"
val started = System.nanoTime()
try {
val res: TestData = com.tokilabs.proto_socket.sendRequestTyped(
comm, TestData.newBuilder().setIndex(index).setMessage(message).build(), timeoutMs,
)
latencies.add((System.nanoTime() - started) / 1e6)
if (res.index != index * 2 || res.message != "echo:$message") {
s.nonceMismatch.incrementAndGet()
}
if (index <= completedByClient[clientId].get()) {
s.fifoViolations.incrementAndGet()
}
completedByClient[clientId].set(index)
} catch (error: Throwable) {
classifyRequestError(error, s)
}
}
private suspend fun runRequestLoad(comm: Communicator, total: Int, concurrency: Int, timeoutMs: Long, s: StressStability): List<Double> {
val latencies = ConcurrentLinkedQueue<Double>()
var issued = 0
@ -499,6 +536,65 @@ private suspend fun profileParallel(transport: String, mode: String) {
}
}
private suspend fun profileSlowMix(transport: String, mode: String) {
val clientCount = if (mode == "quick") 4 else 16
val perClient = if (mode == "quick") 20 else 200
val slowEvery = if (mode == "quick") 5 else 10
val delayMs = if (mode == "quick") 50L else 100L
val timeoutMs = if (mode == "quick") 10_000L else 30_000L
val axis = "clients=$clientCount,slowEvery=$slowEvery,delayMs=$delayMs"
log("[slow-mix] transport=$transport mode=$mode $axis perClient=$perClient")
val deferred = deferredBottleneck(transport, "slow-mix", perClient)
if (deferred != null) {
emitSkip("slow-mix", axis, transport, deferred)
return
}
val s = StressStability()
var peak = memMb()
val srv = startSlowMixServer(transport, freePort(), slowEvery, delayMs)
val handles = mutableListOf<Pair<Int, StressClientHandle>>()
val latencies = ConcurrentLinkedQueue<Double>()
var elapsedMs = 0.0
try {
for (clientId in 0 until clientCount) {
try {
handles.add(clientId to dialWithRetry(transport, srv.port))
} catch (error: Throwable) {
s.timeouts.incrementAndGet()
log("[slow-mix] client=$clientId dial error=$error")
}
}
val completedByClient = Array(clientCount) { AtomicInteger(-1) }
val started = System.nanoTime()
coroutineScope {
for ((clientId, handle) in handles) {
for (index in 0 until perClient) {
launch(Dispatchers.Default) {
sendSlowMixOne(handle.comm, clientId, index, timeoutMs, completedByClient, latencies, s)
}
}
}
}
val cur = memMb()
if (cur > peak) peak = cur
elapsedMs = (System.nanoTime() - started) / 1e6
} finally {
for ((_, handle) in handles) {
handle.close()
if (handle.comm.isAlive()) s.pendingLeak.incrementAndGet()
}
srv.stop()
}
val sorted = latencies.toList().sorted()
val throughput = if (elapsedMs > 0) latencies.size / elapsedMs * 1000.0 else 0.0
emitRow(
"slow-mix", axis, transport, testDataPayloadBytes("sm-0-0"), clientCount, latencies.size,
throughput, percentile(sorted, 50.0), percentile(sorted, 95.0), percentile(sorted, 99.0), peak, s,
)
log("[slow-mix] transport=$transport clients=$clientCount done peakMemMb=${String.format("%.1f", peak)} violations=${s.violations()}")
}
// profilePayload는 payload size matrix를 측정한다. 동일 connection request-response를 1KB/64KB/1MB
// payload에서 돌리고, message field를 deterministic filler로 채워 실제 serialized payload bytes,
// payload별 p50/p95/p99 latency, throughput, used heap, stability counters를 결과 row에 남긴다.
@ -595,6 +691,7 @@ fun main(args: Array<String>) = runBlocking {
"burst" to ::profileBurst,
"sustained" to ::profileSustained,
"parallel" to ::profileParallel,
"slow-mix" to ::profileSlowMix,
"payload" to ::profilePayload,
)

View file

@ -31,7 +31,7 @@ from proto_socket.ws_server import WsServer
HOST = "127.0.0.1"
LANGUAGE = "Python"
WS_PATH = "/"
ALL_PROFILES = ["roundtrip", "burst", "sustained", "parallel", "payload"]
ALL_PROFILES = ["roundtrip", "burst", "sustained", "parallel", "slow-mix", "payload"]
# payload size matrix 축. quick은 작은 샘플(1KB/64KB), full은 Milestone 기준(1KB/64KB/1MB)을 측정한다.
PAYLOAD_SEED = "0123456789abcdef"
@ -282,6 +282,19 @@ def make_echo_server(transport: str, port: int):
)
def make_slow_mix_server(transport: str, port: int, slow_every: int, delay: float):
async def on_request(req: TestData) -> TestData:
if (req.index + 1) % slow_every == 0:
await asyncio.sleep(delay)
return TestData(index=req.index * 2, message="echo:" + req.message)
return make_server(
transport,
port,
lambda client: client.communicator.add_request_listener(type_name_of(TestData), on_request),
)
async def run_request_load(client, total: int, concurrency: int, timeout: float, stability: Stability) -> list[float]:
latencies: list[float] = []
issued = 0
@ -484,6 +497,88 @@ async def profile_parallel(transport: str, mode: str) -> None:
log(f"[parallel] transport={transport} clients={client_count} done violations={stability.violations()}")
async def profile_slow_mix(transport: str, mode: str) -> None:
client_count = 4 if mode == "quick" else 16
per_client = 20 if mode == "quick" else 200
slow_every = 5 if mode == "quick" else 10
delay_ms = 50 if mode == "quick" else 100
timeout = 10.0 if mode == "quick" else 30.0
axis = f"clients={client_count},slowEvery={slow_every},delayMs={delay_ms}"
stability = Stability()
peak = mem_mb()
latencies: list[float] = []
elapsed = 0.0
log(f"[slow-mix] transport={transport} mode={mode} {axis} perClient={per_client}")
port = free_port()
server = make_slow_mix_server(transport, port, slow_every, delay_ms / 1000.0)
await server.start()
clients: list[tuple[int, object]] = []
try:
for client_id in range(client_count):
try:
clients.append((client_id, await dial_with_retry(transport, port)))
except Exception as exc: # noqa: BLE001
stability.timeouts += 1
log(f"[slow-mix] client={client_id} dial error={exc}")
completed_by_client = [-1] * client_count
started = time.perf_counter()
async def one_request(client, client_id: int, index: int) -> None:
message = f"sm-{client_id}-{index}"
req_start = time.perf_counter()
try:
res = await client.communicator.send_request(
TestData(index=index, message=message), TestData, timeout=timeout
)
latencies.append((time.perf_counter() - req_start) * 1000.0)
if res.index != index * 2 or res.message != "echo:" + message:
stability.nonce_mismatch += 1
if index <= completed_by_client[client_id]:
stability.fifo_violations += 1
completed_by_client[client_id] = index
except Exception as exc: # noqa: BLE001
classify_request_error(str(exc), stability)
await asyncio.gather(
*(
one_request(client, client_id, index)
for client_id, client in clients
for index in range(per_client)
)
)
peak = max(peak, mem_mb())
elapsed = (time.perf_counter() - started) * 1000.0
finally:
for _, client in clients:
with suppress(Exception):
await asyncio.wait_for(client.close(), 2.0)
if client.communicator.is_alive():
stability.pending_leak += 1
await server.stop()
ordered = sorted(latencies)
throughput = (len(latencies) / elapsed * 1000.0) if elapsed > 0 else 0.0
emit_row(
profile="slow-mix",
axis=axis,
transport=transport,
payload_bytes=test_data_payload_bytes("sm-0-0"),
client_count=client_count,
requests=len(latencies),
throughput=throughput,
p50=percentile(ordered, 50),
p95=percentile(ordered, 95),
p99=percentile(ordered, 99),
mem=peak,
stability=stability,
)
log(
f"[slow-mix] transport={transport} clients={client_count} done "
f"peakMemMb={peak:.1f} violations={stability.violations()}"
)
async def profile_payload(transport: str, mode: str) -> None:
sizes = PAYLOAD_SIZES_QUICK if mode == "quick" else PAYLOAD_SIZES_FULL
concurrency = 4 if mode == "quick" else 8
@ -584,6 +679,7 @@ RUNNERS = {
"burst": profile_burst,
"sustained": profile_sustained,
"parallel": profile_parallel,
"slow-mix": profile_slow_mix,
"payload": profile_payload,
}

View file

@ -14,6 +14,7 @@
* - burst : 단일 connection fire-and-forget burst, dispatch //leak.
* - sustained : 일정 request-response, throughput/p95/p99/peak memory.
* - parallel : 다중 connection request-response, connection별 FIFO/nonce .
* - slow-mix : 빠른 handler와 handler가 connection FIFO/nonce .
* - gateway : communicator `onReceivedFrame` opt-in worker_threads gateway(on) vs
* inline decode fallback(off) baseline. TcpClient는 inline decode로 enqueue하므로
* gateway frame-ingest , payload에서는
@ -50,13 +51,13 @@ function newTcpClient(socket: net.Socket): TcpClient {
const HOST = "127.0.0.1";
const WS_PATH = "/";
const ALL_PROFILES = ["roundtrip", "burst", "sustained", "parallel", "payload", "gateway"] as const;
const ALL_PROFILES = ["roundtrip", "burst", "sustained", "parallel", "slow-mix", "payload", "gateway"] as const;
type Profile = (typeof ALL_PROFILES)[number];
type Mode = "quick" | "full";
/** same-language transport baseline 축. gateway profile은 transport-agnostic frame-ingest 경로라 별도다. */
const ALL_TRANSPORTS = ["tcp", "ws"] as const;
type WireTransport = (typeof ALL_TRANSPORTS)[number];
const TRANSPORT_PROFILES: ReadonlySet<Profile> = new Set(["roundtrip", "burst", "sustained", "parallel", "payload"]);
const TRANSPORT_PROFILES: ReadonlySet<Profile> = new Set(["roundtrip", "burst", "sustained", "parallel", "slow-mix", "payload"]);
/** payload size matrix 축. quick은 작은 샘플(1KB/64KB), full은 Milestone 기준(1KB/64KB/1MB)을 측정한다. */
const PAYLOAD_SIZES_QUICK = [1_024, 65_536] as const;
@ -585,6 +586,104 @@ async function profileParallel(transport: WireTransport, mode: Mode): Promise<vo
}
}
/** 빠른/느린 request handler를 섞어 same-connection FIFO delay와 connection 간 nonce 독립성을 검증한다. */
async function profileSlowMix(transport: WireTransport, mode: Mode): Promise<void> {
const clientCount = mode === "quick" ? 4 : 16;
const perClient = mode === "quick" ? 20 : 200;
const slowEvery = mode === "quick" ? 5 : 10;
const delayMs = mode === "quick" ? 50 : 100;
const timeoutMs = mode === "quick" ? 10_000 : 30_000;
const axis = `clients=${clientCount},slowEvery=${slowEvery},delayMs=${delayMs}`;
const stability = newStability();
let peakMem = memMb();
const latencies: number[] = [];
let elapsedMs = 0;
log(`[slow-mix] transport=${transport} mode=${mode} ${axis} perClient=${perClient}`);
await withSlowMixServer(transport, slowEvery, delayMs, async (port) => {
const clients: Array<{ client: BenchClient; clientId: number }> = [];
try {
for (let clientId = 0; clientId < clientCount; clientId += 1) {
try {
clients.push({ client: await connectClient(transport, port), clientId });
} catch (err) {
stability.timeouts += 1;
log(`[slow-mix] client=${clientId} dial error=${errorMessage(err)}`);
}
}
const completedByClient = Array(clientCount).fill(-1) as number[];
const started = performance.now();
await Promise.all(
clients.map(({ client, clientId }) =>
Promise.all(
Array.from({ length: perClient }, async (_, index) => {
const message = `sm-${clientId}-${index}`;
const reqStart = performance.now();
try {
const res = await client.communicator.sendRequest(
create(TestDataSchema, { index, message }),
TestDataSchema,
timeoutMs,
);
latencies.push(performance.now() - reqStart);
if (res.index !== index * 2 || res.message !== `echo:${message}`) {
stability.nonceMismatch += 1;
}
if (index <= completedByClient[clientId]) {
stability.fifoViolations += 1;
}
completedByClient[clientId] = index;
} catch (err) {
classifyRequestError(errorMessage(err), stability);
}
}),
),
),
);
peakMem = Math.max(peakMem, memMb());
elapsedMs = performance.now() - started;
} finally {
await Promise.all(clients.map(async ({ client }) => client.close()));
for (const { client } of clients) {
if (!leakClear(client)) {
stability.pendingLeak += 1;
}
}
}
});
summarizeLatencies("slow-mix", axis, latencies, elapsedMs, stability, "off", peakMem, {
transport: transportName(transport),
payloadBytes: testDataPayloadBytes("sm-0-0"),
clientCount,
});
log(`[slow-mix] transport=${transport} clients=${clientCount} done peakMemMb=${peakMem.toFixed(1)} violations=${stabilityViolations(stability)}`);
}
async function withSlowMixServer(
transport: WireTransport,
slowEvery: number,
delayMs: number,
body: (port: number) => Promise<void>,
): Promise<void> {
const server = makeServer(transport);
server.onClientConnected = (client: { communicator: Communicator }) => {
client.communicator.addRequestListener(TestDataSchema.typeName, async (msg) => {
const data = msg as TestData;
if ((data.index + 1) % slowEvery === 0) {
await sleep(delayMs);
}
return create(TestDataSchema, { index: data.index * 2, message: `echo:${data.message}` });
});
};
await server.start();
try {
await body(server.port);
} finally {
await server.stop();
}
}
/**
* payload size matrix. connection request-response를 1KB/64KB/1MB payload에서 .
* message field를 deterministic filler로 serialized payload bytes를 , payload별
@ -828,6 +927,7 @@ async function main(): Promise<void> {
burst: profileBurst,
sustained: profileSustained,
parallel: profileParallel,
"slow-mix": profileSlowMix,
payload: profilePayload,
};