views:

670

answers:

4

I want to write some stats to a text file every time a person loads a page. But every once in awhile I am getting at 'Could Not Open File, Already in use' type of error. I can not 100% replicate this error it is very erratic. My code is

Public Sub WriteStats(ByVal ad_id As Integer)
    Dim ad_date As String = Now.Year & Now.Month

    Dim FILENAME As String = Server.MapPath("text/BoxedAds.txt")
    Dim objStreamWriter As StreamWriter
    objStreamWriter = File.AppendText(FILENAME)
    objStreamWriter.WriteLine(ad_id & ";" & ad_date)
    objStreamWriter.Close()
End Sub

My question is, how can I lock and unlock the file so I stop getting the erratic errors?

Thanks

+4  A: 

If two or more requests hit your web server at roughly the same time, they will all try to open the same file. You will need to create unique file names for each request.

Jakob Christensen
Actually: each session is probably good enough. It's unlikely for a single user be able to create that many simultaneous requests.
Joel Coehoorn
+1  A: 

You will have to handle the exception and build some handling to re-try writing to the file after a short random interval.

If you get too much contention then it might make more sense to log it to a table in a database and create a process to export to a file (if its still needed)

Nick Kavadias
I sometimes need to write to the file up to 20 times per page how big of a strain would that be on a database?
Bruno43
A: 

I haven't had any trouble with short info using: File.AppendAllText(path, info);

Regarding the comment on it causing locks, from reflector it uses the same options explained very well by Joel. It does not use the trace writer, so it won't output to a temp file in the case of high load / large content causing trouble.

If the info is large, you really want separate files. For high load, I would go with Joel's suggestion and create a temp file, which can be alternatively done by catching the exception on File.AppendAllText, and using the same File.AppeandAllText with a unique filename.

eglasius
It can still lock: I've seen it.
Joel Coehoorn
are you sure, this is on production and not a single log entry for that ... as I said, small bits of info
eglasius
@Joel I confirmed it uses the appropriate options with reflector, do you mean your solution also lock? (don't think so, as I said no log entries on production)
eglasius
@Joel, I didn't notice your use of the tracewriter generating a temp file if it was locked, updated my answer.
eglasius
+4  A: 
Public Sub WriteStats(ByVal ad_id As Integer)
    Dim ad_date As String = Now.Year & Now.Month
    Dim FILENAME As String = Server.MapPath("text/BoxedAds.txt")
    Dim index As Integer

    Using fs As New IO.FileStream(FILENAME, IO.FileMode.Append, IO.FileAccess.Write, IO.FileShare.ReadWrite), _
          tl As New TextWriterTraceListener(fs)

        index = Trace.Listeners.Add(tl)
        Trace.WriteLine(ad_id & ";" & ad_date)
        Trace.Listeners(index).Flush()
        Trace.Flush()
    End Using
    Trace.Listeners.RemoveAt(index)
End Sub

Three important things here:

  1. Use of IO.FileShare.ReadWrite to allow multiple writers on the file at once.
  2. The Using statement to make sure the stream is closed immediately, even if an exception occurs. This will minimize collisions
  3. The TextWriterTraceListener will create a temp file for your if it can't open the file you request, to make sure the message isn't lost.
Joel Coehoorn
Joel, no need to go with a custom solution when the simple File.AppendAllText does exactly that (check with reflector)
eglasius
I'll have to do that, but as I said above: I've see File.AppendAllText get lock.
Joel Coehoorn
Then your solution also does, I guess we need an alternate solution then / my take is that it wasn't small of info which will give trouble with load regardless of the approach
eglasius
hmm, nvm, re-read your answer, didn't notice your comment on TextWriterTraceListener using a temp file if it fails opening the file
eglasius
Yeah, I added that a bit later: I still have the code where the file was being locked and went back to reference how we solved it.
Joel Coehoorn
As an aside: I don't really like this because it preempts the trace class- if you're doing other tracing it can be weird. But I didn't see how else to create such nice temp file in the same folder: the IO.Path.GetRandom__/GetTemp___ methods aren't very helpful.
Joel Coehoorn
+1 nice answer + follow up :)
eglasius
With this code I am getting 'Listeners' is not a member of 'System.Web.TraceContext'
Bruno43
It's pulling in the wrong namespace: should be the Trace class from System.Diagnostics, not System.Web
Joel Coehoorn
I imported System.Diagnostics. Still giving me same error.
Bruno43
If you'll notice, no where in the code I posted do I ever use "TraceContext". It sounds like you let visual studio autocomplete substitute that in somewhere instead of just "Trace"
Joel Coehoorn
I do notice that but it doesnt say or use TraceContext anywhere on my side either.
Bruno43
In that case, make sure all the types are fully-qualified.
Joel Coehoorn
It was doing it on any line I had Trace. So I changed it to Diagnostics.Trace. that got rid of the errors but now it doesn't write to the file...
Bruno43
Thank you for trying to work with me, but it was easier for me to write error handling.
Bruno43