Search code examples
multithreadingamazon-web-servicesabort

Generic timeout function without thread.abort?


Reference: Implement C# Generic Timeout

I'm currently working on a project that pulls metrics from AWS and my program is just stalling on a few particular calls to ListObjects. I told my supervisor that I was considering using thread abort for this purpose and now he is asking me to write a generic timeout function rather than one specific to this case. This doesn't seem safe. Now I don't completely understand the snippet I'm using from the referenced question, so I'm a little in the dark here.

Call stack on stuck code:

mscorlib.dll!System.Threading.Thread.Sleep(int millisecondsTimeout) + 0x5 bytes AWSSDK.dll!Amazon.S3.AmazonS3Client.pauseOnRetry(int retries, int maxRetries, System.Net.HttpStatusCode status, string requestAddr, System.Net.WebHeaderCollection headers, System.Exception cause) + 0x5b bytes
AWSSDK.dll!Amazon.S3.AmazonS3Client.handleRetry(Amazon.S3.Model.S3Request userRequest, System.Net.HttpWebRequest request, System.Net.WebHeaderCollection respHdrs, long orignalStreamPosition, int retries, System.Net.HttpStatusCode statusCode, System.Exception cause) + 0x17e bytes AWSSDK.dll!Amazon.S3.AmazonS3Client.getResponseCallback(System.IAsyncResult result) + 0x57a bytes
AWSSDK.dll!Amazon.S3.AmazonS3Client.invoke(Amazon.S3.AmazonS3Client.S3AsyncResult s3AsyncResult, bool isRedirect) + 0xcde bytes
AWSSDK.dll!Amazon.S3.AmazonS3Client.getResponseCallback(System.IAsyncResult result) + 0x636 bytes
AWSSDK.dll!Amazon.S3.AmazonS3Client.invoke(Amazon.S3.AmazonS3Client.S3AsyncResult s3AsyncResult, bool isRedirect) + 0xcde bytes
AWSSDK.dll!Amazon.S3.AmazonS3Client.getResponseCallback(System.IAsyncResult result) + 0x636 bytes
AWSSDK.dll!Amazon.S3.AmazonS3Client.invoke(Amazon.S3.AmazonS3Client.S3AsyncResult s3AsyncResult, bool isRedirect) + 0xcde bytes
AWSSDK.dll!Amazon.S3.AmazonS3Client.getResponseCallback(System.IAsyncResult result) + 0x636 bytes
AWSSDK.dll!Amazon.S3.AmazonS3Client.invoke(Amazon.S3.AmazonS3Client.S3AsyncResult s3AsyncResult, bool isRedirect) + 0xcde bytes
AWSSDK.dll!Amazon.S3.AmazonS3Client.invoke(Amazon.S3.AmazonS3Client.S3AsyncResult s3AsyncResult) + 0x53 bytes
AWSSDK.dll!Amazon.S3.AmazonS3Client.invokeListObjects(Amazon.S3.Model.ListObjectsRequest request, System.AsyncCallback callback, object state, bool synchronized) + 0x102 bytes AWSSDK.dll!Amazon.S3.AmazonS3Client.ListObjects(Amazon.S3.Model.ListObjectsRequest request) + 0x31 bytes

First, is the purpose that I want to use this for (Amazon's ListObjects stalling) safe for thread abort?

Second, is there a safe way to do this in a generic function without asynchronous thread aborting?

Here is what I have:

Implementation:

public static class Timeout<TResult>
{
    private static int _timeout = 5000;

    //WARNING - This method uses asynchronous thread aborting and can result
    //in roughhousing and !@#$ hitting the fan
    public static TResult Run(Func<TResult> function)
    {
        if (function == null) throw new ArgumentNullException("function to timeout is null");

        var sync = new object();
        var isCompleted = false;

        WaitCallback watcher = obj =>
        {
            var watchedThread = obj as Thread;

            lock (sync)
            {
                if (!isCompleted)
                {
                    Monitor.Wait(sync, _timeout);
                }
            }

            if (!isCompleted)
            {
                watchedThread.Abort();
            }
        };

        try
        {
            ThreadPool.QueueUserWorkItem(watcher, Thread.CurrentThread);
            return function();
        }
        catch (ThreadAbortException)
        {
            Thread.ResetAbort();
            return default(TResult);
        }
        finally
        {
            lock (sync)
            {
                isCompleted = true;
                Monitor.Pulse(sync);
            }
        }
    }
}

The call:

response = Timeout<ListObjectsResponse>.Run(() => s3Client.ListObjects(request));

Solution

  • Well I guess I don't need to use generics, I found the cause of the root problem with amazon AWS. Apparently, if the name of the bucket has a period in it and the region isnt US(east), a specific endpoint needs to be configured for the client.