I'm having an odd issue using FTP for .NET 3.3.7467.13330 on a production server running Windows Server 2003 R2.
Sometimes (apparently randomly) the FTP connection seems to just freeze. I can see from the trace writer that a command is sent to the server, but no response is being written, and the method neither returns nor throws an exception (I'd expect a timeout to occur). The connection is over SSL and I've tried setting keep-alive or removing it - this makes no difference. The job is run every two minutes and there would normally only be one or two files to retrieve. The "hang" can occur on calling AbstractFolder.GetFiles, AbstractFile.Exists, AbstractFile.CopyTo or AbstractFile.Delete.
This job is running inside an ASP.NET application, but on a background thread with some locking logic in place to ensure that only one instance can be running at any one time. Because there is no exception thrown when this problem arises the process never ends, so my only solution is to recycle the server's app pools, which is very annoying!
Can anyone help on why this might be happening and how to fix it? If an exception would fire on timeout it would solve all this at a stroke!
Edited version of my code is below. I have a general Transfer method that's designed to accept either an FTP or local directory as source or destination. I've stripped out all the logging calls and other unnecessary stuff to give an idea of what I'm doing. I've not included the Location class - it just defines either an FTP location or a local disk location.
public IList<string> Transfer(
string sourceFolder,
string sourceFilePattern,
string destinationFolder,
bool deleteOriginal,
bool overwriteExisting,
int maxTransfers,
bool throwExceptions)
{
//... null checks etc ...
IList<string> result = new List<string>();
Location source = new Location(sourceFolder);
Location destination = new Location(destinationFolder);
// Check for source and destination both being FTP - we don't want this!
if (source.Type == LocationType.Ftp && destination.Type == LocationType.Ftp)
{
throw new InvalidOperationException("Source and destination cannot both be FTP");
}
// Check for an FTP requirement
FtpConnection connection = null;
if (source.Type == LocationType.Ftp)
{
connection = this.GetConnection(source);
}
else if (destination.Type == LocationType.Ftp)
{
connection = this.GetConnection(destination);
}
// Wrap the connection in a wrapper to correctly dispose of logging
ConnectionWrapper connectionWrapper = new ConnectionWrapper(connection);
using (connectionWrapper)
{
// FTP connection handling
if (connectionWrapper.Connection != null)
{
// Delegate the CertificateReceived event
connectionWrapper.Connection.CertificateReceived += this.OnCertificateReceived;
// Ping the connection
connectionWrapper.Connection.TestConnection();
}
// Get the source abstract folder and check it exists
AbstractFolder sourceAbstract = this.GetAbstractFolder(source, ref connectionWrapper);
if (!sourceAbstract.Exists)
{
throw new ApplicationException("Folder " + sourceAbstract.HostedFullName + " does not appear to exist");
}
// Get the destination abstract folder and check it exists
AbstractFolder destinationAbstract = this.GetAbstractFolder(destination, ref connectionWrapper);
if (!sourceAbstract.Exists)
{
throw new ApplicationException("Folder " + destinationAbstract.HostedFullName + " does not appear to exist");
}
// Get the list of files to transfer
AbstractFile[] sourceFiles = sourceAbstract.GetFiles(false, sourceFilePattern);
// Count transfers
int count = 0;
// Perform the transfer
foreach (AbstractFile sourceFile in sourceFiles)
{
try
{
// Need to check that the source file exists
if (sourceFile.Exists)
{
AbstractFile transferred =
(AbstractFile)sourceFile.CopyTo(destinationAbstract, overwriteExisting);
// Verify the modified date if destination is local
if (destination.Type == LocationType.Local
&& transferred.LastWriteDateTime != sourceFile.LastWriteDateTime)
{
throw new ApplicationException(
"Donwloaded file " + transferred.FullName + " has modified date "
+ transferred.LastWriteDateTime + " which does not match"
+ sourceFile.HostedFullName + " which has a modified date of "
+ sourceFile.LastWriteDateTime);
}
// Verify the size
if (transferred.Size != sourceFile.Size)
{
throw new ApplicationException(
"Downloaded file " + transferred.FullName + " has size " + transferred.Size
+ " which does not match" + sourceFile.HostedFullName + " which has a size of "
+ sourceFile.Size);
}
if (deleteOriginal)
{
sourceFile.Delete();
}
result.Add(transferred.HostedFullName);
}
}
catch (Exception ex)
{
if (throwExceptions)
{
throw ex;
}
}
count++;
if (maxTransfers > 0 && count >= maxTransfers)
{
break;
}
}
}
return result;
}
/// <summary>
/// Returns an Xceed base AbstractFolder object that is either a DiskFolder for
/// a local location, or FtpFolder for FTP locations
/// </summary>
/// <param name="location">The Location to retrieve an AbstractFolder</param>
/// <param name="connectionWrapper">The current FTP connection (or null) wrapped</param>
/// <returns>An AbstractFolder</returns>
private AbstractFolder GetAbstractFolder(Location location, ref ConnectionWrapper connectionWrapper)
{
if (location.Type == LocationType.Local)
{
return new DiskFolder(location.Folder);
}
if (location.Folder.Equals("/"))
{
return new FtpFolder(connectionWrapper.Connection);
}
return new FtpFolder(connectionWrapper.Connection, location.Folder.Substring(1));
}
/// <summary>
/// A method to create a connection using the values
/// in this service's fields
/// </summary>
/// <param name="location">
/// The location.
/// </param>
/// <returns>
/// An FtpConnection to use when communicating with the remote server
/// </returns>
private FtpConnection GetConnection(Location location)
{
if (string.IsNullOrEmpty(location.Server) || location.Port == 0)
{
throw new InvalidOperationException("You cannot create an FTP connection without a server and port.");
}
FtpConnection result;
if (location.Secure)
{
result = new FtpConnection(
location.Server,
location.Port,
location.Username,
location.Password,
AuthenticationMethod.Tls,
VerificationFlags.None,
null,
DataChannelProtection.Private,
false);
}
else
{
return new FtpConnection(location.Server, location.Port, location.Username, location.Password);
}
// Set up FTP logging
result.TraceWriter = new StreamWriter(**********, true);
return result;
}
/// <summary>
/// A delegate for the OnCertificateReceived event that always accepts
/// </summary>
/// <param name="sender">
/// The sender.
/// </param>
/// <param name="e">
/// The CertificateReceivedEventArgs.
/// </param>
private void OnCertificateReceived(object sender, CertificateReceivedEventArgs e)
{
e.Action = VerificationAction.Accept;
}
/// <summary>
/// A little class to wrap a connection so that we can dispose of the tracewriter
/// </summary>
private class ConnectionWrapper : IDisposable
{
public ConnectionWrapper(FtpConnection connection)
{
this.Connection = connection;
}
public FtpConnection Connection
{
get;
set;
}
public void Dispose()
{
if (this.Connection != null)
{
if (this.Connection.TraceWriter != null)
{
this.Connection.TraceWriter.Close();
this.Connection.TraceWriter.Dispose();
}
this.Connection.Dispose();
}
}
}