- 홈
- Documentation
- 네이티브
- 네이티브 셸, PTY, 프로세스 및 키 내부 구조
네이티브 셸, PTY, 프로세스 및 키 내부 구조
이 문서는 @f5-sales-demo/pi-natives의 실행/프로세스/터미널 기본 요소인 shell, pty, ps, keys를 docs/natives-architecture.md의 아키텍처 용어를 사용하여 설명합니다.
구현 파일
섹션 제목: “구현 파일”crates/pi-natives/src/shell.rscrates/pi-natives/src/shell/windows.rs(Windows 전용)crates/pi-natives/src/pty.rscrates/pi-natives/src/ps.rscrates/pi-natives/src/keys.rscrates/pi-natives/src/task.rs(shell/pty에서 사용하는 공유 취소 동작)packages/natives/src/shell/index.tspackages/natives/src/shell/types.tspackages/natives/src/pty/index.tspackages/natives/src/pty/types.tspackages/natives/src/ps/index.tspackages/natives/src/ps/types.tspackages/natives/src/keys/index.tspackages/natives/src/keys/types.tspackages/natives/src/bindings.ts
레이어 소유권
섹션 제목: “레이어 소유권”- TS 래퍼/API 레이어 (
packages/natives/src/*): 타입이 지정된 진입점, 취소 인터페이스 (timeoutMs,AbortSignal), JS 편의 기능. - Rust N-API 모듈 레이어 (
crates/pi-natives/src/*): 셸/PTY 프로세스 실행, 프로세스 트리 순회/종료, 키 시퀀스 파싱. - 유효성 검사 게이트 (
native.ts, 아키텍처 레벨): 래퍼가 사용되기 전에 필요한 내보내기(Shell,executeShell,PtySession,killTree,listDescendants, 키 헬퍼)가 존재하는지 확인.
셸 서브시스템 (shell)
섹션 제목: “셸 서브시스템 (shell)”API 모델
섹션 제목: “API 모델”두 가지 실행 모드가 제공됩니다:
executeShell(options, onChunk?)를 통한 일회성 실행.new Shell(options?)을 통한 영구 세션 생성 후shell.run(...)을 반복 호출.
두 모드 모두 스레드 안전 콜백을 통해 출력을 스트리밍하며 { exitCode?, cancelled, timedOut }을 반환합니다.
세션 생성 및 환경 모델
섹션 제목: “세션 생성 및 환경 모델”Rust는 다음과 같이 brush_core::Shell을 생성합니다:
- 비대화형 모드,
do_not_inherit_env: true,- 호스트 환경에서의 명시적인 환경 재구성,
- 셸 민감 변수(
PS1,PWD,SHLVL, bash 함수 내보내기 등)에 대한 스킵 목록 적용.
세션 환경 동작:
ShellOptions.sessionEnv는 세션 생성 시 한 번만 적용됩니다.ShellRunOptions.env는 명령 범위(EnvironmentScope::Command)로 각 실행 후 팝됩니다.PATH는 Windows에서 대소문자 구분 없는 중복 제거와 함께 특별히 병합됩니다.
Windows 전용 경로 보강(shell/windows.rs): 발견된 Git-for-Windows 경로(cmd, bin, usr/bin)가 존재하고 아직 포함되지 않은 경우 추가됩니다.
런타임 생명주기 및 상태 전환
섹션 제목: “런타임 생명주기 및 상태 전환”영구 셸(Shell.run)은 다음 상태 머신을 사용합니다:
- 유휴/미초기화:
session: None. - 실행 중: 첫 번째
run()이 세션을 지연 생성하고,current_abort토큰을 저장하며, 명령을 실행합니다. - 완료 + 킵얼라이브: 실행 제어 흐름이
Normal이면current_abort가 지워지고 세션이 재사용됩니다. - 완료 + 종료: 제어 흐름이 루프/스크립트/셸 종료 관련(
BreakLoop,ContinueLoop,ReturnFromFunctionOrScript,ExitShell)이면 세션이 삭제됩니다(session: None). - 취소됨/타임아웃: 실행 태스크가 취소되고, 2초의 유예 대기 후 강제 중단되며, 세션이 삭제됩니다.
- 오류: 세션이 삭제됩니다.
일회성 셸(executeShell)은 호출마다 항상 새 세션을 생성하고 삭제합니다.
스트리밍/출력 동작
섹션 제목: “스트리밍/출력 동작”- 표준 출력/표준 오류는 공유 파이프로 라우팅되어 동시에 읽힙니다.
- 리더는 UTF-8을 증분 방식으로 디코딩하며, 잘못된 바이트 시퀀스는
U+FFFD대체 청크를 내보냅니다. - 프로세스 완료 후, 출력 드레인에는 백그라운드 작업이 디스크립터를 열어두는 경우 중단을 방지하기 위해 유휴/최대 가드(
250ms유휴,2s최대)가 적용됩니다.
취소, 타임아웃 및 백그라운드 작업
섹션 제목: “취소, 타임아웃 및 백그라운드 작업”CancelToken은timeoutMs와 선택적AbortSignal로부터 구성됩니다.- 취소/타임아웃 시 셸 취소 토큰이 트리거되고, 강제 중단 전에 태스크에 2초의 유예 기간이 부여됩니다.
- 취소가 발생하면 brush 작업 메타데이터를 사용하여 백그라운드 작업이 종료됩니다(
TERM, 이후 지연KILL).
Shell.abort() 동작:
- 해당
Shell인스턴스에서 현재 실행 중인 명령만 중단합니다, - 실행 중인 것이 없으면 성공적인 no-op입니다.
실패 동작
섹션 제목: “실패 동작”자주 발생하는 오류에는 다음이 포함됩니다:
- 세션 초기화 실패(
Failed to initialize shell), - cwd 오류(
Failed to set cwd), - 환경 설정/팝 실패,
- 스냅샷 소스 실패,
- 파이프 생성/복제 실패,
- 실행 실패(
Shell execution failed: ...), - 태스크 래퍼 실패(
Shell execution task failed: ...).
결과 수준의 취소 플래그:
- 타임아웃 ->
exitCode: undefined,timedOut: true. - 중단 신호 ->
exitCode: undefined,cancelled: true.
PTY 서브시스템 (pty)
섹션 제목: “PTY 서브시스템 (pty)”API 모델
섹션 제목: “API 모델”new PtySession()은 다음을 제공합니다:
start(options, onChunk?) -> Promise<{ exitCode?, cancelled, timedOut }>write(data)resize(cols, rows)kill()
런타임 생명주기 및 상태 전환
섹션 제목: “런타임 생명주기 및 상태 전환”PtySession 상태 머신:
- 유휴:
core: None. - 예약됨:
start()가 비동기 작업 시작 전에 동기적으로 제어 채널을 설치하므로(core: Some),write/resize/kill이 즉시 유효해집니다. - 실행 중: 블로킹 PTY 루프가 자식 상태, 리더 이벤트, 취소 하트비트 및 제어 메시지를 처리합니다.
- 터미널 종료: 자식 종료 + 리더 완료.
- 최종화됨:
core는 start 태스크 완료(성공 또는 오류) 후 항상None으로 재설정됩니다.
동시성 가드:
- 이미 실행 중인 상태에서 시작하면
PTY session already running을 반환합니다.
스폰/연결/쓰기/읽기/종료 패턴
섹션 제목: “스폰/연결/쓰기/읽기/종료 패턴”- PTY는
portable_pty::native_pty_system().openpty(...)를 통해 열립니다. - 명령은 현재 선택적
cwd및 환경 오버라이드와 함께sh -lc <command>로 실행됩니다. write()는 원시 바이트를 PTY 표준 입력으로 전송합니다.resize()는 차원을 클램프(cols 20..400,rows 5..200)하고 마스터 크기 조정을 호출합니다.kill()은 실행을 취소됨으로 표시하고 자식 프로세스를 종료합니다.
출력 경로:
- 전용 리더 스레드가 마스터 스트림을 읽고,
- 잘못된 바이트에서
U+FFFD대체와 함께 증분 UTF-8 디코딩을 수행하며, - 청크는 N-API 스레드 안전 콜백을 통해 전달됩니다.
취소 및 타임아웃 의미론
섹션 제목: “취소 및 타임아웃 의미론”timeoutMs와AbortSignal이CancelToken을 구성합니다.- 루프는 주기적으로
ct.heartbeat()를 호출하며, 중단은 자식 종료를 트리거합니다. - 타임아웃 분류는 하트비트 오류에서 문자열 기반(
"Timeout"부분 문자열)으로 수행됩니다.
실패 동작
섹션 제목: “실패 동작”오류 발생 지점에는 다음이 포함됩니다:
- PTY 할당/열기 실패,
- PTY 스폰 실패,
- 라이터/리더 획득 실패,
- 자식 상태/대기 실패,
- 락 포이즈닝,
- 제어 채널 연결 끊김(
PTY session is no longer available).
실행 중이 아닐 때의 제어 호출 실패:
write/resize/kill은PTY session is not running을 반환합니다.
프로세스 트리 서브시스템 (ps)
섹션 제목: “프로세스 트리 서브시스템 (ps)”API 모델
섹션 제목: “API 모델”killTree(pid, signal) -> numberlistDescendants(pid) -> number[]
TS 래퍼는 또한 setNativeKillTree(native.killTree)를 통해 네이티브 킬 트리 통합을 공유 유틸리티에 등록합니다.
플랫폼별 구현
섹션 제목: “플랫폼별 구현”- Linux:
/proc/<pid>/task/<pid>/children을 재귀적으로 읽습니다. - macOS:
libproc의proc_listchildpids를 사용합니다. - Windows:
CreateToolhelp32Snapshot으로 프로세스 테이블을 스냅샷하고, 부모->자식 맵을 구성하며,OpenProcess(PROCESS_TERMINATE)+TerminateProcess로 종료합니다.
킬 트리 동작
섹션 제목: “킬 트리 동작”- 자손은 재귀적으로 수집됩니다.
- 종료 순서는 하위에서 상위 방향(가장 깊은 자손 먼저)으로, 고아 재부모화를 줄이기 위함입니다.
- 루트 pid는 마지막에 종료됩니다.
- 반환 값은 성공적으로 종료된 수입니다.
시그널 동작:
- POSIX: 제공된
signal이kill에 전달됩니다. - Windows:
signal은 무시되며, 종료는 무조건적인 프로세스 종료입니다.
실패 동작
섹션 제목: “실패 동작”이 모듈은 API 표면에서 의도적으로 예외를 발생시키지 않습니다:
- 누락되거나 접근 불가능한 프로세스 트리 브랜치는 건너뜁니다,
- pid별 종료 실패는 오류가 아닌 실패로 계산됩니다,
- 조회 실패 시 일반적으로
listDescendants는[]를,killTree는0을 반환합니다.
키 파싱 서브시스템 (keys)
섹션 제목: “키 파싱 서브시스템 (keys)”API 모델
섹션 제목: “API 모델”제공되는 헬퍼:
parseKey(data, kittyProtocolActive)matchesKey(data, keyId, kittyProtocolActive)parseKittySequence(data)matchesKittySequence(data, expectedCodepoint, expectedModifier)matchesLegacySequence(data, keyName)
파싱 모델
섹션 제목: “파싱 모델”파서는 다음을 결합합니다:
- 직접 단일 바이트 매핑(
enter,tab,ctrl+<letter>, 인쇄 가능한 ASCII), - O(1) 레거시 이스케이프 시퀀스 조회 (PHF 맵),
- xterm
modifyOtherKeys파싱, - Kitty 프로토콜 파싱(
CSI u,CSI ~,CSI 1;...<letter>), - 키 ID로의 정규화(
ctrl+c,shift+tab,pageUp,f5등).
수정자 처리:
- 키 매칭에는 shift/alt/ctrl 비트만 비교됩니다,
- 잠금 비트는 비교 전에 마스크됩니다.
레이아웃 동작:
- 기본 레이아웃 폴백은 의도적으로 제한되어 있어 리매핑된 레이아웃이 ASCII 문자/기호에 대한 잘못된 매칭을 생성하지 않습니다.
실패 동작
섹션 제목: “실패 동작”- 인식되지 않거나 잘못된 시퀀스는 파싱 함수에서
null을 반환합니다. - 매치 함수는 파싱 실패 또는 불일치 시
false를 반환합니다. - 잘못된 키 입력에 대해 발생하는 오류 표면이 없습니다.
JS 래퍼 API ↔ Rust 내보내기 매핑
섹션 제목: “JS 래퍼 API ↔ Rust 내보내기 매핑”셸 + PTY + 프로세스
섹션 제목: “셸 + PTY + 프로세스”| TS 래퍼 API | Rust N-API 내보내기 | 비고 |
|---|---|---|
executeShell(options, onChunk?) | executeShell (execute_shell) | 일회성 셸 실행 |
new Shell(options?) | Shell 클래스 | 영구 셸 세션 |
shell.run(options, onChunk?) | Shell::run | 킵얼라이브 제어 흐름에서 세션 재사용 |
shell.abort() | Shell::abort | 해당 셸 인스턴스의 활성 실행 중단 |
new PtySession() | PtySession 클래스 | 상태 저장 PTY 세션 |
pty.start(options, onChunk?) | PtySession::start | 대화형 PTY 실행 |
pty.write(data) | PtySession::write | 원시 표준 입력 패스스루 |
pty.resize(cols, rows) | PtySession::resize | 클램프된 터미널 크기 |
pty.kill() | PtySession::kill | 활성 PTY 자식 강제 종료 |
killTree(pid, signal) | killTree (kill_tree) | 자식 우선 프로세스 트리 종료 |
listDescendants(pid) | listDescendants (list_descendants) | 재귀적 자손 목록 조회 |
| TS 래퍼 API | Rust N-API 내보내기 | 비고 |
|---|---|---|
matchesKittySequence(data, cp, mod) | matchesKittySequence (matches_kitty_sequence) | Kitty 코드포인트+수정자 매칭 |
parseKey(data, kittyProtocolActive) | parseKey (parse_key) | 정규화된 키 ID 파서 |
matchesLegacySequence(data, keyName) | matchesLegacySequence (matches_legacy_sequence) | 정확한 레거시 시퀀스 맵 확인 |
parseKittySequence(data) | parseKittySequence (parse_kitty_sequence) | 구조화된 Kitty 파싱 결과 |
matchesKey(data, keyId, kittyProtocolActive) | matchesKey (matches_key) | 고수준 키 매처 |
포기된 세션 정리 및 최종화 참고 사항
섹션 제목: “포기된 세션 정리 및 최종화 참고 사항”- 셸 영구 세션: 실행이 취소/타임아웃/오류/비킵얼라이브 제어 흐름인 경우, Rust는 내부 세션 상태를 명시적으로 삭제합니다. 정상적인 성공 실행은 세션을 재사용을 위해 유지합니다.
- PTY 세션:
core는 실패 경로를 포함하여start()가 완료된 후 항상 지워집니다. - 명시적인 JS 파이널라이저 기반 종료 계약은 래퍼에 의해 제공되지 않으며, 정리는 주로 실행 완료/취소 경로에 연결됩니다. 호출자는 결정론적 종료를 위해
timeoutMs,AbortSignal,shell.abort()또는pty.kill()을 사용해야 합니다.