forked from animatedread/Warrior_EA
CopyFileWithRetry (System/SharedFileCopy.mqh) and CModelPersistence:: LoadNetWithRetry independently implemented the identical 5-attempt Sleep-doubled-and-capped retry shape around a different single operation, with a comment on the latter pointing at the former as the "same reasoning" instead of sharing code. Added System/RetryWithBackoff.mqh: an IRetryableOp interface (one bool TryOnce(bool quiet) method, MQL5 has no closures/function pointers that bind per-call-site arguments) plus the RetryWithBackoff(op, attempts, initialDelayMs, delayCapMs) loop. Each call site now defines a tiny local operand class (CCopySharedFileOp, CLoadNetOnceOp) and keeps its own tuning constants (150ms/1000ms cap vs 200ms/2000ms cap) unchanged - pure mechanical relocation, no behavior change. CModelPersistence stays stateless (grep-verified in the prior Persistence extraction): CLoadNetOnceOp is a separate local class, not a new member on CModelPersistence itself. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
49 lines
2.4 KiB
MQL5
49 lines
2.4 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| Warrior_EA |
|
|
//| AnimateDread |
|
|
//| |
|
|
//| The exponential-backoff retry SHAPE, shared by every call site |
|
|
//| that retries one transient-lock-prone operation a fixed number |
|
|
//| of times with an increasing, capped sleep between attempts. Each |
|
|
//| operation's own arguments differ (a file copy vs a model load), |
|
|
//| so the shape is factored out behind a tiny callback interface |
|
|
//| instead of a raw function pointer - MQL5 has no closures, and |
|
|
//| a function pointer cannot bind the per-call-site arguments. |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef WARRIOR_RETRY_WITH_BACKOFF_MQH
|
|
#define WARRIOR_RETRY_WITH_BACKOFF_MQH
|
|
//+------------------------------------------------------------------+
|
|
//| One retryable attempt. quiet is true on every attempt but the |
|
|
//| last, so an implementation that logs its own per-attempt reason |
|
|
//| can stay silent until the failure actually matters. |
|
|
//+------------------------------------------------------------------+
|
|
class IRetryableOp
|
|
{
|
|
public:
|
|
virtual bool TryOnce(bool quiet) = 0;
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Runs op.TryOnce() up to retryAttempts times, sleeping delayMs |
|
|
//| (doubled each attempt, capped at delayCapMs) between attempts. |
|
|
//| Returns the first successful attempt's result, or false if every |
|
|
//| attempt failed. |
|
|
//+------------------------------------------------------------------+
|
|
bool RetryWithBackoff(IRetryableOp *op, const int retryAttempts, const int initialDelayMs, const int delayCapMs)
|
|
{
|
|
if(CheckPointer(op) == POINTER_INVALID)
|
|
return false;
|
|
int delayMs = initialDelayMs;
|
|
bool ok = false;
|
|
for(int attempt = 0; attempt < retryAttempts && !ok; attempt++)
|
|
{
|
|
if(attempt > 0)
|
|
{
|
|
Sleep(delayMs);
|
|
delayMs = (int)MathMin(delayMs * 2, delayCapMs);
|
|
}
|
|
ok = op.TryOnce(attempt < retryAttempts - 1);
|
|
}
|
|
return ok;
|
|
}
|
|
#endif // WARRIOR_RETRY_WITH_BACKOFF_MQH
|
|
//+------------------------------------------------------------------+
|