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
|
| | | //+------------------------------------------------------------------+
|