From 3fe57d687f5ed6dc10b2b9891c81ffee78583ea9 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Fri, 30 Oct 2026 00:38:59 +0800 Subject: [PATCH] fix(shell): retry clipboard writes within a short window refs #38 #85 ClipboardAccess takes explicit attempts and delay; reads keep five attempts, writes use three short ones (<=60ms) so micro-contention stops failing the copy while the owned negative fixture still produces the same OpenClipboard: 5 status. --- experiments/shell/clipboard.hpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/experiments/shell/clipboard.hpp b/experiments/shell/clipboard.hpp index 0e8205d3..574df8ba 100644 --- a/experiments/shell/clipboard.hpp +++ b/experiments/shell/clipboard.hpp @@ -14,27 +14,29 @@ class ClipboardError : public std::runtime_error { class ClipboardAccess { public: // A hosted desktop can hold the clipboard for a few milliseconds at a time - // (Explorer or a neighbouring test). Reads retry briefly so contention is - // not reported as a hard refusal; writes keep the immediate failure because - // the copy-failure contract asserts the first OpenClipboard result. - explicit ClipboardAccess(HWND owner, bool retry = false) { - constexpr int attempts = 5; + // (Explorer or a neighbouring test), which turns contention into a spurious + // hard refusal. Reads and writes both retry briefly on ERROR_ACCESS_DENIED. + // The copy-failure contract still holds: its negative fixture keeps the + // clipboard owned for the whole attempt window, so the failure and its + // "OpenClipboard: 5" status are unchanged, only reached a few ms later. + explicit ClipboardAccess(HWND owner, int attempts, DWORD first_delay_ms) { for (int attempt = 0; attempt < attempts; ++attempt) { if (OpenClipboard(owner)) return; const DWORD error = GetLastError(); - if (!retry || error != ERROR_ACCESS_DENIED || attempt + 1 == attempts) + if (error != ERROR_ACCESS_DENIED || attempt + 1 == attempts) throw ClipboardError("OpenClipboard", error); - Sleep(static_cast(20 * (attempt + 1))); + Sleep(first_delay_ms * static_cast(attempt + 1)); } throw ClipboardError("OpenClipboard", GetLastError()); } + explicit ClipboardAccess(HWND owner, bool retry = false) : ClipboardAccess(owner, retry ? 5 : 1, 20) {} ~ClipboardAccess() { CloseClipboard(); } ClipboardAccess(const ClipboardAccess&) = delete; }; inline std::u16string read_unicode_clipboard(HWND owner, size_t max_units = PastePolicy::hard_utf16_units) { static_assert(sizeof(wchar_t) == sizeof(char16_t), "Windows UTF-16 expected"); if (max_units > PastePolicy::hard_utf16_units) throw std::length_error("clipboard limit exceeds hard bound"); - ClipboardAccess clipboard(owner, true); + ClipboardAccess clipboard(owner, 5, 20); if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) throw ClipboardError("Unicode text unavailable", ERROR_NOT_FOUND); HANDLE memory = GetClipboardData(CF_UNICODETEXT); if (!memory) throw ClipboardError("GetClipboardData", GetLastError()); @@ -58,7 +60,9 @@ inline void write_unicode_clipboard(HWND owner, std::u16string_view text) { if (!text.empty()) std::memcpy(destination, text.data(), text.size() * sizeof(char16_t)); destination[text.size()] = 0; GlobalUnlock(memory); - ClipboardAccess clipboard(owner); + // Three short attempts (<=60 ms): enough for micro-contention, still far + // shorter than the owned hold the copy-failure contract relies on. + ClipboardAccess clipboard(owner, 3, 10); if (!EmptyClipboard()) throw ClipboardError("EmptyClipboard", GetLastError()); if (!SetClipboardData(CF_UNICODETEXT, memory)) throw ClipboardError("SetClipboardData", GetLastError()); allocation.value = nullptr; // The window station owns the allocation now.