Wróć do bloga

Until Successful bez magii: drzewo decyzji, RETRY_EXHAUSTED i suppressedErrors

2026-09-17

Until Successful to nie amulet na niestabilne integracje. To synchroniczny scope, który ponawia procesory aż wszystkie się uda albo wyczerpie maxRetries — wtedy rzuca MULE:RETRY_EXHAUSTED. Zespoły wpadają, gdy retryują błędy biznesowe, wstawiają On Error Continue tam, gdzie potrzebny był Propagate (scope „kończy się sukcesem” i nie retryuje), albo obsługują exhaustion bez odczytu suppressed błędów bazowych.

Czego ten tekst nie jest: klonem Medium z samym XML, tutorialem klikania w Studio ani radą „opakuj wszystko w Until Successful”. To drzewo decyzji + taksonomia + antywzorce dla developerów / architektów, którzy potrzebują retry connectivity bez zamiany błędów walidacji w storm retry.

Poniżej: kiedy retryować, jak Try + On Error steruje retry vs skip, co znaczą RETRY_EXHAUSTED i suppressedErrors, antywzorce, checklista, FAQ.


Co Until Successful naprawdę robi

Z docs Until Successful Scope:

  • Procesory w środku lecą po kolei, synchronicznie.
  • Gdy któryś padnie, Until Successful ponawia wszystkie procesory w scope, w tym ten, który padł, aż do sukcesu albo wyczerpania retry.
  • Po ostatniej porażce: komunikat w stylu 'until-successful' retries exhausted. — typ MULE:RETRY_EXHAUSTED.
  • Atrybuty: maxRetries (liczba lub expression); millisBetweenRetries (minimalny odstęp; domyślnie 60000 ms; rzeczywisty odstęp zależy od czasu poprzedniej próby, nie powinien przekroczyć dwukrotności wartości).
  • Propagacja zmiennych: każda próba startuje ze zmiennymi sprzed scope. Zmiany z nieudanej próby nie wchodzą do następnej. Po sukcesie zmienne i payload idą dalej w flow.

Typowe przypadki z docs: outbound z problemami dostępności, komponenty zależne od niestabilnych zasobów, krótki łańcuch akcji aż się uda.


Drzewo decyzji: retry czy nie?

Zanim wrzucisz Until Successful na call, idź po kolei:

1. Czy failure jest przejściowy (connectivity, krótki outage, rate-limit z backoffem)?
   NIE → NIE używaj Until Successful dla tego typu błędu.
   TAK → dalej.

2. Czy operacja jest idempotentna (albo bezpiecznie ponowić cały scope)?
   NIE → retry re-wykonuje WSZYSTKIE procesory w scope — ryzyko side-effectów.
   TAK → dalej.

3. Czy umiesz nazwać retryowalne typy (np. HTTP:CONNECTIVITY, DB:CONNECTIVITY)?
   NIE → przypadkiem retryujesz BAD_REQUEST / UNAUTHORIZED / walidację biznesową.
   TAK → zagnieźdź Try; Propagate tylko te typy.

4. Po exhaustion: potrzebujesz DLQ / NACK / alert / zmapowany błąd API?
   TAK → handler na poziomie flow na RETRY_EXHAUSTED (+ inspect suppressed cause).
Sytuacja Preferuj
Przejściowy *:CONNECTIVITY / krótki outage Until Successful + Try; Propagate typów retryowalnych
HTTP:BAD_REQUEST, auth, walidacja schematu Bez retry — On Error Continue (lub Propagate poza US) na ścieżkę client/DLQ
Wyczerpane retry Obsłuż MULE:RETRY_EXHAUSTED; DLQ / NACK / alert
Critical JVM / overload Nie traktuj jako „po prostu retry” — CRITICAL / feature flags

Taksonomia: Continue vs Propagate wewnątrz Until Successful

Until Successful retryuje tylko wtedy, gdy procesor psuje scope. Sterujesz tym obsługą błędów w środku — niemal zawsze przez zagnieżdżony Try.

Z On-Error Components / Error Handlers:

Handler Efekt na ownera (Try / flow) Efekt na Until Successful
On Error Propagate Owner pada; błąd re-throw Scope widzi failure → retry
On Error Continue Owner traktowany jako sukces Scope widzi success → bez retry; flow idzie dalej

Wzorzec (retry tylko connectivity):

<until-successful maxRetries="3" millisBetweenRetries="2000">
  <try>
    <http:request method="GET" config-ref="HTTP_Request_configuration" path="/resource"/>
    <error-handler>
      <on-error-propagate type="HTTP:CONNECTIVITY"/>
      <on-error-continue type="HTTP:BAD_REQUEST, HTTP:UNAUTHORIZED, HTTP:NOT_FOUND">
        <!-- mapuj na payload client/DLQ; US NIE będzie retryował -->
      </on-error-continue>
    </error-handler>
  </try>
</until-successful>

Kolejność ma znaczenie: typy szczegółowe przed ANY. Matching jest sekwencyjny (On-Error docs).


MULE:RETRY_EXHAUSTED po ostatniej próbie

Gdy ostatnia próba pada, Until Successful rzuca MULE:RETRY_EXHAUSTED. Docs pokazują handler na poziomie flow:

<error-handler>
  <on-error-continue type="RETRY_EXHAUSTED">
    <logger level="INFO" message="File upload failed"/>
  </on-error-continue>
</error-handler>

(Until Successful Scope — przykład FTP.)

Mule loguje każdą nieudaną próbę przed finalnym błędem (Retrying execution of event, attempt N of M).

Mule 3 → 4: deadLetterQueue-ref zniknął. Łap RETRY_EXHAUSTED w error handlerze i wyślij na DLQ / endpoint (Migrating the Until Successful).

Z Mule Errors: RETRY_EXHAUSTED = wyczerpane retry bloku wykonania (Until Successful lub retry connectora). Connectory mają w hierarchii też CONNECTIVITY i RETRY_EXHAUSTED.


Error suppression i suppressedErrors

Przy włączonej fladze mule.suppress.mule.exceptions (domyślnie) komponenty takie jak Until Successful i Web Service Consumer raportują błędy w swoich namespace’ach (np. MULE:RETRY_EXHAUSTED), a oryginalny błąd connectora staje się underlying / suppressed cause. Docs: Suppressed errors are treated as underlying causes that can also be matched by On Error handlers (Feature Flagging Mechanism; Help: How to disable the error suppression feature in runtime 4.4).

Po exhaustion w praktyce:

  1. Typ powierzchniowy to często MULE:RETRY_EXHAUSTED (detailedDescription: 'until-successful' retries exhausted).
  2. Ostatni underlying (np. HTTP:CONNECTIVITY) ląduje w suppressedErrors w obiekcie błędu (dump z Help pokazuje suppressedErrors=[ { errorType=HTTP:CONNECTIVITY, ... } ]).
  3. Handlery mogą matchować typy suppressed jako underlying causes — nie jesteś ograniczony tylko do powierzchniowego RETRY_EXHAUSTED (feature-flagging docs).
  4. Wyłączenie suppression (mule.suppress.mule.exceptions=false) to właściwość system / server-level (Help); nie zakładaj przełącznika per-app na CloudHub bez sprawdzenia opcji runtime.

Nota uczciwości: publiczne tabele selektorów DataWeave wymieniają description, errorType, cause, errorMessage, childErrors (Mule Errors, Predefined Variables) — bez osobnego wiersza suppressedErrors. Help dump + feature-flagging to główne źródła Anypoint dla modelu suppression; preferuj type-matching suppressed causes i/lub inspect struktury błędu w logach/debuggerze zamiast wymyślać nieudokumentowane API.


Antywzorce (i co zamiast)

1. On Error Continue jako jedyny handler w Until Successful

Objaw: retry nigdy nie startuje; flow idzie jakby call się udał.

Przyczyna: Continue oznacza sukces Try/ownera → Until Successful nie retryuje.

Fix: Propagate dla typów retryowalnych; Continue tylko dla świadomie pomijanych błędów biznesowych.

2. Retry BAD_REQUEST / walidacji / auth

Objaw: ten sam 400/401/422 walony maxRetries razy; latency; rate limity partnera.

Przyczyna: goły Until Successful wokół HTTP bez filtracji Try.

Fix: krok 3 drzewa — Propagate tylko typów przejściowych.

3. Nieidempotentne procesory w scope

Objaw: podwójne POST-y, podwójne płatności, podwójne zapisy pliku przy każdej próbie.

Przyczyna: docs — przy failure Until Successful ponawia wszystkie procesory w scope, nie tylko ten, który padł.

Fix: scope minimalny (jeden outbound); side-effecty poza lub idempotentne (patrz artykuł o idempotencji OSv2).

4. Obsługa tylko RETRY_EXHAUSTED bez root cause

Objaw: w ops same „retries exhausted”; brak statusu HTTP / connectivity do triage.

Przyczyna: suppression pokazuje namespace MULE; underlying siedzi w suppressed causes.

Fix: loguj/matchuj suppressed cause; alertuj exhaustion i typ bazowy.

5. Oczekiwanie Mule 3 failureExpression / deadLetterQueue-ref

Objaw: konfiguracja nie migruje; DLQ milczy.

Fix: Validation zamiast failureExpression; error handler na RETRY_EXHAUSTED zamiast deadLetterQueue-ref (migration guide).

6. CRITICAL / overload jako „retryowalne”

Objaw: pętle retry przy stresie JVM.

Docs: flaga mule.untilSuccessful.retryOnCriticalError.disallow — gdy włączona, eventy nie są retryowane przy MULE:CRITICAL w Until Successful (Feature Flagging). Preferuj fail-fast + restart/health zamiast ślepego retry.


Checklista

  1. Wypisz typy retryowalne vs nieretryowalne dla tego calla.
  2. Zagnieźdź Try w Until Successful; Propagate tylko typów retryowalnych.
  3. Continue (lub wyjście) dla business/validation/auth — bez retry.
  4. Ustaw świadomie maxRetries i millisBetweenRetries (domyślny odstęp to 1 minuta).
  5. Scope mały i idempotentny — cały scope się powtarza.
  6. Handler flow na RETRY_EXHAUSTED → DLQ / NACK / zmapowana odpowiedź / alert.
  7. Uwzględnij error suppression: powierzchnia może być MULE:RETRY_EXHAUSTED; inspect suppressed underlying.
  8. Pamiętaj o resecie zmiennych między nieudanymi próbami.
  9. MUnit: assert liczby retry na ścieżce Propagate i zera retry na Continue.
  10. Nie zastępuj Until Successful brokerem (MQ), gdy potrzebujesz trwałego async retry.

FAQ

1. Jaki błąd rzuca Until Successful po wyczerpaniu retry?

MULE:RETRY_EXHAUSTED (komunikat: 'until-successful' retries exhausted.) — Until Successful Scope.

2. Dlaczego Until Successful nie retryuje?

Zwykle On Error Continue (albo cokolwiek, co oznacza sukces próby) w środku scope. Propagate dla błędów, które mają być ponawiane.

3. Czy retry odpala tylko padnięty procesor?

Nie — docs: ponawia wszystkie procesory w scope, w tym ten, który padł.

4. Co to suppressed errors?

Przy mule.suppress.mule.exceptions (domyślnie) Until Successful raportuje w namespace MULE; underlying błędy connectora to suppressed causes, które nadal mogą być matchowane przez On Error (Feature Flagging; Help).

5. Jak zastąpić Mule 3 deadLetterQueue-ref?

Łap RETRY_EXHAUSTED w error handlerze i kieruj na DLQ (migration).

6. Domyślne millisBetweenRetries?

60000 (jedna minuta), jeśli nie ustawisz (Until Successful Scope).

7. Czy retryować HTTP 400?

Prawie nigdy — nieretryowalne przez On Error Continue (albo fail bez US).

8. Czy zmienne przechodzą między nieudanymi próbami?

Nie — każda próba startuje ze zmiennych sprzed scope; zmiany z failed attempt są odrzucane (Until Successful Scope).

9. Continue vs Propagate w jednym zdaniu?

Continue = owner sukces (bez retry US). Propagate = owner fail (US retryuje).


Soft CTA

Projektujesz retry vs fail-fast dla calli System API (connectivity vs walidacja) i chcesz przejrzeć nesting Until Successful, handlery RETRY_EXHAUSTED oraz suppression zanim produkcja urośnie w storm retry? Solita to nordycki partner MuleSoft z dostawą z Polski (EU-shoring) — pomagamy ułożyć taksonomię błędów i ścieżki DLQ. Bez obietnic „#1” i bez checklisty marketingowej.


Źródła

Dokumentacja

Help

YouTube (oEmbed-verified 2026-09-17)