2026-08-23 21:18:32 -04:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Warrior_EA |
|
|
|
|
|
//| AnimateDread |
|
|
|
|
|
//| |
|
|
|
|
|
//| Share-aware file copy, retried against a transient lock. Pure |
|
|
|
|
|
//| functions - no state, no class needed (same doctrine as |
|
|
|
|
|
//| System\TradeChecks.mqh's TC* free functions). |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
#ifndef WARRIOR_SHARED_FILE_COPY_MQH
|
|
|
|
|
#define WARRIOR_SHARED_FILE_COPY_MQH
|
2026-08-24 02:55:04 -04:00
|
|
|
#include "RetryWithBackoff.mqh"
|
2026-08-23 21:18:32 -04:00
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Share-aware streamed file copy FROM the shared (FILE_COMMON) |
|
|
|
|
|
//| folder INTO this program's own sandbox (the tester agent's local |
|
|
|
|
|
//| MQL5\Files when running under the Strategy Tester). |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CopySharedFile(string srcFileName, string dstFileName, bool quiet)
|
|
|
|
|
{
|
|
|
|
|
ResetLastError();
|
|
|
|
|
int src = FileOpen(srcFileName, FILE_COMMON | FILE_BIN | FILE_READ | FILE_SHARE_READ | FILE_SHARE_WRITE);
|
|
|
|
|
if(src == INVALID_HANDLE)
|
|
|
|
|
{
|
|
|
|
|
//--- Distinguish the two failure stages explicitly - a source failure here (with the share flags
|
|
|
|
|
//--- already set) would mean the shared folder itself is unreachable from this sandbox, which is a
|
|
|
|
|
//--- completely different problem from a destination/sandbox write failure below.
|
|
|
|
|
if(!quiet)
|
|
|
|
|
Print(__FUNCTION__ + ": cannot open SOURCE " + srcFileName + " in the shared folder, error " +
|
|
|
|
|
IntegerToString(GetLastError()) + " (share flags were set, so this is not a lock).");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
ulong size = FileSize(src);
|
|
|
|
|
uchar buf[];
|
|
|
|
|
//--- 0-byte source would produce a 0-byte model file that CNet::Load rejects later as a stub - refuse
|
|
|
|
|
//--- it here instead, so the caller falls back to a fresh topology with an accurate reason logged.
|
|
|
|
|
if(size == 0 || ArrayResize(buf, (int)size) != (int)size)
|
|
|
|
|
{
|
|
|
|
|
FileClose(src);
|
|
|
|
|
if(!quiet)
|
|
|
|
|
Print(__FUNCTION__ + ": refusing to copy " + srcFileName + " - source is " + IntegerToString((int)size) +
|
|
|
|
|
" bytes (empty, or too large to buffer).");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
uint read = FileReadArray(src, buf, 0, (int)size);
|
|
|
|
|
FileClose(src);
|
|
|
|
|
if(read != (uint)size)
|
|
|
|
|
{
|
|
|
|
|
if(!quiet)
|
|
|
|
|
Print(__FUNCTION__ + ": short read on " + srcFileName + " (" + IntegerToString((int)read) + " of " +
|
|
|
|
|
IntegerToString((int)size) + " bytes) - not copying a partial model.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
//--- destination is this program's OWN sandbox (no FILE_COMMON) - never the shared production folder.
|
|
|
|
|
string tmpName = dstFileName + ".copytmp";
|
|
|
|
|
ResetLastError();
|
|
|
|
|
int dst = FileOpen(tmpName, FILE_BIN | FILE_WRITE | FILE_SHARE_READ | FILE_SHARE_WRITE);
|
|
|
|
|
if(dst == INVALID_HANDLE)
|
|
|
|
|
{
|
|
|
|
|
if(!quiet)
|
|
|
|
|
Print(__FUNCTION__ + ": cannot open DESTINATION " + tmpName + " in this agent's sandbox, error " +
|
|
|
|
|
IntegerToString(GetLastError()) + ".");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
uint written = FileWriteArray(dst, buf, 0, ArraySize(buf));
|
|
|
|
|
FileFlush(dst);
|
|
|
|
|
FileClose(dst);
|
|
|
|
|
if(written != (uint)size)
|
|
|
|
|
{
|
|
|
|
|
FileDelete(tmpName);
|
|
|
|
|
if(!quiet)
|
|
|
|
|
Print(__FUNCTION__ + ": short write to " + tmpName + " (" + IntegerToString((int)written) + " of " +
|
|
|
|
|
IntegerToString((int)size) + " bytes) - discarded the partial copy.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
//--- atomic swap into place, so a reader never sees a half-written file
|
|
|
|
|
if(!FileMove(tmpName, 0, dstFileName, FILE_REWRITE))
|
|
|
|
|
{
|
|
|
|
|
if(!quiet)
|
|
|
|
|
Print(__FUNCTION__ + ": atomic rename " + tmpName + " -> " + dstFileName + " failed, error " +
|
|
|
|
|
IntegerToString(GetLastError()) + ".");
|
|
|
|
|
ResetLastError();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-08-24 02:55:04 -04:00
|
|
|
//| CopyFileWithRetry's operand - see RetryWithBackoff.mqh's |
|
|
|
|
|
//| declaration comment for the shared exponential-backoff shape |
|
|
|
|
|
//| this plugs into. quiet is forwarded straight to CopySharedFile, |
|
|
|
|
|
//| which is what actually stays silent until the last attempt. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
class CCopySharedFileOp : public IRetryableOp
|
|
|
|
|
{
|
|
|
|
|
private:
|
|
|
|
|
string m_src;
|
|
|
|
|
string m_dst;
|
|
|
|
|
public:
|
|
|
|
|
CCopySharedFileOp(string src, string dst) : m_src(src), m_dst(dst) { }
|
|
|
|
|
virtual bool TryOnce(bool quiet) override { return CopySharedFile(m_src, m_dst, quiet); }
|
|
|
|
|
};
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-08-23 21:18:32 -04:00
|
|
|
//| Retries a FileCopy FROM FILE_COMMON that raced a concurrent |
|
|
|
|
|
//| writer (typically a live chart's own atomic Save(), mid |
|
|
|
|
|
//| write-then-rename on the SAME source file). |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CopyFileWithRetry(string srcFileName, string dstFileName)
|
|
|
|
|
{
|
|
|
|
|
//--- Retries are only a backstop for a genuinely transient hiccup.
|
|
|
|
|
const int RETRY_ATTEMPTS = 5;
|
|
|
|
|
const int RETRY_DELAY_CAP_MS = 1000;
|
2026-08-24 02:55:04 -04:00
|
|
|
CCopySharedFileOp op(srcFileName, dstFileName);
|
|
|
|
|
bool ok = RetryWithBackoff(GetPointer(op), RETRY_ATTEMPTS, 150, RETRY_DELAY_CAP_MS);
|
2026-08-23 21:18:32 -04:00
|
|
|
if(!ok)
|
|
|
|
|
Print(__FUNCTION__ + ": WARNING - failed to copy " + srcFileName + " (shared folder) -> " + dstFileName +
|
|
|
|
|
" after " + IntegerToString(RETRY_ATTEMPTS) + " attempts (see the per-stage reason above)." +
|
|
|
|
|
" This run will train from scratch instead of reusing the deployed model.");
|
|
|
|
|
return ok;
|
|
|
|
|
}
|
|
|
|
|
#endif // WARRIOR_SHARED_FILE_COPY_MQH
|
|
|
|
|
//+------------------------------------------------------------------+
|