public static Task DownloadAsync(this ILargeFileDownloadParameters parameters,
                                         CancellationToken?cancellationToken = null,
                                         IAsyncProgress <LargeFileDownloadProgressChangedEventArgs> progress = null,
                                         Action <string> logger = null, BufferManager bufferManager = null)
        {
            CancellationToken ct = (cancellationToken != null) ? cancellationToken.Value : CancellationToken.None;
            FailureToken      ft = new FailureToken();
            Task task            = Task.Factory.StartNew(() =>
            {
                // we put this logic on a new thread to avoid the risk of
                // running into thread starvation on the threadpool / deadlocks
                var t = new Thread(() => Downloader.StartDownloading(ct, ft, parameters, progress, logger));
                t.Start();
                t.Join();
            }, ct);

            return(task);
        }
Example #2
0
        public static Task DownloadAsync(this ILargeFileDownloadParameters parameters,
                                         CancellationToken? cancellationToken = null,
                                         IAsyncProgress<LargeFileDownloadProgressChangedEventArgs> progress = null,
                                         Action<string> logger = null, BufferManager bufferManager = null)
        {
            CancellationToken ct = (cancellationToken != null) ? cancellationToken.Value : CancellationToken.None;
            FailureToken ft = new FailureToken();
            Task task = Task.Factory.StartNew(() =>
            {
                // we put this logic on a new thread to avoid the risk of
                // running into thread starvation on the threadpool / deadlocks
                var t = new Thread(() => Downloader.StartDownloading(ct, ft, parameters, progress, logger));
                t.Start();
                t.Join();
            } , ct);

            return task;
        }
Example #3
0
        // needed for Unit Testing
        internal static void StartDownloading(CancellationToken ct,  FailureToken ft,
            ILargeFileDownloadParameters parameters, 
            IAsyncProgress<LargeFileDownloadProgressChangedEventArgs> progress = null,
            Action<string> logger = null,
            BufferManager bufferManager = null)
        {
            //create the file
            Stream stream = parameters.GetOutputStream();
            if (parameters.FileSize == 0) // Terminate Zero size files
            {
                if (progress != null)
                {
                    progress.Report(new LargeFileDownloadProgressChangedEventArgs(100, 0, 0,
                                                                                  parameters.FileSize,
                                                                                  parameters.FileSize, "",
                                                                                  "",
                                                                                  null));
                }
                if (parameters.AutoCloseStream)
                    stream.Close();
                return;
            }

            //figure out number of chunks
            int chunkCount = GetChunkCount(parameters.FileSize, parameters.MaxChunkSize);
            int numberOfThreads = Math.Min(parameters.MaxThreads, chunkCount);
            logger = logger ?? ((s) => { });

            var downloadWorkers = new List<Downloader>(numberOfThreads);
            var chunksWritten = new Dictionary<int, bool>();
            bool isFailed = false;
            long totalBytesWritten = 0;
            double byteWriteRate = 0.0;
            try
            {

                var readStack = new ConcurrentStack<int>();

                //add all of the chunks to the stack
                var rangeArray = Enumerable.Range(0, chunkCount).Reverse().ToArray();
                readStack.PushRange(rangeArray);
                chunksWritten = readStack.ToDictionary(k => k, v => false);

                var writeQueue = new ConcurrentQueue<ChunkedFilePart>();

                // ReSharper disable AccessToModifiedClosure
                Func<int, bool> downloadThrottle = (int c) => writeQueue.Count > 30;
                // ReSharper restore AccessToModifiedClosure
                if (bufferManager == null)
                {

                    bufferManager = new BufferManager(new[]
                    {
                        new BufferQueueSetting(SimpleHttpGetByRangeClient.BUFFER_SIZE, (uint) numberOfThreads),
                        new BufferQueueSetting((uint) parameters.MaxChunkSize, (uint) numberOfThreads)
                    });
                }

                int expectedChunkDownloadTime = ExpectedDownloadTimeInSeconds(parameters.MaxChunkSize);

                for (int i = 0; i < numberOfThreads; i++)
                {
                    downloadWorkers.Add(new Downloader(bufferManager, parameters, writeQueue, readStack,
                                        downloadThrottle, expectedChunkDownloadTime, ft, logger, ct));
                }
                //start all the download threads
                downloadWorkers.ForEach(x => x.Start());

                var watch = new System.Diagnostics.Stopwatch();
                watch.Start();
                long oldElapsedMilliSeconds = watch.ElapsedMilliseconds;
                DateTime lastWriteTime = DateTime.MaxValue;
                long lastPointInFile = 0;
                int kc = 0;
                //start the write loop
                while (chunksWritten.Any(kvp => !kvp.Value) && !ct.IsCancellationRequested && !ft.FailureDetected)
                {
                    ChunkedFilePart part;
                    while (writeQueue.TryDequeue(out part) && !ft.FailureDetected)
                    {
                        //retry?
                        logger(string.Format("[{1}] writing chunk: {0}", part.Chunk, parameters.Id));
                        stream.Position = part.FileOffset;
                        stream.Write(part.Content, 0, part.Length);
                        totalBytesWritten += part.Length;
                        bufferManager.FreeBuffer(part.Content);
                        chunksWritten[part.Chunk] = true;
                        lastWriteTime = DateTime.Now;
                        if (progress != null)
                        {
                            var elapsed = watch.ElapsedMilliseconds;
                            var diff = elapsed - oldElapsedMilliSeconds;
                            if (diff > 2000)
                            {
                                long bytesDownloaded = (long)chunksWritten.Count(kvp => kvp.Value) * parameters.MaxChunkSize;
                                long interimReads = bytesDownloaded + part.Length - lastPointInFile;
                                byteWriteRate = (interimReads / (diff / (double)1000));

                                lastPointInFile += interimReads;
                                oldElapsedMilliSeconds = elapsed;
                                progress.Report(new LargeFileDownloadProgressChangedEventArgs(ComputeProgressIndicator(totalBytesWritten, parameters.FileSize),
                                                                                              byteWriteRate, byteWriteRate, totalBytesWritten, totalBytesWritten, "", "", null));
                            }
                        }
                    }

                    // kill hanged workers
                    var timedOutWorkers = downloadWorkers
                        .Where(w => w.Status == ThreadState.Running || w.Status == ThreadState.WaitSleepJoin)
                        .Where((w) =>
                           {
                               if (w.SimulateTimedOut)
                                   return true;
                               return w.HeartBeat.AddSeconds(expectedChunkDownloadTime) < DateTime.Now;
                           })
                   .ToList();

                    if (timedOutWorkers.Any())
                    {
                        foreach (var worker in timedOutWorkers)
                        {
                            try
                            {
                                worker.DownloadWorkerThread.Abort(); // this has a minute chance of throwing
                                logger(string.Format("[{1}] killing thread as it timed out {0}", kc++, parameters.Id));
                                if (worker.SimulateTimedOut)
                                    Thread.Sleep(3000); // introduce delay for unit test to pick-up the condition
                            }
                            catch (Exception)
                            { }
                        }
                    }

                    var activeWorkers = downloadWorkers.Where(x => x != null &&
                        (x.Status == ThreadState.Running
                        || x.Status == ThreadState.WaitSleepJoin)).ToList();
                    // re-spawn the missing workers if some had too many retries or were killed

                    if (NeedToCheckForUnwrittenChunks(readStack, lastWriteTime, STALE_WRITE_CHECK_MINUTES))
                    {
                        // if there are any parts remaining to be written, AND the read stack is empty
                        var unreadParts = chunksWritten.Where(kvp => !kvp.Value);
                        if (readStack.IsEmpty && unreadParts.Any() && !ft.FailureDetected)
                        {
                            logger(string.Format("read stack is empty, but there remains unwritten parts!  Adding {0} parts back to read stack.", unreadParts.Count()));
                            readStack.Push(unreadParts.Select(kvp => kvp.Key).First());
                        }

                        lastWriteTime = DateTime.Now; // don't check again for a while
                    }

                    //wait for something that was added
                    Thread.Sleep(100);
                    if (activeWorkers.Count() < numberOfThreads)
                    {
                        for (int i = 0; i < numberOfThreads; i++)
                        {
                            if (downloadWorkers[i] == null)
                            {
                                logger(string.Format("[{0}] reviving killed thread", parameters.Id));
                                downloadWorkers[i] = new Downloader(bufferManager, parameters, writeQueue, readStack,
                                downloadThrottle, expectedChunkDownloadTime, ft, logger, ct);
                                downloadWorkers[i].Start();
                                continue;
                            }

                            if (downloadWorkers[i].Status == ThreadState.Running
                                || downloadWorkers[i].Status == ThreadState.WaitSleepJoin
                                || downloadWorkers[i].Status == ThreadState.Background
                                || downloadWorkers[i].Status == ThreadState.Stopped) continue;

                            logger(string.Format("[{0}] reviving killed thread", parameters.Id));
                            downloadWorkers[i] = new Downloader(bufferManager, parameters, writeQueue, readStack,
                                                                downloadThrottle, expectedChunkDownloadTime, ft, logger, ct);
                            downloadWorkers[i].Start();
                        }
                    }

                }

                if (ft.FailureDetected)
                {
                    throw new Exception(String.Format("[{0}]A Non Retry-able Failure was reported by one or more of the download workers.", parameters.Id));
                }
            }
            catch (Exception e)
            {
                // Report Failure
                isFailed = true;
                logger(string.Format("[{0}] Exception: TerminalVelocity Downloading failed.", parameters.Id));
                logger(string.Format("[{0}] Message: {1} ", parameters.Id, e.Message));
                logger(string.Format("[{0}] StackTrace: {1}", parameters.Id, e.StackTrace));
                if (progress != null)
                {
                    progress.Report(new LargeFileDownloadProgressChangedEventArgs(ComputeProgressIndicator(totalBytesWritten, parameters.FileSize), 0, 0, totalBytesWritten, totalBytesWritten, "", "", null, isFailed, e.Message));
                }
            }
            finally
            {
                //kill all the tasks if exist
                if (downloadWorkers != null)
                {
                    downloadWorkers.ForEach(x =>
                    {
                        if (x == null) return;

                        ExecuteAndSquash(x.Dispose);
                    });
                }
                if (parameters.AutoCloseStream)
                {
                    if (progress != null)
                    {
                        progress.Report(new LargeFileDownloadProgressChangedEventArgs(ComputeProgressIndicator(totalBytesWritten, parameters.FileSize), byteWriteRate, byteWriteRate, totalBytesWritten, totalBytesWritten, "", "", null, isFailed));
                    }
                    logger(string.Format("[{0}] AutoClosing stream", parameters.Id));
                    stream.Close();
                }
            }
        }
Example #4
0
        public Downloader(BufferManager bufferManager, 
                            ILargeFileDownloadParameters parameters,
                            ConcurrentQueue<ChunkedFilePart> writeQueue,
                            ConcurrentStack<int> readStack,
                            Func<int, bool> downloadThrottle, 
                            int expectedChunkTimeInSeconds,
                            FailureToken failureToken,
                            Action<string> logger = null,
                            CancellationToken? cancellation = null,                             
                            Func<ILargeFileDownloadParameters, 
                            ISimpleHttpGetByRangeClient> clientFactory = null)
        {
            SimulateTimedOut = false;
            NonRetryableError = false;
            HeartBeat = DateTime.Now;
            cancellation = (cancellation != null) ? cancellation.Value : CancellationToken.None;

            DownloadWorkerThread = new Thread((() =>
                                 {
                                     try
                                     {
                                     clientFactory = clientFactory ?? ((p) => new SimpleHttpGetByRangeClient(p.Uri, bufferManager, expectedChunkTimeInSeconds * 1000 ));

                                     logger = logger ?? ((s) => { });

                                     ISimpleHttpGetByRangeClient client = clientFactory(parameters);
                                     int currentChunk;
                                     readStack.TryPop(out currentChunk);
                                     int delayThrottle = 0;

                                     try
                                         {

                                             while (currentChunk >= 0 && !cancellation.Value.IsCancellationRequested && !failureToken.FailureDetected) //-1 when we are done
                                             {
                                                 logger(string.Format("[{1}] downloading: {0}", currentChunk,parameters.Id));
                                                 SimpleHttpResponse response = null;
                                                  var part = new ChunkedFilePart();
                                                 part.FileOffset =  GetChunkStart(currentChunk, parameters.MaxChunkSize);
                                                 part.Length = GetChunkSizeForCurrentChunk(parameters.FileSize,
                                                                                           parameters.MaxChunkSize,
                                                                                           currentChunk);
                                                 try
                                                 {
                                                     response = client.Get(parameters.Uri, part.FileOffset, part.Length);
                                                 }
                                                 catch (Exception e)
                                                 {
                                                     logger(string.Format("[{0}] {1}", parameters.Id, (e.InnerException != null
                                                             ? e.InnerException.Message
                                                             : e.Message)));

                                                     ExecuteAndSquash(client.Dispose);
                                                     client = clientFactory(parameters);
                                                 }

                                                 if (response != null && response.WasSuccessful)
                                                 {
                                                     part.Chunk = currentChunk;
                                                     part.Content = response.Content;
                                                     writeQueue.Enqueue(part);

                                                     // reset the throttle when the part is finally successful
                                                     delayThrottle = 0;
                                                     logger(string.Format("[{1}] downloaded: {0}", currentChunk,parameters.Id));

                                                     HeartBeat = DateTime.Now;

                                                     if (!readStack.TryPop(out currentChunk))
                                                     {
                                                         currentChunk = -1;
                                                     }
                                                     while (downloadThrottle(currentChunk))
                                                     {
                                                         logger(string.Format("[{1}] throttling for chunk: {0}", currentChunk,parameters.Id));
                                                         if (!cancellation.Value.IsCancellationRequested && !failureToken.FailureDetected)
                                                         {
                                                             Thread.Sleep(500);
                                                         }
                                                     }
                                                 }
                                                 else if (response == null || response.IsStatusCodeRetryable)
                                                 {
                                                     int sleepSecs = Math.Min((int)Math.Pow(4.95, delayThrottle), 600);
                                                     logger(string.Format("[{2}] sleeping: {0}, {1}s", currentChunk, sleepSecs, parameters.Id));
                                                     if (!cancellation.Value.IsCancellationRequested && !failureToken.FailureDetected)
                                                     {
                                                         Thread.Sleep(sleepSecs * 1000); // 4s, 25s, 120s, 600s
                                                         delayThrottle++;
                                                     }
                                                 }
                                                 else
                                                 {
                                                     logger(String.Format("[{3}] parameters.Uri:{0}  part.FileOffset:{1} part.Length:{2}", parameters.Uri, part.FileOffset, part.Length, parameters.Id));
                                                     logger(string.Format("[{1}] ERROR!NonRetryableError! going to trigger download failure because got Response.StatusCode: {0}", response.StatusCode, parameters.Id));
                                                     NonRetryableError = true;
                                                     failureToken.TriggerFailure();
                                                     break;
                                                     //throw new SimpleHttpClientException(response);
                                                 }
                                             }
                                         }
                                         finally
                                         {
                                             if (currentChunk >= 0  /*&& !NonRetryableError*/)
                                             {
                                                 //put it back on the stack, if it's poison everyone else will die
                                                 readStack.Push(currentChunk);
                                             }
                                             if (client != null)
                                             {
                                                 ExecuteAndSquash(client.Dispose);
                                             }
                                         }
                                     logger(String.Format("[{1}] Thread {0} done", Thread.CurrentThread.ManagedThreadId, parameters.Id));
                                     }
                                     catch (ThreadAbortException exc)
                                     {
                                         Console.WriteLine(exc);
                                         Thread.Sleep(1000);
                                         throw;
                                     }
                                 }));
        }
        // needed for Unit Testing

        internal static void StartDownloading(CancellationToken ct, FailureToken ft,
                                              ILargeFileDownloadParameters parameters,
                                              IAsyncProgress <LargeFileDownloadProgressChangedEventArgs> progress = null,
                                              Action <string> logger      = null,
                                              BufferManager bufferManager = null)
        {
            //create the file
            Stream stream = parameters.GetOutputStream();

            if (parameters.FileSize == 0) // Terminate Zero size files
            {
                if (progress != null)
                {
                    progress.Report(new LargeFileDownloadProgressChangedEventArgs(100, 0, 0,
                                                                                  parameters.FileSize,
                                                                                  parameters.FileSize, "",
                                                                                  "",
                                                                                  null));
                }
                if (parameters.AutoCloseStream)
                {
                    stream.Close();
                }
                return;
            }

            //figure out number of chunks
            int chunkCount      = GetChunkCount(parameters.FileSize, parameters.MaxChunkSize);
            int numberOfThreads = Math.Min(parameters.MaxThreads, chunkCount);

            logger = logger ?? ((s) => { });


            var    downloadWorkers   = new List <Downloader>(numberOfThreads);
            var    chunksWritten     = new Dictionary <int, bool>();
            bool   isFailed          = false;
            long   totalBytesWritten = 0;
            double byteWriteRate     = 0.0;

            try
            {
                var readStack = new ConcurrentStack <int>();

                //add all of the chunks to the stack
                var rangeArray = Enumerable.Range(0, chunkCount).Reverse().ToArray();
                readStack.PushRange(rangeArray);
                chunksWritten = readStack.ToDictionary(k => k, v => false);

                var writeQueue = new ConcurrentQueue <ChunkedFilePart>();

                // ReSharper disable AccessToModifiedClosure
                Func <int, bool> downloadThrottle = (int c) => writeQueue.Count > 30;
                // ReSharper restore AccessToModifiedClosure
                if (bufferManager == null)
                {
                    bufferManager = new BufferManager(new[]
                    {
                        new BufferQueueSetting(SimpleHttpGetByRangeClient.BUFFER_SIZE, (uint)numberOfThreads),
                        new BufferQueueSetting((uint)parameters.MaxChunkSize, (uint)numberOfThreads)
                    });
                }

                int expectedChunkDownloadTime = ExpectedDownloadTimeInSeconds(parameters.MaxChunkSize);

                for (int i = 0; i < numberOfThreads; i++)
                {
                    downloadWorkers.Add(new Downloader(bufferManager, parameters, writeQueue, readStack,
                                                       downloadThrottle, expectedChunkDownloadTime, ft, logger, ct));
                }
                //start all the download threads
                downloadWorkers.ForEach(x => x.Start());

                var watch = new System.Diagnostics.Stopwatch();
                watch.Start();
                long     oldElapsedMilliSeconds = watch.ElapsedMilliseconds;
                DateTime lastWriteTime          = DateTime.MaxValue;
                long     lastPointInFile        = 0;
                int      kc = 0;
                //start the write loop
                while (chunksWritten.Any(kvp => !kvp.Value) && !ct.IsCancellationRequested && !ft.FailureDetected)
                {
                    ChunkedFilePart part;
                    while (writeQueue.TryDequeue(out part) && !ft.FailureDetected)
                    {
                        //retry?
                        logger(string.Format("[{1}] writing chunk: {0}", part.Chunk, parameters.Id));
                        stream.Position = part.FileOffset;
                        stream.Write(part.Content, 0, part.Length);
                        totalBytesWritten += part.Length;
                        bufferManager.FreeBuffer(part.Content);
                        chunksWritten[part.Chunk] = true;
                        lastWriteTime             = DateTime.Now;
                        if (progress != null)
                        {
                            var elapsed = watch.ElapsedMilliseconds;
                            var diff    = elapsed - oldElapsedMilliSeconds;
                            if (diff > 2000)
                            {
                                long bytesDownloaded = (long)chunksWritten.Count(kvp => kvp.Value) * parameters.MaxChunkSize;
                                long interimReads    = bytesDownloaded + part.Length - lastPointInFile;
                                byteWriteRate = (interimReads / (diff / (double)1000));

                                lastPointInFile       += interimReads;
                                oldElapsedMilliSeconds = elapsed;
                                progress.Report(new LargeFileDownloadProgressChangedEventArgs(ComputeProgressIndicator(totalBytesWritten, parameters.FileSize),
                                                                                              byteWriteRate, byteWriteRate, totalBytesWritten, totalBytesWritten, "", "", null));
                            }
                        }
                    }

                    // kill hanged workers
                    var timedOutWorkers = downloadWorkers
                                          .Where(w => w.Status == ThreadState.Running || w.Status == ThreadState.WaitSleepJoin)
                                          .Where((w) =>
                    {
                        if (w.SimulateTimedOut)
                        {
                            return(true);
                        }
                        return(w.HeartBeat.AddSeconds(expectedChunkDownloadTime) < DateTime.Now);
                    })
                                          .ToList();

                    if (timedOutWorkers.Any())
                    {
                        foreach (var worker in timedOutWorkers)
                        {
                            try
                            {
                                worker.DownloadWorkerThread.Abort(); // this has a minute chance of throwing
                                logger(string.Format("[{1}] killing thread as it timed out {0}", kc++, parameters.Id));
                                if (worker.SimulateTimedOut)
                                {
                                    Thread.Sleep(3000); // introduce delay for unit test to pick-up the condition
                                }
                            }
                            catch (Exception)
                            { }
                        }
                    }

                    var activeWorkers = downloadWorkers.Where(x => x != null &&
                                                              (x.Status == ThreadState.Running ||
                                                               x.Status == ThreadState.WaitSleepJoin)).ToList();
                    // re-spawn the missing workers if some had too many retries or were killed

                    if (NeedToCheckForUnwrittenChunks(readStack, lastWriteTime, STALE_WRITE_CHECK_MINUTES))
                    {
                        // if there are any parts remaining to be written, AND the read stack is empty
                        var unreadParts = chunksWritten.Where(kvp => !kvp.Value);
                        if (readStack.IsEmpty && unreadParts.Any() && !ft.FailureDetected)
                        {
                            logger(string.Format("read stack is empty, but there remains unwritten parts!  Adding {0} parts back to read stack.", unreadParts.Count()));
                            readStack.Push(unreadParts.Select(kvp => kvp.Key).First());
                        }

                        lastWriteTime = DateTime.Now; // don't check again for a while
                    }

                    //wait for something that was added
                    Thread.Sleep(100);
                    if (activeWorkers.Count() < numberOfThreads)
                    {
                        for (int i = 0; i < numberOfThreads; i++)
                        {
                            if (downloadWorkers[i] == null)
                            {
                                logger(string.Format("[{0}] reviving killed thread", parameters.Id));
                                downloadWorkers[i] = new Downloader(bufferManager, parameters, writeQueue, readStack,
                                                                    downloadThrottle, expectedChunkDownloadTime, ft, logger, ct);
                                downloadWorkers[i].Start();
                                continue;
                            }

                            if (downloadWorkers[i].Status == ThreadState.Running ||
                                downloadWorkers[i].Status == ThreadState.WaitSleepJoin ||
                                downloadWorkers[i].Status == ThreadState.Background ||
                                downloadWorkers[i].Status == ThreadState.Stopped)
                            {
                                continue;
                            }

                            logger(string.Format("[{0}] reviving killed thread", parameters.Id));
                            downloadWorkers[i] = new Downloader(bufferManager, parameters, writeQueue, readStack,
                                                                downloadThrottle, expectedChunkDownloadTime, ft, logger, ct);
                            downloadWorkers[i].Start();
                        }
                    }
                }

                if (ft.FailureDetected)
                {
                    throw new Exception(String.Format("[{0}]A Non Retry-able Failure was reported by one or more of the download workers.", parameters.Id));
                }
            }
            catch (Exception e)
            {
                // Report Failure
                isFailed = true;
                logger(string.Format("[{0}] Exception: TerminalVelocity Downloading failed.", parameters.Id));
                logger(string.Format("[{0}] Message: {1} ", parameters.Id, e.Message));
                logger(string.Format("[{0}] StackTrace: {1}", parameters.Id, e.StackTrace));
                if (progress != null)
                {
                    progress.Report(new LargeFileDownloadProgressChangedEventArgs(ComputeProgressIndicator(totalBytesWritten, parameters.FileSize), 0, 0, totalBytesWritten, totalBytesWritten, "", "", null, isFailed, e.Message));
                }
            }
            finally
            {
                //kill all the tasks if exist
                if (downloadWorkers != null)
                {
                    downloadWorkers.ForEach(x =>
                    {
                        if (x == null)
                        {
                            return;
                        }

                        ExecuteAndSquash(x.Dispose);
                    });
                }
                if (parameters.AutoCloseStream)
                {
                    if (progress != null)
                    {
                        progress.Report(new LargeFileDownloadProgressChangedEventArgs(ComputeProgressIndicator(totalBytesWritten, parameters.FileSize), byteWriteRate, byteWriteRate, totalBytesWritten, totalBytesWritten, "", "", null, isFailed));
                    }
                    logger(string.Format("[{0}] AutoClosing stream", parameters.Id));
                    stream.Close();
                }
            }
        }
        public Downloader(BufferManager bufferManager,
                          ILargeFileDownloadParameters parameters,
                          ConcurrentQueue <ChunkedFilePart> writeQueue,
                          ConcurrentStack <int> readStack,
                          Func <int, bool> downloadThrottle,
                          int expectedChunkTimeInSeconds,
                          FailureToken failureToken,
                          Action <string> logger         = null,
                          CancellationToken?cancellation = null,
                          Func <ILargeFileDownloadParameters,
                                ISimpleHttpGetByRangeClient> clientFactory = null)
        {
            SimulateTimedOut  = false;
            NonRetryableError = false;
            HeartBeat         = DateTime.Now;
            cancellation      = (cancellation != null) ? cancellation.Value : CancellationToken.None;

            DownloadWorkerThread = new Thread((() =>
            {
                try
                {
                    clientFactory = clientFactory ?? ((p) => new SimpleHttpGetByRangeClient(p.Uri, bufferManager, expectedChunkTimeInSeconds * 1000));

                    logger = logger ?? ((s) => { });

                    ISimpleHttpGetByRangeClient client = clientFactory(parameters);
                    int currentChunk;
                    readStack.TryPop(out currentChunk);
                    int delayThrottle = 0;

                    try
                    {
                        while (currentChunk >= 0 && !cancellation.Value.IsCancellationRequested && !failureToken.FailureDetected)                      //-1 when we are done
                        {
                            logger(string.Format("[{1}] downloading: {0}", currentChunk, parameters.Id));
                            SimpleHttpResponse response = null;
                            var part = new ChunkedFilePart();
                            part.FileOffset = GetChunkStart(currentChunk, parameters.MaxChunkSize);
                            part.Length = GetChunkSizeForCurrentChunk(parameters.FileSize,
                                                                      parameters.MaxChunkSize,
                                                                      currentChunk);
                            try
                            {
                                response = client.Get(parameters.Uri, part.FileOffset, part.Length);
                            }
                            catch (Exception e)
                            {
                                logger(string.Format("[{0}] {1}", parameters.Id, (e.InnerException != null
                                                             ? e.InnerException.Message
                                                             : e.Message)));

                                ExecuteAndSquash(client.Dispose);
                                client = clientFactory(parameters);
                            }

                            if (response != null && response.WasSuccessful)
                            {
                                part.Chunk = currentChunk;
                                part.Content = response.Content;
                                writeQueue.Enqueue(part);

                                // reset the throttle when the part is finally successful
                                delayThrottle = 0;
                                logger(string.Format("[{1}] downloaded: {0}", currentChunk, parameters.Id));

                                HeartBeat = DateTime.Now;

                                if (!readStack.TryPop(out currentChunk))
                                {
                                    currentChunk = -1;
                                }
                                while (downloadThrottle(currentChunk))
                                {
                                    logger(string.Format("[{1}] throttling for chunk: {0}", currentChunk, parameters.Id));
                                    if (!cancellation.Value.IsCancellationRequested && !failureToken.FailureDetected)
                                    {
                                        Thread.Sleep(500);
                                    }
                                }
                            }
                            else if (response == null || response.IsStatusCodeRetryable)
                            {
                                int sleepSecs = Math.Min((int)Math.Pow(4.95, delayThrottle), 600);
                                logger(string.Format("[{2}] sleeping: {0}, {1}s", currentChunk, sleepSecs, parameters.Id));
                                if (!cancellation.Value.IsCancellationRequested && !failureToken.FailureDetected)
                                {
                                    Thread.Sleep(sleepSecs * 1000);                      // 4s, 25s, 120s, 600s
                                    delayThrottle++;
                                }
                            }
                            else
                            {
                                logger(String.Format("[{3}] parameters.Uri:{0}  part.FileOffset:{1} part.Length:{2}", parameters.Uri, part.FileOffset, part.Length, parameters.Id));
                                logger(string.Format("[{1}] ERROR!NonRetryableError! going to trigger download failure because got Response.StatusCode: {0}", response.StatusCode, parameters.Id));
                                NonRetryableError = true;
                                failureToken.TriggerFailure();
                                break;
                                //throw new SimpleHttpClientException(response);
                            }
                        }
                    }
                    finally
                    {
                        if (currentChunk >= 0 /*&& !NonRetryableError*/)
                        {
                            //put it back on the stack, if it's poison everyone else will die
                            readStack.Push(currentChunk);
                        }
                        if (client != null)
                        {
                            ExecuteAndSquash(client.Dispose);
                        }
                    }
                    logger(String.Format("[{1}] Thread {0} done", Thread.CurrentThread.ManagedThreadId, parameters.Id));
                }
                catch (ThreadAbortException exc)
                {
                    Console.WriteLine(exc);
                    Thread.Sleep(1000);
                    throw;
                }
            }));
        }