ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SE-0548] resignRemoteID for remote distributed actor references
    Swift 2026. 9. 26. 18:12

    안녕하세요. 그린입니다 🍏
    이번 포스팅에서는 SE-0548 — 원격 분산 액터 참조를 위한 resignRemoteID에 대해 정리해보겠습니다 🙋🏻

    Intro

    Proposal: SE-0548

    Author: Konrad 'ktoso' Malawski

    Review Manager: Doug Gregor

    Status: Accepted

    Implementation: swiftlang/swift#91610

    Summary of Changes

    분산 액터(distributed actor)의 정체성은 연관된 DistributedActorSystem이 관리해요.

    지금까지 시스템은 assignID(_:)/resignID(_:) 쌍을 통해 로컬 분산 액터 인스턴스의 생명주기를 관찰하고, resolve(id:as:)를 통해 원격 참조의 생성에 관여했어요.

     

    하지만 원격 참조가 언제 deinitialize되는지 관찰할 방법은 없었습니다.

    이 제안은 원격 분산 액터 프록시가 deinitialize될 때 호출되는 새 프로토콜 요구사항 resignRemoteID(_:) 하나를 추가하자고 제안합니다.

     

    Motivation

    SE-0344: Distributed Actor Runtime는 DistributedActorSystem에 다음과 같은 생명주기 콜백들을 정의해요.

    • assignID(_:): 로컬 분산 액터가 초기화되는 중에 정체성(identity)을 얻기 위해 호출됨
    • actorReady(_:): 로컬 분산 액터가 초기화를 완료했을 때 호출됨
    • resignID(_:): 로컬 분산 액터가 deinitialize될 때(또는 초기화가 실패할 때) 호출됨
    • resolve(id:as:): 사용자 코드가 SomeActor.resolve(id:using:)를 호출할 때 실행됨. nil을 반환하면 런타임에게 이 id에 대한 원격 참조를 할당하라고 알려줌

    원격 경로의 생명주기는 비대칭적이에요.

    resolve(id:as:)는 원격 참조를 생성할 때 호출되지만, 그 참조들에 대해 resignID와 대응되는 콜백은 없습니다.

     

    원격 참조가 상태가 없고 리소스를 들고 있지 않은 시스템이라면 이걸로 충분해요.

    하지만 예를 들어 특정 커넥션을 쓰고 있는 원격 액터 참조가 최소 하나라도 존재하는 동안엔 그 커넥션을 계속 살려두고 싶은 시스템에는 문제가 되는 것으로 나타났습니다.

     

    이상적으로는, 원격 참조가 할당 해제될 때마다 시스템이 통지받아서, 그 id를 이용해 커넥션을 이미 종료해도 되는지 아직 안 되는지를 관리할 수 있어야 합니다.

    Proposed Solution

    원격 분산 액터 참조가 deinitialize될 때 호출되는 새 프로토콜 요구사항을 DistributedActorSystem에 추가해서 이 문제를 해결하자고 제안합니다.

     

    public protocol DistributedActorSystem {
      // ... existing requirements, including:
      func resignID(_ id: ActorID)
    
      /// Invoked when a remote distributed actor reference is deinitializing. 
      /// 
      /// The passed in ID, generally, would have been passed to \`resolve(id:as:)\` earlier
      /// and a remote distributed reference would have been created as a result of that resolve call.
      ///
      /// - SeeAlso: \`\`resignID(_:)\`\` which is invoked for local instances.
      func resignRemoteID(_ id: ActorID)
    }
    
    extension DistributedActorSystem {
      public func resignRemoteID(_ id: ActorID) {}
    }

     

    Swift 런타임은 원격 분산 액터 프록시의 deinit이 실행될 때 resignRemoteID(_:)를 호출하도록 업데이트돼요.

     

    기존 resignID(_:)는 계속 로컬 인스턴스에 대해서만 호출됩니다.

     

    Detailed Design

    구현 수준에서, 프로토콜 요구사항을 도입하는 것 외의 유일한 변경은 원격 분산 액터의 deinit에서 이걸 실제로 호출하는 것뿐이에요.

    deinit {
      // if  {
      //   << actorSystem.resignRemoteID(self.id)  // NEW
      //   << deallocate memory for \`id\` and \`actorSystem\` only
      // } else if  {
      //   << run user-declared deinit body
      //   << actorSystem.resignID(self.id)
      //   << deallocate all stored properties
      // }
    }

     

    사용자가 선언한 deinit 본문은 원격 참조에서는 여전히 절대 실행되지 않고, 기존 resignID 경로도 변경되지 않은 채로 유지됩니다.

     

    이 결과로 생명주기 관리는 다음과 같이 정리돼요.

      "Local instance" 분산 액터 "Remote reference" 분산 액터
    Registration init 중 assignID(_:) resolve(id:as:)가 nil 반환
    Ready init 후 actorReady(_:) N/A
    Deinit deinit 시 resignID(_:) deinit 시 resignRemoteID(_:)

     

    resolve는 같은 id로 여러 번 호출될 수 있기 때문에, resignRemoteID도 같은 id로 여러 번 호출될 수 있어요.

    resolve로 생성된 원격 참조마다 한 번씩요.

     

    새로 추가된 resignRemoteID(_:)는 "대응되는" resolve(id:as:)가 다음과 같은 경우엔 호출되지 않아요.

    • 에러를 던진 경우 (참조가 생성되지 않음)
    • 로컬 액터를 반환한 경우 (원격 참조가 아니라 로컬 경로가 적용됨)

    Source Compatibility

    기존 DistributedActorSystem 구현체들은 계속 컴파일되고 동일하게 동작합니다.

     

    ABI Compatibility

    DistributedActorSystem은 기본 구현이 있는 새 요구사항을 하나 추가로 얻는데, 이건 기존 구현체들에 대해 소스/ABI 호환적이에요.

    분산 액터의 deinit에 새로 추가되는 SILGen 호출 지점은 back-deployment 호환적이라, 이 요구사항이 없는 옛날 런타임에 새로 컴파일된 코드를 배포하는 것도 가능해요.

    그런 경우엔 그냥 그 호출이 일어나지 않을 뿐입니다.

     

    Alternatives Considered

    그냥 resignID를 원격 참조에도 재사용하기

    가장 쉬운 해법은 resignID가 원격 참조를 포함해서 항상 호출되도록 동작을 바꾸는 것일 거예요.

    여기엔 두 가지 문제가 있어요.

    • resignID 메서드의 문서는 개발자들에게 자신이 할당하지 않은 id가 전달되면 크래시내라고 권장해왔어요.
      그게 액터 시스템의 오작동을 나타낸다고 봤기 때문이에요.
      실제로는 그런 값을 무시해도 완전히 안전하니 이 가이드는 너무 보수적이었던 셈이지만, 실제로 크래시내는 시스템이 있다면 새 Swift 버전에서 동작이 바뀌었을 때 새롭게 크래시가 나기 시작할 수 있어요.
      • 실제로는 이 리스크가 작은 편인데, 우리가 아는 액터 시스템들은 "알 수 없는 ID" 케이스를 허용하도록 쉽게 업데이트해서 계속 동작하게 만들 수 있기 때문이에요.
    • "resign"이라는 의미에 과부하가 걸리고, assign/resign 짝과도 잘 안 어울려요.
      이 메서드들은 더 이상 짝이 아니게 되고, resolve-remote 경로에는 호출될 "짝"이 없어져서 resign을 구현하기가 더 어려워져요 (어떤 경로를 타야 하는지 추적해야 하니까요).

    Conclusion

    그동안 분산 액터의 원격 참조는 언제 사라지는지 시스템이 알 방법이 없어서, 커넥션처럼 원격 참조 개수에 따라 살았다 죽었다 해야 하는 리소스를 관리하기가 까다로웠어요.

    resignRemoteID(_:) 하나만 추가해서 assignID/resignID 짝처럼 로컬과 원격 양쪽 모두 대칭적인 생명주기 콜백을 갖추게 됐다는 점이 좋은 포인트인것 같습니다 👍

    기본 구현이 빈 함수라 기존 구현체에 아무 영향도 주지 않으면서 필요한 시스템만 채택하면 되는 점도 실용적입니다 🙌

     

    References

     

    swift-evolution/proposals/0548-resign-remote-id.md at main · swiftlang/swift-evolution

    This maintains proposals for changes and user-visible enhancements to the Swift Programming Language. - swiftlang/swift-evolution

    github.com

Designed by Tistory.