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))
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))
}
}
¿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.
Top comments (0)