I'm making a Windows service that watches a directory for new files to process them.
I hit a race condition between the file copying going on and me trying to process it. Going by a SO answer I wrote the following wait-and-retry method to make sure I can work with the file:
public static void WaitForFile(string file, CancellationToken token, int retryIntervalMillis = 200)
{
while (!token.IsCancellationRequested)
{
try
{
using (new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))
return;
}
catch
{
Thread.Sleep(retryIntervalMillis);
}
}
throw new OperationCanceledException(token);
}
My question is: would this be a correct way to make this waiting cancellable? The token is cancelled to shut down the service, the idea is to allow for a clean (-ish) shutdown even in a case a bug means the file in question is inadvertently kept open indefinitely.
(Or if the reason the file can't be opened is some other, non-intermittent condition I haven't guarded against yet.)
For instance, is it okay to use the token this way in my method, and also expect the same token to cancel a BlockingCollection
's consuming enumerator?
My intent is to base the service around the expectation it can be hard-crashed at any time, so I'm fine with an exception taking out most of the call stack leading to it.
Edit:
These are changes that I have since made to the code
const int ErrorSharingViolation = -2147024864;
public static FileStream WaitForFile(string file,
CancellationToken token,
FileMode mode = FileMode.Open,
FileAccess access = FileAccess.Read,
Action<Stream> action = null,
int retryIntervalMillis = 200)
{
while (!token.IsCancellationRequested)
{
try
{
var stream = File.Open(file, mode, access, FileShare.None);
if (action != null)
{
using (stream)
{
action(stream);
}
}
return stream;
}
catch (IOException exception)
{
if (exception.HResult != ErrorSharingViolation)
{
throw;
}
else
{
Thread.Sleep(retryIntervalMillis);
}
}
}
throw new OperationCanceledException(token);
}
1 Answer 1
Your catch
statement is catching any error (EVERY ERROR), which is not a good thing. I assume there are several errors that you are anticipating, but you shouldn't catch them all. You will run into bugs and won't know what is going on because the errors will be bulked into your cancellation.
I would catch only the exceptions that you know about and are ready for, meaning that you have a solution for that exception.
You also might want to set it so that right before WaitForFile
is exited, it closes the file no matter what happens inside the method.
-
\$\begingroup\$ I've changed that part in my code since yesterday, I'll edit that into my sample. It's a valid observation about the code as posted, but really the crux of my question was, specifically, implementing cancellation correctly. \$\endgroup\$millimoose– millimoose2014年01月08日 18:17:34 +00:00Commented Jan 8, 2014 at 18:17
-
2\$\begingroup\$ The
using
statement should take care of closing (by callingDispose()
) the file. As far as I can tell there's no code path that does not do so. \$\endgroup\$millimoose– millimoose2014年01月08日 18:18:28 +00:00Commented Jan 8, 2014 at 18:18 -
\$\begingroup\$ I'm sorry you feel I was moving the goalposts with my edit, but I believe even as originally phrased it was clear what I'm asking about is how I implement cancellation, not the wait-and-retry mechanism itself. \$\endgroup\$millimoose– millimoose2014年01月08日 23:47:25 +00:00Commented Jan 8, 2014 at 23:47
-
\$\begingroup\$ on Code Review we Review what code you have. catching every error is bad practice, unless you are logging it. the new code looks better from what I have seen \$\endgroup\$Malachi– Malachi2014年01月09日 00:28:20 +00:00Commented Jan 9, 2014 at 0:28
-
2\$\begingroup\$ Ah, I think I get it now. You weren't accusing me of moving the goalposts, but of messing up the context of your answer and making it seem like it's completely unrelated. Sorry for being argumentative there, I read the situation wrong. \$\endgroup\$millimoose– millimoose2014年01月09日 23:21:58 +00:00Commented Jan 9, 2014 at 23:21
using
is there – the file is opened, then immediately closed again so the caller can open it. I've since changed it to actually invoke a callback instead to avoid the (in my use case unlikely) race condition where another process may open the file betweenWaitForFile()
closing and the caller opening it. \$\endgroup\$