Gestire il timeout di un'operazione con Polly in ASP.NET Core MVC

di Marco De Sanctis, in ASP.NET Core,



Negli ultimi due script abbiamo trattato alcuni accorgimenti che ci permettono di rendere la nostra applicazione più resistente a malfunzionamenti che possono accadere ai servizi a cui accede. Nel farlo, abbiamo introdotto l'uso di Polly (https://github.com/App-vNext/Polly), una libreria open source che espone tutta una serie di pattern estremamente utili, come Retry, Circuit Breaker, Fallback, ecc.

In questo script, ci occuperemo della gestione dei Timeout. Quando accediamo a risorse esterne, è sempre un ottimo accorgimento darsi un tempo limite entro cui la chiamata al servizio debba essere completata. La motivazione è molto semplice: un servizio che non risponde nel tempo "usuale", molto probabilmente non risponderà mai, pertanto è meglio terminare in anticipo la richiesta senza attendere troppo.

Altrimenti, potremmo trovarci ad attendere per secondi che una risorsa non disponibile ci palesi il suo stato, con il solo risultato di aver fatto aspettare l'utente più del dovuto.

Questo accorgimento è particolarmente importante anche nei casi in cui dobbiamo rispettare una Service Level Agreement (SLA), e non vogliamo essere troppo penalizzati dalle prestazioni delle nostre dipendenze.

Come gestirlo con Polly? Al solito, il primo passo è sempre quello di installare il relativo package NuGet:

install-package Polly

A questo punto, in Startup.cs, possiamo definire una TimeoutPolicy come la seguente, per terminare l'esecuzione dopo - per esempio - 500ms:

public void ConfigureServices(IServiceCollection services)
{
  // .. altri servizi ..
  var timeout = Policy
    .Timeout(TimeSpan.FromMilliseconds(500), TimeoutStrategy.Pessimistic);

  services.AddSingleton<TimeoutPolicy>(timeout);
}

Ora possiamo spostarci sul controller e utilizzarla per invocare il servizio:

public HomeController(IContentService content, TimeoutPolicy timeout)
{
  _content = content;
  _timeout = timeout;
}

public async Task<IActionResult> Index()
{
  string[] articles;

  Stopwatch watch = Stopwatch.StartNew();
  try
  {
    articles = await _timeout.Execute(
      async () => await _content.LoadArticlesAsync());
  }
  catch (Exception ex)
  {
    articles = new[] { "Articoli non disponibili al momento" };
  }

  watch.Stop();

  ViewBag.Time = $"{watch.ElapsedMilliseconds} ms";

  return View(articles);
}

Il controller accetta la policy che abbiamo configurato come parametro nel costruttore e, nella action Index, ne sfrutta il metodo Execute per invocare LoadArticlesAsync. Se l'esecuzione non dovesse terminare entro i 500ms stabiliti, questo metodo solleverà una TimeoutRejectException e verrà mostrato il messaggio "articoli non disponibili" all'utente.

Il beneficio, come già detto, è che l'attesa durerà al più 500ms, anche se il servizio - lo si può notare dal codice allegato - è fatto in modo di durare per ben 10 secondi.


Prima di concludere vale la pena spendere due parole su cosa accade al delegate eseguito in caso si verifichi il timeout. Contrariamente a ciò che potremmo aspettarci, l'esecuzione del metodo LoadArticlesAsync verrà comunque completata: Polly si limita infatti a restituire il controllo al chiamante entro il timeout, ma non effettua l'abort del delegate per non rischiare di compromettere lo stato della nostra applicazione. Questa strategia di esecuzione prende il nome di Pessimistic, come abbiamo specificato in fase di configurazione:

var timeout = Policy
  .Timeout(TimeSpan.FromMilliseconds(500), TimeoutStrategy.Pessimistic);

La modalità di default, invece, si basa sulla strategia Optimistic, che sfrutta il concetto di CancellationToken per terminare in maniera "graceful" l'esecuzione del metodo. Ne mostreremo un esempio di utilizzo nel prossimo script.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi