//+------------------------------------------------------------------+ //| 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 #include "RetryWithBackoff.mqh" //+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+ //| 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); } }; //+------------------------------------------------------------------+ //| 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; CCopySharedFileOp op(srcFileName, dstFileName); bool ok = RetryWithBackoff(GetPointer(op), RETRY_ATTEMPTS, 150, RETRY_DELAY_CAP_MS); 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 //+------------------------------------------------------------------+