Gestione degli errori in Funzioni permanenti (Funzioni di Azure)
Articolo
Le orchestrazioni di funzioni durevoli vengono implementate nel codice e possono usare le funzionalità di gestione degli errori predefinite del linguaggio di programmazione. Non sono presenti nuovi concetti che è necessario imparare ad aggiungere la gestione degli errori e la compensazione nelle orchestrazioni. È tuttavia opportuno conoscere alcuni comportamenti.
Nota
La versione 4 del modello di programmazione Node.js per Funzioni di Azure è disponibile a livello generale. Il nuovo modello v4 è progettato per avere un'esperienza più flessibile e intuitiva per sviluppatori JavaScript e TypeScript. Altre informazioni sulle differenze tra v3 e v4 nella guida alla migrazione.
Nei frammenti di codice seguenti JavaScript (PM4) indica il modello di programmazione V4, la nuova esperienza.
Errori nelle funzioni di attività
Qualsiasi eccezione generata in una funzione di attività viene eseguito il marshalling della funzione dell'agente di orchestrazione e generata come FunctionFailedException. È possibile scrivere il codice di compensazione e gestione degli errori adatto alle esigenze nella funzione dell'agente di orchestrazione.
Ad esempio, si consideri la seguente funzione dell'agente di orchestrazione che consente di trasferire fondi da un account a un altro:
[FunctionName("TransferFunds")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var transferDetails = context.GetInput<TransferOperation>();
await context.CallActivityAsync("DebitAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
try
{
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.DestinationAccount,
Amount = transferDetails.Amount
});
}
catch (Exception)
{
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
}
}
Nota
Gli esempi C# precedenti sono per Durable Functions 2.x. Per Durable Functions 1.x, è necessario usare DurableOrchestrationContext anziché IDurableOrchestrationContext. Per altre informazioni sulle differenze tra le versioni, vedere l'articolo Durable Functions versioni.
[FunctionName("TransferFunds")]
public static async Task Run(
[OrchestrationTrigger] TaskOrchestrationContext context, TransferOperation transferDetails)
{
await context.CallActivityAsync("DebitAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
try
{
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.DestinationAccount,
Amount = transferDetails.Amount
});
}
catch (Exception)
{
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
}
}
const df = require("durable-functions");
module.exports = df.orchestrator(function* (context) {
const transferDetails = context.df.getInput();
yield context.df.callActivity("DebitAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
try {
yield context.df.callActivity("CreditAccount", {
account: transferDetails.destinationAccount,
amount: transferDetails.amount,
});
} catch (error) {
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
yield context.df.callActivity("CreditAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
}
})
const df = require("durable-functions");
df.app.orchestration("transferFunds", function* (context) {
const transferDetails = context.df.getInput();
yield context.df.callActivity("debitAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
try {
yield context.df.callActivity("creditAccount", {
account: transferDetails.destinationAccount,
amount: transferDetails.amount,
});
} catch (error) {
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
yield context.df.callActivity("creditAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
}
});
Per impostazione predefinita, i cmdlet in PowerShell non generano eccezioni che possono essere rilevate usando i blocchi try/catch. Sono disponibili due opzioni per modificare questo comportamento:
Usare il -ErrorAction Stop flag quando si richiamano cmdlet, ad esempio Invoke-DurableActivity.
Impostare la variabile di preferenza su "Stop" nella funzione dell'agente $ErrorActionPreference di orchestrazione prima di richiamare i cmdlet.
Per altre informazioni sulla gestione degli errori in PowerShell, vedere la documentazione di Try-Catch-Finally PowerShell.
@FunctionName("TransferFunds")
public void transferFunds(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
TransferOperation transfer = ctx.getInput(TransferOperation.class);
ctx.callActivity(
"DebitAccount",
new OperationArgs(transfer.sourceAccount, transfer.amount)).await();
try {
ctx.callActivity(
"CreditAccount",
new OperationArgs(transfer.destinationAccount, transfer.amount)).await();
} catch (TaskFailedException ex) {
// Refund the source account on failure
ctx.callActivity(
"CreditAccount",
new OperationArgs(transfer.sourceAccount, transfer.amount)).await();
}
}
Se la prima chiamata di funzione CreditAccount ha esito negativo, la funzione dell'agente di orchestrazione compensa il credito dei fondi all'account di origine.
Ripetizione automatica in caso di errore
Quando si chiamano le funzioni di attività o di orchestrazione secondaria, è possibile specificare un criterio di ripetizione automatica. Nell'esempio seguente si tenta di chiamare una funzione fino a tre volte e si attende 5 secondi tra un tentativo e l'altro:
[FunctionName("TimerOrchestratorWithRetry")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var retryOptions = new RetryOptions(
firstRetryInterval: TimeSpan.FromSeconds(5),
maxNumberOfAttempts: 3);
await context.CallActivityWithRetryAsync("FlakyFunction", retryOptions, null);
// ...
}
Nota
Gli esempi C# precedenti sono per Durable Functions 2.x. Per Durable Functions 1.x, è necessario usare DurableOrchestrationContext anziché IDurableOrchestrationContext. Per altre informazioni sulle differenze tra le versioni, vedere l'articolo Durable Functions versioni.
@FunctionName("TimerOrchestratorWithRetry")
public void timerOrchestratorWithRetry(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
final int maxAttempts = 3;
final Duration firstRetryInterval = Duration.ofSeconds(5);
RetryPolicy policy = new RetryPolicy(maxAttempts, firstRetryInterval);
TaskOptions options = new TaskOptions(policy);
ctx.callActivity("FlakeyFunction", options).await();
// ...
}
La chiamata alla funzione di attività nell'esempio precedente accetta un parametro per la configurazione di un criterio di ripetizione automatica dei tentativi. Esistono diverse opzioni per personalizzare i criteri di ripetizione automatica dei tentativi:
Numero massimo di tentativi: numero massimo di tentativi. Se impostato su 1, non verrà eseguito alcun tentativo.
First retry interval (Intervallo primo tentativo): il tempo di attesa prima del primo tentativo.
Backoff coefficient (Coefficiente di backoff): il coefficiente usato per determinare la frequenza di aumento del backoff. Assume il valore predefinito 1.
Max retry interval (Intervallo massimo tra tentativi): il tempo di attesa massimo tra i tentativi di ripetizione.
Retry timeout (Timeout tentativi): il tempo massimo a disposizione per i tentativi. Il comportamento predefinito è la ripetizione per un periodo illimitato.
Gestori di tentativi personalizzati
Quando si usa .NET o Java, è anche possibile implementare i gestori di ripetizione dei tentativi nel codice. Questo è utile quando i criteri di ripetizione dichiarativa non sono sufficienti. Per le lingue che non supportano gestori di tentativi personalizzati, è comunque possibile implementare criteri di ripetizione dei tentativi usando cicli, gestione delle eccezioni e timer per inserire ritardi tra i tentativi.
RetryOptions retryOptions = new RetryOptions(
firstRetryInterval: TimeSpan.FromSeconds(5),
maxNumberOfAttempts: int.MaxValue)
{
Handle = exception =>
{
// True to handle and try again, false to not handle and throw.
if (exception is TaskFailedException failure)
{
// Exceptions from TaskActivities are always this type. Inspect the
// inner Exception to get more details.
}
return false;
};
}
await ctx.CallActivityWithRetryAsync("FlakeyActivity", retryOptions, null);
TaskOptions retryOptions = TaskOptions.FromRetryHandler(retryContext =>
{
// Don't retry anything that derives from ApplicationException
if (retryContext.LastFailure.IsCausedBy<ApplicationException>())
{
return false;
}
// Quit after N attempts
return retryContext.LastAttemptNumber < 3;
});
try
{
await ctx.CallActivityAsync("FlakeyActivity", options: retryOptions);
}
catch (TaskFailedException)
{
// Case when the retry handler returns false...
}
JavaScript non supporta attualmente gestori di tentativi personalizzati. Tuttavia, è comunque possibile implementare la logica di ripetizione dei tentativi direttamente nella funzione dell'agente di orchestrazione usando cicli, gestione delle eccezioni e timer per inserire ritardi tra i tentativi.
JavaScript non supporta attualmente gestori di tentativi personalizzati. Tuttavia, è comunque possibile implementare la logica di ripetizione dei tentativi direttamente nella funzione dell'agente di orchestrazione usando cicli, gestione delle eccezioni e timer per inserire ritardi tra i tentativi.
Python attualmente non supporta gestori di tentativi personalizzati. Tuttavia, è comunque possibile implementare la logica di ripetizione dei tentativi direttamente nella funzione dell'agente di orchestrazione usando cicli, gestione delle eccezioni e timer per inserire ritardi tra i tentativi.
PowerShell non supporta attualmente gestori di tentativi personalizzati. Tuttavia, è comunque possibile implementare la logica di ripetizione dei tentativi direttamente nella funzione dell'agente di orchestrazione usando cicli, gestione delle eccezioni e timer per inserire ritardi tra i tentativi.
RetryHandler retryHandler = retryCtx -> {
// Don't retry anything that derives from RuntimeException
if (retryCtx.getLastFailure().isCausedBy(RuntimeException.class)) {
return false;
}
// Quit after N attempts
return retryCtx.getLastAttemptNumber() < 3;
};
TaskOptions options = new TaskOptions(retryHandler);
try {
ctx.callActivity("FlakeyActivity", options).await();
} catch (TaskFailedException ex) {
// Case when the retry handler returns false...
}
Timeout delle funzioni
Potrebbe essere necessario abbandonare una chiamata di funzione all'interno di una funzione dell'agente di orchestrazione se è necessario troppo tempo per completare. Il modo appropriato per eseguire questa operazione è creare un timer durevole con un selettore di attività "any", come nell'esempio seguente:
Gli esempi C# precedenti sono per Durable Functions 2.x. Per Durable Functions 1.x, è necessario usare DurableOrchestrationContext anziché IDurableOrchestrationContext. Per altre informazioni sulle differenze tra le versioni, vedere l'articolo Durable Functions versioni.
Questo meccanismo non termina effettivamente l'esecuzione della funzione di attività in corso, ma consente semplicemente alla funzione di orchestrazione di ignorare il risultato e continuare. Per altre informazioni, vedere la documentazione sui Timer.
Eccezioni non gestite
Se una funzione dell'agente di orchestrazione ha esito negativo generando un'eccezione non gestita, i dettagli dell'eccezione vengono registrati e l'istanza viene completata con uno stato Failed.
Presto disponibile: Nel corso del 2024 verranno gradualmente disattivati i problemi di GitHub come meccanismo di feedback per il contenuto e ciò verrà sostituito con un nuovo sistema di feedback. Per altre informazioni, vedere https://aka.ms/ContentUserFeedback.