Forum > Windows

[SOLVED] How to cleanup ReadDirectoryChangesW operations properly?

(1/4) > >>

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

Go to full version