mirror of
https://github.com/doublecmd/doublecmd.git
synced 2026-06-21 09:58:13 +00:00
FIX: sync - suspend panel watchers during copy/delete (avoid O(n²) reload)
A folder-sync copy/delete on a large LOCAL directory crawled at ~1 file/sec. The cost is not the file operation (raw unlink runs hundreds-thousands/sec) but the source panel's directory watcher: each changed file fires a watcher event, and once >100 (or >25% of the listing) pending changes accumulate, TFileView reloads the WHOLE directory (ufileview.pas), rate-limited to once per second. During a bulk operation this full reload keeps re-firing, so the whole thing becomes O(n²) and stalls — the sync runs the operation inline on the GUI thread, so each reload directly blocks it. Add TFileView.SetWatcherEnabled (public wrapper over the existing protected EnableWatcher, plus one reconciling reload on resume) and have the sync dialog suspend both source panels' watchers around the copy/delete run and the compare-grid delete buttons, restoring them via try/finally. Operations now proceed at filesystem speed; the panels refresh once at the end. Cross-platform: the reload path is platform-agnostic and is fed by inotify (Linux), ReadDirectoryChangesW (Windows) and FSEvents (macOS) alike. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d843b84a80
commit
9a172ea610
2 changed files with 51 additions and 2 deletions
|
|
@ -443,6 +443,13 @@ type
|
|||
function Reload(const PathToReload: String): Boolean; overload;
|
||||
procedure Reload(AForced: Boolean);
|
||||
procedure ReloadIfNeeded;
|
||||
{en
|
||||
Suspend/resume the directory watcher around an app-initiated bulk
|
||||
operation. While suspended, inotify changes are not turned into
|
||||
repeated full reloads (which is O(n²) for large folders); resuming
|
||||
performs a single reconciling reload. No-op for non-filesystem views.
|
||||
}
|
||||
procedure SetWatcherEnabled(AEnabled: Boolean);
|
||||
procedure StopWorkers; virtual;
|
||||
|
||||
// For now we use here the knowledge that there are tabs.
|
||||
|
|
@ -3390,6 +3397,22 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure TFileView.SetWatcherEnabled(AEnabled: Boolean);
|
||||
begin
|
||||
if AEnabled then
|
||||
begin
|
||||
// Only the filesystem watcher causes the per-file reload storm, so only
|
||||
// it was suspended; re-enable and do one reload to catch up on changes.
|
||||
if Assigned(FileSource) and FileSource.IsClass(TFileSystemFileSource) then
|
||||
begin
|
||||
EnableWatcher(True);
|
||||
Reload(True);
|
||||
end;
|
||||
end
|
||||
else if WatcherActive then
|
||||
EnableWatcher(False);
|
||||
end;
|
||||
|
||||
procedure TFileView.SetFlatView(AFlatView: Boolean);
|
||||
begin
|
||||
FFlatView:= AFlatView;
|
||||
|
|
|
|||
|
|
@ -166,6 +166,9 @@ type
|
|||
FCmpFileSourceL, FCmpFileSourceR: IFileSource;
|
||||
FCmpFilePathL, FCmpFilePathR: string;
|
||||
FAddressL, FAddressR: string;
|
||||
// The two source panels, kept so their directory watchers can be
|
||||
// suspended during bulk copy/delete (avoids the per-file reload storm).
|
||||
FFileView1, FFileView2: TFileView;
|
||||
hCols: array [0..6] of record Left, Width: Integer end;
|
||||
CheckContentThread: TObject;
|
||||
Ftotal, Fequal, Fnoneq, FuniqueL, FuniqueR: Integer;
|
||||
|
|
@ -189,6 +192,7 @@ type
|
|||
procedure SetSyncRecState(AState: TSyncRecState);
|
||||
procedure DeleteFiles(ALeft, ARight: Boolean);
|
||||
function DeleteFiles(FileSource: IFileSource; var Files: TFiles): Boolean;
|
||||
procedure SetPanelWatchers(AEnabled: Boolean);
|
||||
procedure UpdateList(ALeft, ARight: TFiles; ARemoveLeft, ARemoveRight: Boolean);
|
||||
procedure SetProgressBytes(AProgressBar: TKASProgressBar; CurrentBytes: Int64; TotalBytes: Int64);
|
||||
procedure SetProgressFiles(AProgressBar: TKASProgressBar; CurrentFiles: Int64; TotalFiles: Int64);
|
||||
|
|
@ -783,6 +787,8 @@ begin
|
|||
pnlCopyProgress.Visible:= CopyLeft or CopyRight;
|
||||
pnlDeleteProgress.Visible:= DeleteLeft or DeleteRight;
|
||||
|
||||
SetPanelWatchers(False);
|
||||
try
|
||||
i := 0;
|
||||
while i < FVisibleItems.Count do
|
||||
begin
|
||||
|
|
@ -834,6 +840,9 @@ begin
|
|||
else DeleteRightFiles.Free;
|
||||
if not pnlProgress.Visible then Break;
|
||||
end;
|
||||
finally
|
||||
SetPanelWatchers(True);
|
||||
end;
|
||||
EnableControls(True);
|
||||
btnCompare.Click;
|
||||
end;
|
||||
|
|
@ -1836,6 +1845,16 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure TfrmSyncDirsDlg.SetPanelWatchers(AEnabled: Boolean);
|
||||
begin
|
||||
// Suspend the source panels' directory watchers while we copy/delete, so a
|
||||
// large local operation does not trigger one full O(n) panel reload per ~100
|
||||
// changes (which makes a big delete behave as O(n²), ~1 file/sec). Resuming
|
||||
// does a single reconciling reload of each panel.
|
||||
if Assigned(FFileView1) then FFileView1.SetWatcherEnabled(AEnabled);
|
||||
if Assigned(FFileView2) then FFileView2.SetWatcherEnabled(AEnabled);
|
||||
end;
|
||||
|
||||
procedure TfrmSyncDirsDlg.DeleteFiles(ALeft, ARight: Boolean);
|
||||
var
|
||||
Message: String;
|
||||
|
|
@ -1883,8 +1902,13 @@ begin
|
|||
EnableControls(False);
|
||||
pnlCopyProgress.Visible:= False;
|
||||
pnlDeleteProgress.Visible:= True;
|
||||
if ALeft then DeleteFiles(FCmpFileSourceL, ALeftList);
|
||||
if ARight then DeleteFiles(FCmpFileSourceR, ARightList);
|
||||
SetPanelWatchers(False);
|
||||
try
|
||||
if ALeft then DeleteFiles(FCmpFileSourceL, ALeftList);
|
||||
if ARight then DeleteFiles(FCmpFileSourceR, ARightList);
|
||||
finally
|
||||
SetPanelWatchers(True);
|
||||
end;
|
||||
UpdateList(nil, nil, ALeft, ARight);
|
||||
EnableControls(True);
|
||||
end;
|
||||
|
|
@ -2049,6 +2073,8 @@ begin
|
|||
FFoundItems := TStringListEx.Create;
|
||||
FFoundItems.CaseSensitive := FileNameCaseSensitive;
|
||||
FFoundItems.Sorted := True;
|
||||
FFileView1 := FileView1;
|
||||
FFileView2 := FileView2;
|
||||
FFileSourceL := FileView1.FileSource;
|
||||
FFileSourceR := FileView2.FileSource;
|
||||
FAddressL := FileView1.CurrentAddress;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue