FIX: sync — throttle AppProcessMessages to prevent O(n²) slowdown

AppProcessMessages was called between every single file in copy/delete
loops. On Linux, each call processes all pending inotify events; if the
DC file panels are watching the source/destination directories, each
inotify event triggers a full panel reload (O(n) work per file copied).
With ~8000 files this becomes O(n²): ~5 minutes for a trivial local copy
of small files, while Windows finished in seconds.

Fix: add a 50 ms gate in TFileSourceOperation.AppProcessMessages via
GetTickCount64. UI stays responsive (stop button still works via the
separate CheckOperationState flag path) but the GTK event queue is pumped
at most every 50 ms regardless of file count.
This commit is contained in:
heredie 2026-05-25 23:00:23 -06:00
commit d47e1b33ce

View file

@ -180,6 +180,13 @@ type
FUIResponse: TFileSourceOperationUIAnswer;
FTryAskQuestionResult: Boolean;
{en
Timestamp of last AppProcessMessages call (ms). Used to throttle
message-pumping when running on the main thread, to avoid O()
overhead from inotify-triggered panel reloads with large file sets.
}
FLastProcessMessagesTime: QWord;
{en
Used to determine whether the operation has started or not.
}
@ -805,10 +812,21 @@ begin
end;
function TFileSourceOperation.AppProcessMessages(CheckState: Boolean): Boolean;
const
// Minimum ms between GTK event-queue pumps. Prevents O(n²) panel-reload
// overhead from inotify events when copying/deleting large file sets.
ThrottleIntervalMs = 100;
var
Ticks: QWord;
begin
if GetCurrentThreadId = MainThreadID then
begin
WidgetSet.AppProcessMessages;
Ticks := GetTickCount64;
if Ticks - FLastProcessMessagesTime >= ThrottleIntervalMs then
begin
FLastProcessMessagesTime := Ticks;
WidgetSet.AppProcessMessages;
end;
end;
if CheckState then
try