views:

101

answers:

3

I'm building a small web application with ASP.NET MVC 2, using db4o as a datastore.

I have added an HttpModule—as per the example here—to give the application access to the db4o database, and everything is working perfectly on my development machine under the VS2008 ASP.NET Development Server.

However, when I deploy the app to my web host and try to access it, I get a DatabaseFileLockedException at the line where the HttpModule tries to open the database file. But there should be nothing else accessing the file; indeed on first run of the app it will only just have been created when this exception gets thrown.

The web host's servers are running IIS 7 on Windows Server 2008, and the application is running under Full Trust. It is a sub-application, in case that makes any difference.

I can't work out why this error is occurring on the live server, but not locally on my development server. Can anyone help me out or suggest what I should do next?

A: 

Sounds like permission issues if it works on dev. Stick a notepad file in the same directory and try to open that with some bare bones file code. I bet you'll have the same issue.

jfar
Nice idea, but unfortunately it's not a file permission problem. I can successfully open and read from a text file in the same folder with no issues.
Mark B
+3  A: 

That's a mistake in the example-code. It assumes that the HttpModule.Init is only called once, which isn't necessarily true. Depending how your application is configured, it can be called multiple times. To fix this, check in the HttpModule-Handler if the instance is already there:

using System;
using System.Configuration;
using System.Web;
using Db4objects.Db4o;

namespace Db4oDoc.WebApp.Infrastructure
{
    public class Db4oProvider : IHttpModule
    {
        private const string DataBaseInstance = "db4o-database-instance";
        private const string SessionKey = "db4o-session";

        // #example: open database when the application starts
        public void Init(HttpApplication context)
        {
            if (null==HttpContext.Current.Application[DataBaseInstance])
            {
                HttpContext.Current.Application[DataBaseInstance] = OpenDatabase();
            }
            RegisterSessionCreation(context);
        }

        private IEmbeddedObjectContainer OpenDatabase()
        {
            string relativePath = ConfigurationSettings.AppSettings["DatabaseFileName"];
            string filePath = HttpContext.Current.Server.MapPath(relativePath);
            return Db4oEmbedded.OpenFile(filePath);
        }
        // #end example

        // #example: close the database when the application shuts down
        public void Dispose()
        {
            IDisposable toDispose = HttpContext.Current.Application[DataBaseInstance] as IDisposable;
            if (null != toDispose)
            {
                toDispose.Dispose();
            }
        }
        // #end example

        // #example: provide access to the database
        public static IObjectContainer Database
        {
            get { return (IObjectContainer)HttpContext.Current.Items[SessionKey]; }
        }
        // #end example

        // #example: A object container per request
        private void RegisterSessionCreation(HttpApplication httpApplication)
        {
            httpApplication.BeginRequest += OpenSession;
            httpApplication.EndRequest += CloseSession;
        }

        private void OpenSession(object sender, EventArgs e)
        {
            IEmbeddedObjectContainer container =
                (IEmbeddedObjectContainer)HttpContext.Current.Application[DataBaseInstance];
            IObjectContainer session = container.OpenSession();
            HttpContext.Current.Items[SessionKey] = session;
        }

        private void CloseSession(object sender, EventArgs e)
        {
            if (HttpContext.Current.Items[SessionKey] != null)
            {
                IObjectContainer session = (IObjectContainer)HttpContext.Current.Items[SessionKey];
                session.Dispose();
            }
        }
        // #end example
    }
}

As alternative you could use the Application_Start from the Global.apsx, which is called only once for sure.

Gamlor
+1 Thanks, that makes sense. One additional thing: after I'd made the change you suggested, the first exception disappeared, but I then started getting a `NullReferenceException` in the `CloseSession` method. I added a null test around the method code (I've edited the code in your answer so it reflects my change too) and everything seems to be working well now. Can you shed any light on why _that_ exception would have been occurring, just so I understand this better?
Mark B
Well it already checks for null in the close-method? What do you additionally check for null there?
Gamlor
It checks for null _now_ because I edited your answer; I was just trying to make more sense of when exactly those events are fired. But no problem, all is well now anyway, thanks.
Mark B
+1  A: 

You have another problem here.

When AppPools restart there can be an overlap when the old AppPool is finishing request and the new AppPool is servicing new requests.

During this time you will have two processes trying to access the same db4o file

To get around this you can use something like the hack below.

Note the use of Db4oFactory.OpenServer instead of Db4oEmbedded.OpenFile. This allows the use of transactions on a more fine grained basis.

public IObjectServer OpenServer()
{
    Logger.Debug("Waiting to open db4o server.");
    var attempts = 0;
    do
    {
        try
        {
            return Db4oFactory.OpenServer(fileName, 0);
        }
        catch (DatabaseFileLockedException ex)
        {
            attempts++;
            if (attempts > 10)
            {
                throw new Exception("Couldn't open db4o server. Giving up!", ex);
            }

            Logger.Warn("Couldn't open db4o server. Trying again in 5sec.");
            Thread.Sleep(5.Seconds());
        }
    } while (true);
}

Hope this helps

Simon