Problem
 
UAB does not resume
broken downloads (due to connection failure or user cancelling the
download).
 
Solution
 
These are the two
changes to do in the BitsDownloader class
 
1. On the
OnJobError method we need to NOT cancel the BITS job if the state is
ERROR:
 
if( state !=
BG_JOB_STATE.BG_JOB_STATE_ACKNOWLEDGED &&
     state !=
BG_JOB_STATE.BG_JOB_STATE_CANCELLED &&
     state !=
BG_JOB_STATE.BG_JOB_STATE_TRANSFERRING &&      // don’t interrupt
downloads in progress
     state != BG_JOB_STATE.BG_JOB_STATE_TRANSFERRED
&&      // don’t cancel a finished download
     state !=
BG_JOB_STATE.BG_JOB_STATE_TRANSIENT_ERROR &&   // don’t cancel when
connection problems
     state != BG_JOB_STATE.BG_JOB_STATE_ERROR )
{            // don’t cancel when connection problems
    
pJob.Cancel();
 }
 
2. On the
CheckForResumeAndProceed method check to see if the job is in ERROR or
TRANSIENT_ERROR state and resume it. Why add this here? Because this method
checks if there is a pending job. If there is a pending job and its state is
ERROR, that means that the pending job failed due to a network failuire, or user
aborted the application. If we call Resume BITS try to connect again.
It will try to connect for 5 secs (NoProgressTimeout constant). If it couldn’t
do it, the job will stay in ERROR state. If not, it will transfer from the last
byte transferred.
The code in bold
letter need to be added
 
if (
jobState == BG_JOB_STATE.BG_JOB_STATE_TRANSFERRED )
{
      
OnJobTransferred( task, copyJob );
       return true;
}
if (
jobState == BG_JOB_STATE.BG_JOB_STATE_ERROR || jobState ==
BG_JOB_STATE.BG_JOB_STATE_TRANSIENT_ERROR ) {
      
copyJob.Resume();
}
 
Observations
  • This new code was
    tested in this scenarios: stopping the website; disabling the network card and
    aborting the application in the middle of the job, and it worked on all the
    scenarios. The job was resumed from the last byte transfered
  •  It’s important to
    attach to the PendingUpdatesDetected event of the
    ApplicationUpdaterManager. On the handler of this event call
    ResumePendingUpdates. This is important because this method
    deserializes the tasks that holds the BITS Job ID previously
    aborted.
  • It is not recommended
    to debug the WaitForDownload method due to concurrency and sync issues.
    Instead, tracing is recommended. A simple tracing could be implemented to
    troubleshoot, just by using the Logger.LogInformation method that will
    write to the Event Viewer.

I’ve been experimenting with EntLib Logging App Block. I wanted to do the async logging via MSMQ.

Async logging is one of the requirements of Enterprise Applications that does not want to waste cpu cycles logging on the Event Viewer or raising a WMI event or whatever logging strategy they use. Instead, a better approach is write the log message to a MSMQ and then have a service polling that queue that will do whatever with this log.

This way we could have our logging in a separate machine and the applications just write to a queue (which in turns much more fast compared to other things). Indeed we could write to a queue on the same machine. Here we could be more performant because the logging runs in a separate process (with a Windows Service, a Console app or whatever that polls the queue and get the log message).

Enterprise Library takes care of all this for us and we could have async logging setup in a matter of minutes.

I will show here what I did to get it running.

Client Application

First, we need to create the Logging Application Block for our application. Right Click on App -> New -> Logging and Instrumentation Application Block.

ent1

Then we need to remove some stuff that the client application won’t use. The logging will be done by a separate service, no by the client application inprocess. We won’t need the Distributor Settings and we don’t need the InProcess distribution strategy. So right click on Distributor Settings -> Remove. And Right click on InProcess -> Remove.

ent2

Now, we need to add the MSMQ distribution strategy. Right click on Distribution Strategies -> New -> MSMQ. The MSMQ distribution strategy has a queue associated where the LogEntries will be throw. Click on MSMQ and modify the QueuePath to some message queue you want (remember to create the queue before using it!)

ent3

Click on Client Settings and choose MSMQ on the DistributionStrategy property.

ent4

MSMQ Distributor Service

EntLib provides a Windows Service (MsmqDistributor) that will poll the message queue within a configured interval and do the real logging. This service is not installed by default, nor via InstallServices batch. So we need to install it by ourselves.

Open a vs.net 2003 command prompt and do a

cd [EntLibInstallDir]\bin\

Then run the installutil

installutil MsmqDistributor.exe

ent5

Now we need to configure the Logging block in the service. Think now that the service config file is like your client application. Remember that the async logging has two process running: the client application itself (winforms, web, whatever) and the service distributor (the service that will do the real logging).

You will need to copy two files required by the MsmqDistributor config otherwise there will be a validation error (it seems like the PAG guys forgot to include them in the bin directory). So copy these two files

[EntLibInstallDir]\src\Logging\MSMQDistributor\loggingConfiguration.config to [EntLibInstallDir]\bin
[EntLibInstallDir]\src\Logging\MSMQDistributor\loggingDistributorConfiguration.config to [EntLibInstallDir]\bin

Now, open the [EntLibInstallDir]\bin\MsmqDistributor.exe.config with the EntLib console (you can use the same console that you were using for the client app, but if you are using separate machines obviously not :)

ent6

(it seems they forgot to change a path there [C:\Depots\Microsoft\ELF2\QuickStarts\Logging\CS\LoggingQuickStart\App.config] ;)
Let’s configure the last thing! Right click on the Distributor Settings of the MsmqDistributor config file -> New -> MSMQ Distributor Service. This is the configuration that will tells the Windows Service which is the queue path where the logentries are being saved.
You should set the QueuePath in the property grid to the same queue configured in the client app (if you want it to work of course ;)

ent7

Finally, you can configure whatever you want regards Formatters, Sinks and Categories here. Remember to do it on the service and not the client!

Save All, start the service, start your application and enjoy Distributed Async Logging!