Reputation: 25081
The problem:
My company puts out a monthly newsletter which I host on our internal website. I have a page for the author of the newsletter to upload the latest version. Once the author has uploaded the latest newsletter, he sends a broadcast email to announce the new newsletter. Employees invariably check the new newsletter and send feedback to the author with corrections that need to be made.
Once the author has made the necessary corrections (typically within an hour of sending the broadcast email), he revisits my page and replaces the latest version with the updated newsletter.
Immediately following the replacement (or update, if you will) of the newsletter, anyone attempting to access it gets a 500 - Internal Server Error.
My IT guy who maintains the server cannot delete/rename/move the file because of a permissions error and has to do a lot of convoluted things to get the file deleted (and once the file is deleted, the author of the newsletter can re-upload the corrected copy and it works fine.
My IT guy and I are pretty sure that the problem stems from that I'm trying to replace the file while IIS is actively serving it to users (which I thought of and thought that I had coded against happening).
The code that runs the replacement is as follows:
Protected Sub ReplaceLatestNewsletter()
Dim dr As DataRow
Dim sFile As String
Dim mFileLock As Mutex
Try
If Me.Archives.Rows.Count > 0 Then
dr = Me.Archives.Rows(0)
sFile = dr("File").ToString
If dr("Path").ToString.Length > 0 Then
mFileLock = New Mutex(True, "MyMutexToPreventReadsOnOverwrite")
Try
mFileLock.WaitOne()
System.IO.File.Delete(dr("Path").ToString)
Catch ex As Exception
lblErrs.Text = ex.ToString
Finally
mFileLock.ReleaseMutex()
End Try
End If
fuNewsletter.PostedFile.SaveAs(Server.MapPath("~/Newsletter/archives/" & sFile))
End If
Catch ex As Exception
lblErrs.Text = ex.ToString
End Try
dr = Nothing
sFile = Nothing
mFileLock = Nothing
End Sub
I thought the Mutex
would take care of this (although after re-reading documentation I'm not sure I can actually use it like I'm trying to). Other comments on the code above:
Me.Archives
is a DataTable
stored in ViewState
dr("File").ToString
is the filename (no path)dr("Path").ToString
is the full local machine path and filename (i.e., 'C:\App_Root\Newsletters\archives\20120214.pdf')In any case, I'm pretty sure that the code above is not establishing an exclusive lock on the file so that the file can be overwritten safely.
Ultimately, I would like to make sure that the following happens:
Suggestions?
Also, can I use a Mutex
to get a mutually exclusive lock on a file in the Windows filesystem?
Thank you in advance for your assistance and advice.
EDIT:
The way that the links for the newsletter are generated is based on the physical filename. The method used is:
DataRow
in a DataTable
DataTable
by date (descending).UPDATE:
In lieu of not being able to discern when all existing requests for that file have completed, I took a closer look at the first part of @Justin's answer ("your mutex will only have an effect if the process that reads from the file also obtains the same mutex.")
This led me to Configure IIS7 to server static content through ASP.NET Runtime and the linked article in the accepted answer.
To that end, I have implemented a handler for all PDF files which implements New Mutex(True, "MyMutexToPreventReadsOnOverwrite")
to ensure that only one thread is doing something with the PDF at any given time.
Thank you for you answer, @Justin. While I did not wind up using the implementation you suggested, your answer pointed me towards an acceptable solution.
Upvotes: 4
Views: 5096
Reputation: 86729
Your mutex will only have an effect if the process that reads from the file also obtains the same mutex. What is the method used to serve up the file? Is ASP.Net used or is this just a static file?
My workflow would be a little different:
This requires no locking and also means that we don't need to wait for requests for the current file be completed (something which could potentially take an indefinite amount of time if people keep on making new requests). The only interesting bit is step 2 which will depend on how the file is served - the easiest way would probably be to either set up a HTTP redirect or use URL rewriting
A HTTP Redirect is where the server tells the client to look in a different place when it gets a request for a given resource so that the browser URL is automatically updated to match the new location. For example if the user requested http://server/20120221.pdf
then they could be automatically redirected to another URL such as http://server/20120221_v2.pdf
(the URL shown in the browser would change however the URL they need to type in would not).
You can do this in IIS 7 using the httpRedirect
configuration element, for example:
<configuration>
<system.webServer>
<httpRedirect enabled="true" exactDestination="true" httpResponseStatus="Found">
<!-- Note that I needed to add a * in for IIS to accept the wildcard even though it isn't used in this case -->
<add wildcard="*20120221.pdf" destination="20120221_v2.pdf" />
</httpRedirect>
</system.webServer>
</configuration>
The linked page shows how to change these settings from ASP.Net
Alternatively IIS can be set up to automatically serve up the content of a different file for a given URL without the client (the browser) ever knowing the difference. This is called URL rewriting and can be done in IIS using something like this however it does require that additional components be installed to IIS to work.
Using a HTTP Redirect is probably the easiest method.
Upvotes: 4