Back home

Rust/Wasm 런타임 안정성을 위해서는 패닉 및 중단 복구를 모두 처리해야 합니다.

공유 Wasm 인스턴스가 오랫동안 호출을 수락하기 시작하면 충돌이 단일 오류에서 상태 복구 및 오류 격리 문제로 확대됩니다.

Wasm은 처음에는 쉽게 포팅 레이어로 간주될 수 있습니다. 코드를 프로그래밍할 수 있고, 페이지를 실행할 수 있으며, 성능도 괜찮고, 모든 것이 거의 같아 보입니다. 일반적으로 데모를 통과한 후에는 정말 어려워지기 시작합니다. 편집기, 렌더러 및 문서 파서와 같은 모듈이 단일 페이지 실험에서 장기 상주 런타임으로 이동하면 오류 모델이 즉시 변경됩니다.

현재로서는 패닉 및 중단이 더 이상 언어 계층의 예외 분기가 아닙니다. 그들이 결정하는 것은 이 인스턴스가 후속 작업을 계속 받을 수 있는지, 메모리 상태가 오염되었는지, 호스트 계층이 인스턴스를 즉시 폐기해야 하는지, 인스턴스 풀을 채워야 하는지 여부입니다. 모바일 팀이 오랫동안 네이티브 컨테이너에서 실행되어 온 커널을 웹으로 이동할 때 가장 쉽게 과소평가되는 것은 이러한 변경 계층입니다.

데모를 통과한 후 오류 모델이 시작되었습니다.

단일 호출의 충돌은 이해하기 어렵지 않습니다. 버튼을 클릭하면 Wasm 호출이 트리거됩니다. 실패하면 작업에 대한 오류가 보고됩니다. 페이지를 새로 고치고 다시 시도하세요. 비용은 여전히 ​​통제 가능합니다.

런타임이 인스턴스 재사용을 시작한 후에 문제가 발생합니다. 동일한 Wasm 인스턴스가 지속적으로 여러 문서를 열고, 여러 차례의 입력 이벤트를 수신하고, 여러 JS 브리지 호출을 통과하면 패닉 및 중단의 영향 범위가 더 이상 현재 작업에서 멈추지 않습니다. 불완전한 실패로 인해 후속 요청이 중단될 수 있습니다.

이러한 위험은 첫날에는 노출되지 않는 경우가 많습니다. 첫 번째 단계에서는 일반적으로 분산된 오류 보고서만 표시됩니다. 간헐적으로 렌더링이 실패하고, 특정 내보내기가 중단되고, 특정 문서를 닫았다가 다시 연 후 잘못된 상태에 있습니다. 좀 더 자세히 확인해보면 단서는 점차 같은 현상으로 수렴될 것입니다. 콜 체인에서 실패가 발생했지만 공유 인스턴스에는 피해가 남아 있다는 것입니다.

이 시점에서 논의의 초점은 더 이상 "Rust 코드가 패닉을 일으킬지 여부"가 아니라 "이 런타임이 패닉 이후 다음 호출을 계속 제공할 수 있는지 여부"입니다.

패닉은 포착될 수 있으며 중단은 인스턴스만 변경할 수 있습니다.

Rust/Wasm에서 분리해야 할 가장 중요한 것은 패닉과 중단이라는 두 가지 실패 의미 체계입니다.

패닉은 또한 설정된 경계를 따라 긴장을 풀 수 있는 기회도 있습니다. 바인딩 계층과 호스트 계층이 미리 복구 방법에 동의하는 한 현재 호출은 실패할 수 있으며 인스턴스의 다른 상태도 유지될 수 있습니다. 중단은 전혀 갈 길이 아닙니다. 이는 현재 실행이 복구할 수 없는 상태에 도달했음을 의미합니다. 요청을 수신하기 위해 동일한 인스턴스를 계속 사용한다면 기본적으로 메모리와 리소스가 중간에 손상되지 않을 것이라고 장담하는 것입니다.

런타임 중에 두 가지가 함께 혼합되면 후속 처리에서 문제가 확실히 발생합니다.

  • 일반적인 예외로 중단을 삼키면 인스턴스 풀은 신뢰성을 잃은 개체를 계속 재사용합니다.
  • 모든 패닉을 인스턴스가 파괴되어야 하는 것처럼 처리하면 처리량이 불필요하게 감소합니다.
  • JS 호스트는 "호출 실패"만 알지만 재시도할지, 인스턴스를 잃을지, 현재 세션을 끊을지 알 수 없습니다.

이는 Wasm 런타임 안정성에 대한 가장 현실적인 사항이기도 합니다. 후속 격리 및 스케줄링을 구현하기 전에 복구 의미론을 먼저 정의해야 합니다.

바인딩 레이어가 복구 의미를 제공하지 않으면 호스트 레이어는 잘못된 상태를 취하고 계속해서 작업을 수락합니다.

이런 종류의 문제에서 가장 위험한 곳은 비즈니스 코드가 아니라 “이미 처리된” 것처럼 보이는 바인딩 레이어입니다. 호스트 계층은 종종 던져진 오류 개체만 보고 이를 일반 호출 실패로 기록합니다. 로그가 있고 페이지가 즉시 충돌하지 않지만 시스템이 인스턴스 내부에 잘못된 상태를 남겼을 수 있습니다.

실제로 수정해야 할 것은 단지 try/catch가 아니라 실패 후 처리 작업입니다. 다음과 유사한 논리가 이제 막 신뢰성 설계에 들어가기 시작했습니다.

async function runWithRecovery(instance, input) {
  try {
    return await instance.exports.handle(input)
  } catch (error) {
    if (isAbort(error)) {
      pool.replace(instance.id)
    }
    throw error
  }
}

이 코드의 초점은 구문이 아니라 간단한 판단, 즉 현재 오류로 인해 이 인스턴스가 신뢰할 수 없는 개체로 표시되었는지 여부에 있습니다. 대답이 '예’인 경우 복구 작업은 오류 발생 시 중단되어서는 안 되며, 인스턴스 제거, 리소스 재구성 및 요청 흐름 절단까지 계속되어야 합니다.

이 계층이 명확하게 정의되지 않은 한 시스템은 오류를 처리하는 것처럼 보이지만 실제로는 잠재적으로 손상된 런타임을 프로덕션 경로에 다시 넣는 것입니다.

공유 인스턴스는 복구 문제를 풀링 전략 문제로 증폭시킵니다.

Wasm이 실제 제품에 적용된 후 "페이지가 닫힐 때까지 한 인스턴스"만 있는 경우는 거의 없습니다. 더 일반적인 것은 인스턴스 풀, 작업자 풀 또는 런타임 리소스 세트를 공유하는 포그라운드 문서 및 백그라운드 작업입니다. 이 단계에서 패닉 및 중단의 복구 비용은 풀링 전략을 직접 다시 작성합니다.

인스턴스 초기화 비용이 많이 드는 경우 시스템은 자연스럽게 이를 최대한 재사용하는 경향이 있습니다. 그러나 재사용이 설정되면 결함 격리도 동시에 업그레이드되어야 합니다.

  • 단일 호출에서만 정지될 수 있고 실패 후 호출과 함께 폐기되는 상태
  • 호출 전반에 걸쳐 어떤 캐시를 유지할 수 있는지, 중단이 발생하면 어떤 캐시를 완전히 무효화해야 하는지
  • 인스턴스 교체 후 대기 중인 작업은 어떻게 마이그레이션되나요? 재시도하면 부작용이 두 번 발생합니까?

이는 언어 계층이 자동으로 보내는 답변이 아닙니다. 런타임 디자인입니다.

이 때문에 Rust/Wasm 신뢰성에 대한 논의가 "패닉을 잡을 수 있는가?"에만 그친다면 문제를 과소평가하기 쉽습니다. 유지 관리 비용 격차를 실제로 확대하는 것은 인스턴스 풀이 장애 후 명확한 신뢰 경계를 유지할 수 있는지 여부입니다.

적용 가능한 경계는 수명주기와 밀접한 관련이 있습니다.

이 복원 디자인 세트는 모든 Wasm 프로젝트에 필요한 것은 아닙니다.

모듈이 일회성 오프라인 도구이거나 페이지가 삭제될 때 전체 인스턴스가 재활용되는 경우 패닉과 중단의 차이는 여전히 존재하지만 복구 이점은 훨씬 적습니다. 페이지를 직접 새로 고치고 작업을 직접 다시 실행하는 것만으로도 충분할 때가 많습니다.

시스템이 다음과 같은 특성을 갖게 되면 복구 의미 체계는 "최적화 항목"에서 "인프라 항목"으로 빠르게 변경됩니다.

  • 인스턴스는 오랫동안 상주하며 단일 페이지 수명주기와 함께 파괴되지 않습니다.
  • 동일한 인스턴스가 지속적으로 여러 라운드의 호출을 수행합니다.
  • 호스팅 계층은 시작 시간 및 처리량 대신 풀링을 사용해야 합니다.
  • 실패 후 세션 상태, 캐시 상태 및 대기 중인 작업 보호

모바일 팀이 기본 기능을 웹으로 이동할 때 이 경계에 직면할 가능성이 가장 높습니다. 앱 프로세스에서 원래 기본적으로 설정된 격리 관계는 JS/Wasm 호스트 경계에 도달한 후 다시 채워야 하는 경우가 많았습니다.

Wasm을 사용하면 네이티브 코드가 브라우저에 더 쉽게 들어갈 수 있지만 런타임 복구 의미 체계는 제공되지 않습니다. 시스템이 인스턴스 공유, 상태 재사용 및 장기 호출 수락을 시작하자마자 패닉 및 중단은 두 개의 서로 다른 런타임 이벤트로 처리되어야 합니다. 전자는 현재 호출을 어떻게 종료할지에 관심이 있고, 후자는 이 인스턴스가 풀에 계속 존재할 수 있는지 여부에 관심이 있습니다. 이러한 판단이 먼저 이루어지지 않으면 코드 이식이 성공할수록 후속 실패를 처리하기가 더 어려워집니다.