DEV Community

GoyesDev
GoyesDev

Posted on

[SC] Reentrada de actores

Preguntas

¿Qué significa exactamente la "reentrada de un actor" (actor reentrancy)?

Al usar await dentro de un actor, otra tarea potencialmente puede ser ejecutada mientras que la primera está suspendida.

actor Player {
  var name = "Anonymous"
  var score = 0

  func addToScore() {    
    Task {
      score += 1
      try? await Task.sleep(for: .seconds(1))
      print("Score is now \(score)")
    }
  }
}

let player = Player()
await player.addToScore()
await player.addToScore()
await player.addToScore()

try? await Task.sleep(for: .seconds(1.1))
Enter fullscreen mode Exit fullscreen mode

En el ejemplo anterior, cada vez que el Task del actor se suspende en await Task.sleep(for: .seconds(1)) permite que actor ejecute cualquier otra tarea. A esto se le conoce como "interleaving".

"Actor reentrancy" es el fenómeno que se produce cuando, debido al interleaving, una tarea modifica de forma inesperada el estado que estaba usando otra tarea.

El problema se resume en que el estado del actor puede cambiar entre dos líneas si hay un await de por medio.

¿Por qué salir temporalmente de un actor puede causar comportamiento inesperado?

Cuando se sale del actor y se vuelve a entrar, es posible el estado ya haya cambiado. Por esta razón, antes de salir del dominio del actor, hay que procurar terminar las operaciones que cambian el estado.

¿Qué condición permite que otras tareas "entren" al actor mientras este está suspendido?

Un punto de suspensión.

¿Cómo afecta el uso de await al flujo de ejecución dentro de un actor?

El método se suspende, y el actor permite que otra tarea se ejecute mientras tanto.

En el ejemplo del BankAccount, ¿por qué el saldo impreso es 250.0 tres veces en lugar de 150.0, 200.0 y 250.0?

Las tres invocaciones al depósito salían a hacer un sleep. Las tres tareas modificaron el estado del BankAccount, pero después de salir del actor, volvían a depender de ese estado, por lo que las tres tareas reflejaron el estado final después de la última ejecución.

¿Qué papel cumple el BankAccountActivityLogger en el bug descrito?

Hace un Task.sleep que demora la ejecución, además, es otro dominio de aislamiento diferente, lo que ilustra el cambio entre dominios. Sin embargo, el problema también se presenta incluso aunque no se salga del dominio de un actor como se mostró en Player.

actor BankAccount {
  var balance: Double
  let activityLogger = BankAccountActivityLogger()

  init(initialDeposit: Double) {
    self.balance = initialDeposit
  }

  func deposit(amount: Double) async {
    balance = balance + amount
    await activityLogger.log(BankAccountActivity(activity: "Increased balance by \(amount)"))
    print("Balance is now \(balance)")
  }
}

struct BankAccountActivity {
  let date: Date = .now
  let activity: String
}

actor BankAccountActivityLogger {
  private var activities: [BankAccountActivity] = []

  func log(_ activity: BankAccountActivity) async {
    activities.append(activity)

    /// Se agrega un sleep para simular la sincronización remota de la actividad.
    try? await Task.sleep(for: .seconds(1))
  }
}
Enter fullscreen mode Exit fullscreen mode

¿Por qué el Task.sleep dentro del logger es relevante para reproducir el problema?

Provoca suficiente retraso como para que otra tarea del actor pueda provocar cambios.


Recordar

¿Cuál fue la solución propuesta para corregir el bug de reentrada en el método deposit?

En tus propias palabras, ¿cuál es la regla general que se debe seguir antes de salir del dominio de aislamiento de un actor?


Revisión

¿En qué tipos de situaciones reales podría aparecer un bug de actor reentrancy fuera de este ejemplo?

¿Por qué el artículo dice que este es un problema "caso por caso" sin una solución universal?


Bibliografía

Top comments (0)