Forum > Windows
[SOLVED] How to cleanup ReadDirectoryChangesW operations properly?
d7_2_laz:
At the end of my migration of an old Delphi 7 app to Lazarus Windows x64 i detected a remaining flaw with ReadDirectoryChangesW (in combination with GetOverlappedResult).
The scenario is that i have a old grown dirwatcher component for a file listview's folder elements.
The folder is same as the related folder in a directory tree.
The component, somehow adapted to Lazarus, works fine in nearly all use cases, except one.
The problamatic case is when i rename a folder in the tree (with DO_RENAME in SHFileOperationW) and _afterwards_ the parent folder of that already renamed child folder,
then the file operationi failds with error ui "already in use".
Before those rename actions the filewatcher thread is terminated and a CancelIO done, somehting like "CancelWait" in related articles.
The problem appears to me to be that the ReadDirectoryChangesW resp. GetOverlappedResult is not cleaned up properly resp. is sticky on a folder once having renamed it so that i cannot continue on a parent folder. of it
Tried a lot of modification on the stop thread / stop ReadDirectoryChangesW, but keep to be stuck.
Yes, it's hard to say something about that without code, but it would be very hard to condense the complex thing into a little test case.
Maybe a theoretical thought might help, convering "how to finish the IOs of a ReadDirectoryChangesW properly".
Somebody encountered such?
ASerge:
Are you using CancelIO or CancelIoEx? Do you close the directory handle when the thread terminates?
d7_2_laz:
CancelIO (if handle <> 0 and <> INVALID_HANDLE_VALUE), then CloseHandle
It's worthy to try CancelOEx for this special case?
ASerge:
--- Quote from: d7_2_laz on May 20, 2021, 08:54:50 pm ---It's worthy to try CancelOEx for this special case?
--- End quote ---
From docs: "The CancelIoEx function allows you to cancel requests in threads other than the calling thread." You didn't say where the cancellation was.
d7_2_laz:
Yes really, that (in threads other than the calling thread) sounded promising, but i didn't notice a difference.
I tried to test the cancellation from various places. For instance from inside the control thread execute of later after having called the thread terminate.
Maybe it is better for speaking to print an extract from the coding here. The control thread execute:
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure CtrlThread.Execute;var pBuffer : Pointer; // Buffer for return values of ReadDirectoryChangesW dwBufLen : DWORD; dwRead : DWORD; PInfo : PFILE_NOTIFY_INFORMATION; dwNextOfs : DWORD; dwFnLen : DWORD; Overlap : TOverlapped; WaitResult: DWORD; EventArray: Array[0..2] of THandle; // Array der Handles für WaitForMultipleObjects pFileNameForMessage: PWideChar; lasterrcode:Cardinal; ErrorMessage: string; begin try // try .. except Cancel_IO; if not DirectoryExists(FsDirPath) then begin Terminate; exit; end; FhFile:= CreateFileW(PWideChar(UTF8Decode(FsDirPath)), FILE_LIST_DIRECTORY or GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil, OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED, 0); if (FhFile = INVALID_HANDLE_VALUE) or (FhFile = 0) then begin Terminate; exit; end; FileEvent:=CreateEvent(nil,FALSE,FALSE,nil); Overlap.hEvent:=FileEvent; TermEvent:=TEvent.Create(nil,FALSE,FALSE,TermEvName); SuspEvent:=TEvent.Create(nil,FALSE,FALSE,SuspEvName); EventArray[0]:=FileEvent; EventArray[1]:=TermEvent.Handle; EventArray[2]:=SuspEvent.Handle;} EventArray[0] := FileEvent; EventArray[1] := plocaleventrec(TermEvent.Handle)^.Fhandle; EventArray[2] := plocaleventrec(SuspEvent.Handle)^.Fhandle; dwBufLen := 65535; // Dirwatch: BufferSize (=32) * SizeOf(TFILE_NOTIFY_INFORMATION); pBuffer := AllocMem(dwBufLen); try while not terminated do begin dwRead := 0; if ReadDirectoryChangesW(FhFile, pBuffer, dwBufLen, prWatchSubTree, // true or false didn't make a difference here FFilter, //FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME, @dwRead, @Overlap, NIL) then begin WaitResult := WaitForMultipleObjects(3, @EventArray, FALSE, infinite); case WaitResult of WaitDir: begin // WAIT_OBJECT_0 if GetOverlappedResult(FhFile, Overlap, dwRead, False) then begin PInfo:= pBuffer; if dwRead = 0 then begin // check overflow ErrorMessage := SysErrorMessage(ERROR_NOTIFY_ENUM_DIR); SignalError(ErrorMessage, ERROR_NOTIFY_ENUM_DIR); end; repeat dwNextOfs := PInfo.dwNextEntryOffset; FAction := PInfo.dwAction; dwFnLen := PInfo.dwFileNameLength; FsFileName := WideCharLenToString(@PInfo.dwFileName,dwFnLen div 2); GetMem(pFileNameForMessage, dwFnLen + SizeOf(WideChar)); Move(PInfo.dwFileName, Pointer(pFileNameForMessage)^, dwFnLen); PWord(Cardinal(pFileNameForMessage) + dwFnLen)^ := 0; PostMessage(FWndHandle, WM_DIRWATCH_NOTIFY, FAction, LParam(pFileNameForMessage)); pChar(PInfo) := pChar(PInfo)+dwNextOfs; until dwNextOfs=0; end else begin lasterrcode := GetLastError; ErrorMessage := SysErrorMessage(lasterrcode); SignalError(ErrorMessage, lasterrcode); break; end; // GetOverlappedResult end; WaitTerm: // WAIT_OBJECT_0+1 // dont call Terminate again here ; WaitSusp: Suspend; // WAIT_OBJECT_0+2 else break; end; // case WaitResult ReadDirectoryChangesW end else begin ErrorMessage := SysErrorMessage(GetLastError); SignalError(ErrorMessage); end; end; finally FreeMem(pBuffer,dwBufLen); if FhFile <> 0 then if FhFile <> INVALID_HANDLE_VALUE then begin CancelIOEx(FhFile, 0); // or nil, no change CloseHandle(FhFile); FhFile := 0; end; SendMessage(FWndHandle, WM_DIRWATCH_THREADTERMINATED, 0, 0); with TFldrControl(Owner) do ThrdTerminated := True; end; // try except on E :Exception do begin ErrorMessage := E.Message; SignalError(ErrorMessage); end; end; end;
Navigation
[0] Message Index
[#] Next page