install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/77-webgl-jslib-memory-rules" ~/.claude/skills/majiayu000-claude-skill-registry-77-webgl-jslib-memory-rules && rm -rf "$T"
manifest:
skills/data/77-webgl-jslib-memory-rules/SKILL.mdsource content
77-webgl-jslib-memory-rules
Status: ACTIVE AppliesTo: v10 Type: Policy / Hard Rules
Purpose
WebGL
.jslib ↔ C# 간 ptr/len/문자열 메모리 수명 규칙을 Hard Rule로 고정한다.
이 규칙을 위반하면 메모리 누수 또는 크래시가 발생한다.
Hard Rules
1. .jslib
가 _malloc
한 모든 버퍼는 C#이 반드시 Free 호출로 반환해야 함
.jslib_malloc| 이벤트 | 포인터 | Free 함수 |
|---|---|---|
| MESSAGE | | |
| ERROR/CLOSE | | |
위반 시: WASM heap 메모리 누수 → 장기 실행 시 OOM 크래시
2. C#은 WS_PollEvent
로 받은 ptr을 즉시 managed(ArrayPool)로 복사하고 ptr을 보존하지 않는다
WS_PollEvent// CORRECT var buffer = ArrayPool<byte>.Shared.Rent(dataLen); Marshal.Copy(dataPtr, buffer, 0, dataLen); // ... 사용 ... ArrayPool<byte>.Shared.Return(buffer); WS_FreeBuffer(dataPtr); // WRONG - ptr 보존 금지 _savedPtr = dataPtr; // 이후 Free 시점 불명확 → 누수/UAF
위반 시: Use-After-Free 또는 메모리 누수
3. 한 Tick에서 처리 가능한 이벤트 개수 상한이 있어야 한다
const int MaxEventsPerTick = 64; // 권장값 for (int i = 0; i < MaxEventsPerTick; i++) { if (WS_PollEvent(...) == 0) break; // 처리 }
위반 시: 대량 수신 시 프레임 드롭, 메인 스레드 블로킹
4. ToArray()
금지, ArrayPool<byte>
사용 강제
ToArray()ArrayPool<byte>// CORRECT var buffer = ArrayPool<byte>.Shared.Rent(dataLen); Marshal.Copy(dataPtr, buffer, 0, dataLen); // ... 사용 후 ... ArrayPool<byte>.Shared.Return(buffer); // WRONG byte[] buffer = new byte[dataLen]; // GC 압박 Marshal.Copy(dataPtr, buffer, 0, dataLen);
위반 시: GC 압박으로 프레임 스파이크, 핫 경로 allocation
권장 구현 패턴
MESSAGE 처리
case EventType.MESSAGE: // 1. ArrayPool에서 버퍼 임대 var buffer = ArrayPool<byte>.Shared.Rent(dataLen); try { // 2. Marshal.Copy로 복사 Marshal.Copy(dataPtr, buffer, 0, dataLen); // 3. core로 전달 _core.OnFrame(_sessionId, buffer.AsSpan(0, dataLen)); } finally { // 4. ArrayPool 반환 ArrayPool<byte>.Shared.Return(buffer); } // 5. Free 호출 (반드시) WS_FreeBuffer(dataPtr); break;
ERROR/CLOSE 처리
case EventType.CLOSE: string reason = ""; if (messagePtr != IntPtr.Zero) { reason = Marshal.PtrToStringUTF8(messagePtr); WS_FreeString(messagePtr); // 반드시 } EnqueueDispatch(() => OnClose?.Invoke((ushort)code, reason)); break;
FAIL 조건
아래 패턴이 문서/코드에 등장하면 FAIL:
-
Free 누락 가능성이 있는 설계
- 예:
를 필드에 저장하고 나중에 Free하는 구조dataPtr - 예: 예외 발생 시 Free가 호출되지 않는 코드 경로
- 예:
-
또는ToArray()
사용new byte[]- 핫 경로(수신 루프)에서 allocation
-
이벤트 처리량 상한 없음
- 무한 루프로 PollEvent 호출
-
ptr 보존
에서 받은 ptr을 복사 없이 보관WS_PollEvent
Verification Checklist
- MESSAGE 수신 후
호출됨WS_FreeBuffer(dataPtr) - ERROR/CLOSE 수신 후 messagePtr이 non-null이면
호출됨WS_FreeString(messagePtr) - 예외 발생 시에도 Free가 호출되도록 try-finally 또는 동등한 구조 사용
-
/ArrayPool<byte>.Shared.Rent()
사용.Return() -
또는ToArray()
미사용new byte[] - Tick당 이벤트 처리 상한 존재 (권장: 64)
Reference
- 폴링 계약: 76-webgl-ws-polling-bridge
- WebSocket 클라이언트: 72-network-ws-client