15

I use

DirectoryExists (const PathName : String);

to check if a directory is reachable from a computer or not. But if the directory does not exist and the path name is a network path, i.e.

\\computer1\Data

the method takes a very long time to return.

There must be a faster way to determine that a network folder is not reachable. Or can I configure some timeout parameter that DirectoryExists uses internally (I looked at the source code but it just delegates to GetFileAttributes which is defined in kernel32)?

Any ideas?

Uli Gerhardt
  • 13,748
  • 1
  • 45
  • 83
jpfollenius
  • 16,456
  • 10
  • 90
  • 156
  • Is the problem that the directory doesn't exist on the remote computer or that you can't connect to the remote computer in the first place? – Mason Wheeler Sep 17 '09 at 13:36
  • Could be both, but in most of the cases I can't connect. – jpfollenius Sep 17 '09 at 13:48
  • 1
    As a counter-example I'd like to pose my tiny NAS at home: When I haven't used it for a while the disks spin down. When I first access the mounted directory then a simple listing can take about 20 seconds. So during that 20 seconds there's no way to know if any given directory exists on there. Even after 15 seconds you can't be sure that it doesn't exist ... only when you get an actual (negative) answer from the host can you be sure that the file doesn't exist. – Joachim Sauer Nov 03 '09 at 15:21
  • any final solution with full source code sample working about it ? – Kiquenet May 17 '13 at 06:54
  • @Kiquenet: See my answer if you look for a "final solution with full source code sample" – Michael Hutter Feb 08 '19 at 05:38

8 Answers8

19

There is no faster way:

any function accessing anything on a remote share will timeout when that share is not available.

If the cause of your timeouts is automatic disconnecting of shares, then these link may help you:

If the application can continue without the completion of the check, then you can put the check in a separate thread, and upon completion of the check, you can update your status in the UI.

Be aware that when you try a multi-threading way, that you have to disprove your code is free of race-conditions and memory leaks. Time-outs in combination with exceptions usually make that a hard task.

Jeroen Wiert Pluimers
  • 23,965
  • 9
  • 74
  • 154
  • The link is not valid anymore :-( – Michael Hutter Feb 07 '19 at 10:53
  • This answer seems to be wrong. There is a faster way which I show in my answer below. – Michael Hutter Feb 07 '19 at 14:07
  • 2
    @Michael - Well, what you've done is to put the check in a thread as this answer tells. Why would implementing a timeout in the main thread prove this answer wrong? If anything, your code proves this answer is correct. – Sertac Akyuz Feb 07 '19 at 17:51
  • @SertacAkyuz: There are 3 aspects. 1.) To the question for "a faster way to determine that a network folder is not reachable" this answer says "There is no faster way" (which my answer disproves) / 2.) The link in this answer is not valid / 3.) The idea about the separate thread is correct but the answer doesn't give any source code to do this. That's why I blame this answer to be wrong. – Michael Hutter Feb 08 '19 at 05:53
  • 2
    @Michael 1) Your code only proves it is possible to give up on determining if a directory exists after a time period. It is unreliable, but that is your design. Timing may be more important to you than accuracy. 2) A dead link might render a link only answer wrong, but this is not the case here. FWIW link is perhaps [this](https://blogs.msdn.microsoft.com/openspecification/2013/03/19/cifs-and-smb-timeouts-in-windows/) or [this](https://blogs.msdn.microsoft.com/openspecification/2013/03/27/smb-2-x-and-smb-3-0-timeouts-in-windows/). 3) Source code for *"there is no faster way"*? Not required. – Sertac Akyuz Feb 08 '19 at 14:59
6

There was the same question for C#: How to avoid network stalls in GetFileAttributes?

As codymanix said, use threads. The above link will show you how you can do it with delegates in C#. Don't know Delphi, but maybe you know how to convert the code?

Community
  • 1
  • 1
JRL
  • 76,767
  • 18
  • 98
  • 146
  • 4
    (threads will only help if you can do something in paralel. If your next action is dependant on the share (e.g. for loading config), in the thread case you only gain the ability to show the user some movement, it doesn't speed up ) – Marco van de Voort Sep 18 '09 at 08:18
5

If you test for lots of directories you should use threads to do all the queries in parallel because for network shares ther are usually long timeouts.

codymanix
  • 28,510
  • 21
  • 92
  • 151
  • I'm not testing for a lots of directories. Only for one. But DirectoryExists can take about 30 seconds to return, which is annoying. – jpfollenius Sep 17 '09 at 13:49
3

This is the best way. You could add some code to ping the machine to insure it exists, but this would still leave the routine up to fail as many computers today have software firewalls set up to ignore ping requests, as well as the possibility that the share requested doesn't exist.

Also, on some machines if the UNC path is on the local machine and the local machine does not have an active network card (a wi-fi disconnected laptop for instance in "Airplane" mode) then UNC requests will also fail.

skamradt
  • 15,366
  • 2
  • 36
  • 53
3

I use the following code...

private delegate bool DirectoryExistsDelegate(string folder);

bool DirectoryExistsTimeout(string path, int millisecondsTimeout)
{
    try
    {
        DirectoryExistsDelegate callback = new DirectoryExistsDelegate(Directory.Exists);
        IAsyncResult result = callback.BeginInvoke(path, null, null);

        if (result.AsyncWaitHandle.WaitOne(millisecondsTimeout, false))
        {
            return callback.EndInvoke(result);
        }
        else
        {
            callback.EndInvoke(result);  // Needed to terminate thread?

            return false;
        }
    }

    catch (Exception)
    {
        return false;
    }
}

...which allows me to have a timeout version of Directory.Exist. I call it with something like...

bool a = DirectoryExistsTimeout("\\\\machine\\folder", 5000);

Would this be OK for you needs?


To be safe/legal then you need to call "callback.EndInvoke(result);" but calling it locks until the async finishes so this defeats the object of the code. Perhaps this needs to be done at the end of you code - exit maybe?

MKesper
  • 456
  • 5
  • 16
3

This function worked for me very well: NetDirectoryExists(Path, Timeout)
It uses Threading and is the perfect alternative for TDirectory.Exists(Path)

Usage:
if NetDirectoryExists('\\computer1\Data', 1000) then ...
if NetDirectoryExists('C:\Folder', 500) then ...

If the Folder exists, the function needs only some milliseconds, same with not existing folders (C:\NotExisting). If it is a not reachable network path (\\ServerNotReady\C$) then it will consume the number of milliseconds given by the second parameter.

Function NetDirectoryExists( const dirname: String; 
                              timeoutMSecs: Dword ): Boolean; 

implementation 


uses 
   Classes, Sysutils, Windows; 


type 
   ExceptionClass = Class Of Exception; 
   TTestResult = (trNoDirectory, trDirectoryExists, trTimeout ); 
   TNetDirThread = class(TThread) 
   private 
     FDirname: String; 
     FErr    : String; 
     FErrclass: ExceptionClass; 
     FResult : Boolean; 
   protected 
     procedure Execute; override; 
   public 
     Function TestForDir( const dirname: String; 
                timeoutMSecs: Dword ):TTestResult; 
   end; 


Function NetDirectoryExists( 
            const dirname: String; timeoutMSecs: Dword ): Boolean; 
 Var 
   res: TTestResult; 
   thread: TNetDirThread; 
 Begin 
   Assert( dirname <> '', 'NetDirectoryExists: dirname cannot be empty.' ); 
   Assert( timeoutMSecs > 0, 'NetDirectoryExists: timeout cannot be 0.' ); 
   thread:= TNetDirThread.Create( true ); 
   try 
     res:= thread.TestForDir( dirname, timeoutMSecs ); 
     Result := res = trDirectoryExists; 
     If res <> trTimeout Then 
       thread.Free; 
     {Note: if the thread timed out it will free itself when it finally 
      terminates on its own. } 
   except 
     thread.free; 
     raise 
   end; 
 End; 


procedure TNetDirThread.Execute; 
 begin 
   try 
     FResult := DirectoryExists( FDirname ); 
   except 
     On E: Exception Do Begin 
       FErr := E.Message; 
       FErrclass := ExceptionClass( E.Classtype ); 
     End; 
   end; 
 end; 


function TNetDirThread.TestForDir(const dirname: String; 
   timeoutMSecs: Dword): TTestResult; 
 begin 
   FDirname := dirname; 
   Resume; 
   If WaitForSingleObject( Handle, timeoutMSecs ) = WAIT_TIMEOUT 
   Then Begin 
     Result := trTimeout; 
     FreeOnTerminate := true; 
   End 
   Else Begin 
     If Assigned( FErrclass ) Then 
       raise FErrClass.Create( FErr ); 
     If FResult Then 
       Result := trDirectoryExists 
     Else 
       Result := trNoDirectory; 
   End; 
 end; 
Michael Hutter
  • 1,064
  • 13
  • 33
  • 4
    This might be a viable approach if you know and **disclose** the consequences. Your `NetDirectoryExists` will return more false negatives than the OS defaults. What if you set a timeout of 1000ms and the directory is reachable in [3 seconds](https://serverfault.com/a/817088)?. Or [6 seconds](https://superuser.com/q/903398)... Or [a minute](https://superuser.com/a/374841)... FWIW, I find it quite optimistic to make a few tests on a single environment and think that one can come up with a better timeout value than protocol and system developers with years of experience. – Sertac Akyuz Feb 08 '19 at 15:07
2

In a similar situation like you prescribed, I've added an ICMP ping to the server first. If the server doesn't respond to the ping, I assume it is down. You can decide which timeout to use on the ping yourself, so you can set it much shorter than the timeout used internally when trying to open a file-share.

Stijn Sanders
  • 35,982
  • 11
  • 45
  • 67
  • 1
    [Something like this](https://gist.github.com/stijnsanders/25c620ffa26d9335779c283a1ece2668), modern Windows has something special with IcmpSendEcho so it still works without elevated privileges. – Stijn Sanders Feb 08 '19 at 11:42
0

If both computers are on the same domain it will speed-up file operations when dealing with shares.

pani
  • 1,075
  • 6
  • 13