티스토리 뷰
728x90
코틀린 컴파일러가 suspend 함수를 어떻게 Continuation-기반 상태 머신으로 바꾸는지, 주요 과정을 단계별로 정리해 보겠습니다.
1. 함수 시그니처 변환 (CPS: Continuation Passing Style)
원래 형태의 suspend 함수
suspend fun foo(x: Int): String {
val a = computeSomething(x)
delay(1000) // suspend 지점
return "Result: $a"
}
는 컴파일 시 다음처럼 마지막 파라미터로 Continuation<String> 을 받고, 리턴 타입은 Any? (혹은 Object) 로 바뀝니다.
fun foo(x: Int, completion: Continuation<String>): Any?
- continuation 파라미터에 호출이 끝났을 때 결과를 전달하고,
- 함수 내부에서 suspend 지점마다 COROUTINE_SUSPENDED 마커를 반환해 일시 중단 사실을 알립니다.
2. ContinuationImpl 서브클래스 생성
각 suspend 함수 호출마다 한 번만 생성되는, 상태 저장용 객체를 위한 클래스가 만들어집니다. 보통 이름 뒤에 $1 등이 붙는 형태입니다.
// 의사 코드
private class FooContinuation(
completion: Continuation<String>
) : ContinuationImpl(completion) {
// suspend 전후에 살아남아야 할 로컬 변수
var L$0: Any? = null
// 현재 상태(레이블)를 저장
var label: Int = 0
override fun invokeSuspend(result: Result<Any?>): Any? {
when (label) {
0 -> {
// 처음 호출된 상태
label = 1
// computeSomething 결과를 보관
L$0 = computeSomething(x)
// delay 호출, 일시 중단 가능
val suspended = delay(1000, this)
if (suspended === COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
}
1 -> {
// 일시 중단 후 재개된 상태
// 복원된 L$0에서 값 꺼내기
val a = L$0 as Int
// 최종 결과 반환
return "Result: $a"
}
else -> throw IllegalStateException("Unexpected state")
}
}
}
- ContinuationImpl 은 내부에서 resumeWith() 등을 구현하며,
- label 필드로 “몇 번째 suspend 지점”인지 구분하고,
- 로컬 변수(L$0, L$1 등)는 필드로 호이스팅(이동)되어 상태 머신 내부에서 관리됩니다.
3. 최종 호출 플로우
- 호출부에서
- foo(42, myContinuation)
- 컴파일러가 생성한 프록시 함수가 실행되어,
- fun foo(x: Int, completion: Continuation<String>): Any? { // 이미 ContinuationImpl 인스턴스인지 검사 val cont = if (completion is FooContinuation) completion else FooContinuation(completion) // 초기 상태로 설정 cont.label = 0 // 상태 머신 시작 return cont.invokeSuspend(Result.success(Unit)) }
- invokeSuspend 내부의 when(label) 블록이,
- label = 0 에서 첫 부분을 실행 → delay() 호출 → 일시 중단
- 재개될 때 label = 1 블록을 실행 → 최종 결과 반환
요약
- 컴파일러는 suspend fun 마다
- CPS 스타일 함수 시그니처로 변환
- 상태 머신 (ContinuationImpl 서브클래스) 생성
- 각 suspend 지점마다 label 변경 + COROUTINE_SUSPENDED 반환 로직 삽입
- 로컬 변수는 모두 클래스 필드로 호이스팅
- 이 덕분에, 런타임에는 invokeSuspend 메서드만 호출하며 내부 when(label) 분기로 코드를 이어갈 수 있습니다.
728x90
'프로그래밍 > 코틀린' 카테고리의 다른 글
| Kotlin 2.2.0 출시 핵심 정리 (2) | 2025.06.29 |
|---|---|
| KotlinConf 2025 발표 요약 (0) | 2025.05.23 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 스브링부트
- deepseek
- unreal engjin
- First-class citizen
- 언리얼엔진5
- RESTfull
- 일급 객체
- Heap Area
- MCP
- springai
- 코틀린
- vite
- cqrs
- redis
- 카프카 개념
- generated_body()
- model context protocol
- Subagent
- AI 에이전트
- 코프링
- ai통합
- 타입 안전성
- 자바
- Stack Area
- 언리얼엔진
- Claude Agent SDK
- 디자인패턴
- JVM
- Java
- method Area
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
글 보관함
