in Sources/DistributedActors/Supervision.swift [500:550]
func handleError(context: _ActorContext<Message>, target: _Behavior<Message>, processingAction: ProcessingAction<Message>, error: Error) throws -> _Behavior<Message> {
var errorToHandle = error
// The following restart loop exists to support interpreting `_PreRestart` and `Start` signal interpretation failures;
// If the actor fails during restarting, this failure becomes the new failure reason, and we supervise this failure
// this allows "try again a few times restarts" to happen even if the fault occurs in starting the actor (e.g. opening a file or obtaining some resource failing).
//
// This "restart loop" matters only for:
// - `.restartImmediately` decisions which result in throwing during `_restartPrepare` OR `_restartComplete`,
// - or `.restartDelayed` decisions which result in throwing during `_restartPrepare`.
//
// Since this is a special situation somewhat, in which the tight crash loop could consume considerable resources (and maybe never recover),
// we limit the number of times the restart is attempted
repeat {
switch processingAction {
case .closure(let closure):
context.log.warning("Actor has THROWN [\(errorToHandle)]:\(type(of: errorToHandle)) while interpreting [closure defined at \(closure.file):\(closure.line)], handling with \(self.descriptionForLogs)")
default:
context.log.warning("Actor has THROWN [\(errorToHandle)]:\(type(of: errorToHandle)) while interpreting \(processingAction), handling with \(self.descriptionForLogs)")
}
let directive: Directive
do {
directive = try self.handleFailure(context, target: target, failure: .error(errorToHandle), processingType: processingAction.type)
} catch {
// An error was thrown by our Supervisor logic while handling the failure, this is a bug and thus we crash hard
throw _Supervision.SupervisionError.illegalDecision("Illegal supervision decision detected.", handledError: errorToHandle, error: error)
}
do {
switch directive {
case .stop:
return .stop(reason: .failure(.error(error)))
case .escalate(let failure):
return context._downcastUnsafe._escalate(failure: failure)
case .restartImmediately(let replacement):
try context._downcastUnsafe._restartPrepare()
return try context._downcastUnsafe._restartComplete(with: replacement)
case .restartDelayed(let delay, let replacement):
try context._downcastUnsafe._restartPrepare()
return SupervisionRestartDelayedBehavior.after(delay: delay, with: replacement)
}
} catch {
errorToHandle = error // the error captured from restarting is now the reason why we are failing, and should be passed to the supervisor
continue // by now supervising the errorToHandle which has just occurred
}
} while true // the only way to break out of here is succeeding to interpret `directive` OR supervisor giving up (e.g. max nr of restarts being exhausted)
}