Swift Digest
SE-0548 | Swift Evolution↗

リモートの分散アクター参照に対する resignRemoteID

resignRemoteID for remote distributed actor references

Proposal
SE-0548↗
Authors
Konrad 'ktoso' Malawski
Review Manager
Doug Gregor
Status
Accepted

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら↗。

01 何が問題だったのか

分散アクターのアイデンティティは、SE-0344 で導入された DistributedActorSystem が管理します。システムはアクターのライフサイクルに対応する次のコールバックを通じて、インスタンスの生成や破棄を観測します。

  • assignID(_:): ローカルの分散アクターが初期化されるときに呼ばれ、ID を採番します。
  • actorReady(_:): ローカルの分散アクターの初期化が完了したときに呼ばれます。
  • resignID(_:): ローカルの分散アクターがデイニシャライズされるとき(または初期化に失敗したとき)に呼ばれます。
  • resolve(id:as:): ユーザーコードが SomeActor.resolve(id:using:) を呼んだときに呼ばれ、nil を返すとランタイムはその ID に対する リモート参照 を生成します。

ここで、ローカル参照とリモート参照のライフサイクルの扱いが非対称になっています。ローカルインスタンスには生成時の assignID(_:) と破棄時の resignID(_:) という対がありますが、リモート参照には生成時の resolve(id:as:) に対応する破棄時のコールバックが存在しません。つまり、リモート参照がデイニシャライズされたことをシステムが知る手段がありません。

リモート参照が状態やリソースを持たないシステムでは、これは問題になりません。しかし、たとえば「特定のコネクションを使っているリモート参照が 1 つでも存在する限り、そのコネクションを維持しておきたい」といったシステムでは困ります。あるコネクションに紐づくリモート参照がすべて破棄されたことを検知できなければ、コネクションを安全に破棄してよいタイミングを判断できないためです。

02 どのように解決されるのか

この非対称性を解消するため、DistributedActorSystem に、リモートの分散アクター参照がデイニシャライズされるときに呼ばれる新しいプロトコル要件 resignRemoteID(_:) を追加します。

public protocol DistributedActorSystem {
  // ... 既存の要件(次を含む)
  func resignID(_ id: ActorID)

  /// リモートの分散アクター参照がデイニシャライズされるときに呼ばれる。
  ///
  /// 渡される ID は、通常は以前に resolve(id:as:) に渡され、
  /// その結果としてリモート参照が生成されたものに対応する。
  func resignRemoteID(_ id: ActorID)
}

extension DistributedActorSystem {
  public func resignRemoteID(_ id: ActorID) {}
}

デフォルト実装として空の resignRemoteID(_:) が提供されるため、既存の DistributedActorSystem の実装はそのままコンパイル・動作します。この要件を利用したいシステムだけがオーバーライドすればよく、コネクションの解放判断などに使えるようになります。

ランタイムは、リモートの分散アクタープロキシの deinit が走るときに resignRemoteID(_:) を呼び出すように更新されます。既存の resignID(_:) は従来どおり ローカルインスタンス に対してのみ呼ばれ、挙動は変わりません。分散アクターの deinit は、ローカルとリモートで次のように処理が分かれます。

  • リモート参照の場合: resignRemoteID(self.id) を呼び出し、id と actorSystem のメモリだけを解放します。ユーザーが記述した deinit の本体は、従来どおり実行されません。
  • ローカルインスタンスの場合: ユーザーが記述した deinit の本体を実行し、resignID(self.id) を呼び出したうえで、すべての stored property を解放します。

まとめると、ライフサイクル管理は次のようになります。

  ローカルインスタンス リモート参照
登録 初期化時の assignID(_:) nil を返す resolve(id:as:)
ready 化 初期化後の actorReady(_:) なし
破棄 deinit 時の resignID(_:) deinit 時の resignRemoteID(_:)

resolve(id:as:) は同じ ID で複数回呼ばれることがあるため、resignRemoteID(_:) も同じ ID に対して複数回呼ばれる可能性があります。resolve で生成されたリモート参照ごとに 1 回ずつ呼ばれると考えてください。

なお、resolve(id:as:) が次のいずれかだった場合、対応する resignRemoteID(_:) は呼ばれません。

  • エラーを throw した(参照が生成されていない)。
  • ローカルのアクターを返した(リモート参照ではないため、ローカル側の resignID(_:) の経路が適用される)。

この機能はレビュー時点では実験的機能フラグ DistributedActorResignRemoteID の下に置かれています。