how to create a recoverable file uploader console app? - c#

I am uploading thousands of files to a server.
The server connection breaks allot, so I need a way for this console application to be able to recover if the connection fails for a few seconds etc.
My application is simple, it just gets all the files in the c:\uploads folder and then uses a web service to upload the files to the server.
so:
foreach(string file in files)
{
UploadToServer(file);
}
How can I make this so it re-covers in the event of a connection failure? (failures usually last just a few seconds)

Use a little helper method that retries the upload several times before throwing in the towel. For example:
static void UploadFile(string file) {
for (int attempt = 0; ; ++attempt) {
try {
UploadToServer(file);
return;
}
catch (SocketException ex) {
if (attempt < 10 && (
ex.SocketErrorCode == SocketError.ConnectionAborted ||
ex.SocketErrorCode == SocketError.ConnectionReset ||
ex.SocketErrorCode == SocketError.Disconnecting ||
ex.SocketErrorCode == SocketError.HostDown)) {
// Connection failed, retry
System.Threading.Thread.Sleep(1000);
}
else throw;
}
}
}
Tweak the exception handling code as needed.

If the files fail to upload, is there an exception that's thrown? If there is, then handle the exception, and either store those files in some kind of container for retrying later, or maybe you can put some kind of Thread.Sleep to wait a little and try again.

Related

Checking connection state with WinSCP .NET assembly in C#

I have a method for retrying the connection of WinSCP in C#.
How would I know if the state of my connection is opened or closed? Are there methods for this in WinSCP .Net?
using (Session session = new Session())
{
try
{
session.Open(sessionOptions);
}
catch(Exception ex)
{
//I want to reconnect here if my connection
//is timed out
//I want to reconnect here 3 times
}
// Upload file
session.PutFiles(#"C:\Test\files.dat", "var/test/files.dat");
// I want also to reconnect here if my upload failed
// reconnect to the server then upload the files that
// did not upload because of the connection errors
}
In your code, you already know, if the connection succeeded or not. Anyway, you can check Session.Opened, if you want to test explicitly for some reason.
using (Session session = new Session())
{
int attempts = 3;
do
{
try
{
session.Open(sessionOptions);
}
catch (Exception e)
{
Console.WriteLine("Failed to connect - {0}", e);
if (attempts == 0)
{
// give up
throw;
}
}
attempts--;
}
while (!session.Opened);
Console.WriteLine("Connected");
}
The file transfer operations, like the Session.PutFiles, reconnect automatically, if a connection is lost during transfer.
How long it will keep reconnecting is specified by Session.ReconnectTime.

Is it safe to use static methods on File class in C#?

I have following code in code-behind of an ASP.Net app, where a file is being read followed by writing to the file.
Code
var state= File.ReadAllText(Server.MapPath(string.Format("~/state/{0}", fileName)));
if(state.indexOf("1") == 0)
{
File.WriteAllText(Server.MapPath(string.Format("~/state/{0}", fileName)), newState);
}
Sometimes, but not always, I get the following exception.
Exception
The process cannot access the file 'C:\inetpub\wwwroot\mywebsite1\state\20150905005929435_edf9267e-fad1-45a7-bfe2-0e6e643798b5' because it is being used by another process.
I am guessing that the file read operation sometimes is not closing the file before the write operation happens, Or may be the file write operation is not closing the file before the next request from web application comes. But, I cannot find what exactly is the reason.
Question: How can I avoid this error from happening? Is it not safe to use the File class and instead use the traditional approach of FileStream object where I would always dispose the FileStream object explicitly?
UPDATE 1
I tried a retry loop approach, but even that didn't seem to solve the problem , since I was able to reproduce the same error if the ASP.Net page was submitted very quickly multiple times one after another. So I am back to finding a fool-proof solution in my case.
string state = null;
int i = 0;
while (i < 20) {
try {
state = File.ReadAllText(Server.MapPath(string.Format("~/state/{0}", fileName)));
} catch (Exception ex2) {
//log exception
Elmah.ErrorSignal.FromCurrentContext().Raise(ex2);
//if even retry doesn't work then throw an exception
if (i == 19) {
throw;
}
//sleep for a few milliseconds
System.Threading.Thread.Sleep(10);
}
i++;
}
i = 0;
while (i < 20) {
try {
File.WriteAllText(Server.MapPath(string.Format("~/state/{0}", fileName)), newState);
} catch (Exception ex2) {
//log exception
Elmah.ErrorSignal.FromCurrentContext().Raise(ex2);
//if even retry doesn't work then throw an exception
if (i == 19) {
throw;
}
//sleep for a few milliseconds
System.Threading.Thread.Sleep(10);
}
i++;
}
UPDATE 2
The only fool proof solution that seemed to work is by using a File Sequencing approach, as suggested by usr. This involves writing to a different file and not to the same file that was just read. The name of file being written to is the name of file that was just read appended by a sequence number.
string fileName = hiddenField1.Value;
string state = null;
int i = 0;
while (i < 20) {
try {
state = File.ReadAllText(Server.MapPath(string.Format("~/state/{0}", fileName)));
} catch (Exception ex2) {
//log exception
Elmah.ErrorSignal.FromCurrentContext().Raise(ex2);
//if even retry doesn't work then throw an exception
if (i == 19) {
throw;
}
//sleep for a few milliseconds
System.Threading.Thread.Sleep(10);
}
i++;
}
i = 0;
while (i < 20) {
try {
//***************FILE SEQUENCING**************************
//Change the file to which state is written, so no concurrency errors happen
//between reading from and writing to same file. This is a fool-proof solution.
//Since max value of integer is more than 2 billion i.e. 2,147,483,647
//so we can be sure that our sequence will never run out of limits because an ASP.Net page
//is not going to postback 2 billion times
if (fileName.LastIndexOf("-seq_") >= 0) {
fileName = fileName.Substring(0, fileName.LastIndexOf("-seq_") + 4 + 1) + (int.Parse(fileName.Substring(fileName.LastIndexOf("-seq_") + 4 + 1)) + 1);
} else {
fileName = fileName + "-seq_1";
}
//change the file name so in next read operation the new file is read
hiddenField1.Value = fileName;
File.WriteAllText(Server.MapPath(string.Format("~/state/{0}", fileName)), newState);
} catch (Exception ex2) {
//log exception
Elmah.ErrorSignal.FromCurrentContext().Raise(ex2);
//if even retry doesn't work then throw an exception
if (i == 19) {
throw;
}
//sleep for a few milliseconds
System.Threading.Thread.Sleep(10);
}
i++;
}
The only downside to above approach is that many files would get created as end users post back to the same ASP.Net page. So, it would be good to have a background job that deleted stale files so number of files would be minimized.
File Names with sequencing
UPDATE 3
Another fool proof solution is to alternate between read and write file names. This way we do not end up creating many files and only use 2 files as the end user posts back to the same page many times. The code is same as in code under UPDATE 2 except the code after FILE SEQUENCING comment should be replaced by code below.
if (fileName.LastIndexOf("-seq_1") >= 0) {
fileName = fileName.Substring(0, fileName.LastIndexOf("-seq_1"));
} else {
fileName = fileName + "-seq_1";
}
File Names with Alternating approach
I am guessing that the file read operation sometimes is not closing the file before the write operation happens, Or may be the file write operation is not closing the file before the next request from web application comes.
Correct. File systems do not support atomic updates well. (Especially not on Windows; many quirks.)
Using FileStream does not help. You would just rewrite the same code that the File class has. File has no magic inside. It just uses FileStream wrapped for your convenience.
Try keeping files immutable. When you want to write a new contents write a new file. Append a sequence number to the file name (e.g. ToString("D9")). When reading pick the file with the highest sequence number.
Or, just add a retry loop with a small delay.
Or, use a better data store such as a database. File systems are really nasty. This is an easy problem to solve with SQL Server for example.
I am guessing that the file read operation sometimes is not closing the file before the write operation happens
Although according to the documentation the file handle is guaranteed to be closed by this method, even if exceptions are raised, the timing of the closing is not guaranteed to happen before the method returns: the closing could be done asynchronously.
One way to fix this problem is to write the results into a temporary file in the same directory, and then move the new file in place of the old one.

Why does my download queue break when called faster after each other?

I am using the following script to download XML files from a external site, but when the function is called fast after each other (Fast switching of tables to show) the queue seems to slip up.
When the function is called in a normal manner it works just fine, but when the user starts to switch between tables at a faster pace, the data won't load. It does not give any exceptions besides on some rare occasions it will say that the queue is busy. All tough I can't seem to find what is causing this queue to slip.
public void PreObtainData(ref MonavisaRequestForm request, string dateAndTime, string fileDateAndTime)
{
if (!initialized)
initialize();
try
{
if (!request.webclient.IsBusy && requestQueue.Count == 0)
{
request.url = request.url.Replace("&", "%26");
request.url = request.url.Replace("+", "%2B");
Uri uri = new Uri(string.Format("http://localhost/login.php?username={0}&password={1}&request={2}", request.username, request.password, request.url));
request.webclient.DownloadFile(uri, #"Nioo Graph Data " + fileDateAndTime + ".xml");
}
else if (!request.webclient.IsBusy && requestQueue.Count > 0)
{
Uri uri = new Uri(string.Format("http://localhost/login.php?username={0}&password={1}&request={2}", requestQueue.Peek().username, requestQueue.Peek().password, requestQueue.Peek().url));
requestQueue.Peek().webclient.DownloadStringAsync(uri);
requestQueue.Dequeue();
}
else
{
requestQueue.Enqueue(request);
}
}
catch (System.Net.WebException ex)
{
//if (ex.Status != System.Net.WebExceptionStatus.ProtocolError)
{
throw ex;
}
}
}
Queues are not designed to be accessed from multiple threads, and any number of things can go wrong when you do so. You should use a ConcurrentQueue or a BlockingCollection (which uses a ConcurrentQueue), as it is specifically designed to be used from multiple threads.

How to delete a shared folder using c# code if the directory is opened on another machine?

I'm trying to delete an empty shared directory which is opened in another machine.
If i directly delete the directory(right click and delete) it is removed.
Stopwatch st = new Stopwatch();
st.Start();
while(true){
try
{
Directory.Delete(pathToDelete, true);
Console.WriteLine("Directory Deleted" + "Elapsed time:" + st.Elapsed.Seconds.ToString() + "sec");
break;
}
catch (Exception e)
{
if ((e is System.IO.IOException) || (e is System.UnauthorizedAccessException) ||
(e is System.Reflection.TargetInvocationException))
{
Console.WriteLine(e.ToString());
if (st.Elapsed > TimeSpan.FromMinutes(5))
{
Console.WriteLine("Can not delete directory ");
return;
}
Thread.Sleep(1000);
}
else
{
throw;
}
}
}
It is not deleting the directory if the directory opened on same machine or different machine using the shared-path.
I found this
but i couldn't understand the code properly.
Anyone suggest a better method?
Thanks in Advance
System.IO.IOException: The process cannot access the file 'c:\dir' because it is being used by another process
That means that its in use, therefore your not going to be able to remove it. If its empty like you say, then it must be either a) in use by someone on the Network Share or there's something running on the server that its ran from that's monitoring it. You may need to stop a service.

Keep trying to talk to server when the Internet is down

So my application is exchanging request/responses with a server (no problems), until the internet connection dies for a couple of seconds, then comes back. Then a code like this:
response = (HttpWebResponse)request.GetResponse();
will throw an exception, with a status like ReceiveFailure, ConnectFailure, KeepAliveFailure etc.
Now, it's quite important that if the internet connection comes back, I am able to continue communicating with the server, otherwise I'd have to start again from the beginning and that will take a long time.
How would you go about resuming this communication when the internet is back?
At the moment, I keep on checking for a possibility to communicate with the server, until it is possible (at least theoretically). My code attempt looks like this:
try
{
response = (HttpWebResponse)request.GetResponse();
}
catch (WebException ex)
{
// We have a problem receiving stuff from the server.
// We'll keep on trying for a while
if (ex.Status == WebExceptionStatus.ReceiveFailure ||
ex.Status == WebExceptionStatus.ConnectFailure ||
ex.Status == WebExceptionStatus.KeepAliveFailure)
{
bool stillNoInternet = true;
// keep trying to talk to the server
while (stillNoInternet)
{
try
{
response = (HttpWebResponse)request.GetResponse();
stillNoInternet = false;
}
catch
{
stillNoInternet = true;
}
}
}
}
However, the problem is that the second try-catch statement keeps throwing an exception even when the internet is back.
What am I doing wrong? Is there another way to go about fixing this?
Thanks!
You should recreate the request each time, and you should execute the retries in a loop with a wait between each retry. The wait time should progressively increase with each failure.
E.g.
ExecuteWithRetry (delegate {
// retry the whole connection attempt each time
HttpWebRequest request = ...;
response = request.GetResponse();
...
});
private void ExecuteWithRetry (Action action) {
// Use a maximum count, we don't want to loop forever
// Alternativly, you could use a time based limit (eg, try for up to 30 minutes)
const int maxRetries = 5;
bool done = false;
int attempts = 0;
while (!done) {
attempts++;
try {
action ();
done = true;
} catch (WebException ex) {
if (!IsRetryable (ex)) {
throw;
}
if (attempts >= maxRetries) {
throw;
}
// Back-off and retry a bit later, don't just repeatedly hammer the connection
Thread.Sleep (SleepTime (attempts));
}
}
}
private int SleepTime (int retryCount) {
// I just made these times up, chose correct values depending on your needs.
// Progressivly increase the wait time as the number of attempts increase.
switch (retryCount) {
case 0: return 0;
case 1: return 1000;
case 2: return 5000;
case 3: return 10000;
default: return 30000;
}
}
private bool IsRetryable (WebException ex) {
return
ex.Status == WebExceptionStatus.ReceiveFailure ||
ex.Status == WebExceptionStatus.ConnectFailure ||
ex.Status == WebExceptionStatus.KeepAliveFailure;
}
I think what you are trying to do is this:
HttpWebResponse RetryGetResponse(HttpWebRequest request)
{
while (true)
{
try
{
return (HttpWebResponse)request.GetResponse();
}
catch (WebException ex)
{
if (ex.Status != WebExceptionStatus.ReceiveFailure &&
ex.Status != WebExceptionStatus.ConnectFailure &&
ex.Status != WebExceptionStatus.KeepAliveFailure)
{
throw;
}
}
}
}
When you want to retry something on failure then instead of thinking of this as something that you want to do when something fails, think of it instead as looping until you succeed. (or a failure that you don't want to retry on). The above will keep on retrying until either a response is returned or a different exception is thrown.
It would also be a good idea to introduce a maximum retry limit of some sort (for example stop retrying after 1 hour).
If it's still doing it when you get the connection back - then my guess is that it's simply returning the same result again.
You might want to to try recreating the request anew each time, other than that I don't see much wrong with the code or logic. Apart from the fact that you're forever blocking this thread. But then that might be okay if this is, in itself, a worker thread.

Categories