1

This may sound like a duplicate question, but I don't believe it is.

This is the error that I'm getting, which sounds very common. But I'm only getting this error intermittently. For the number of users that I get to my site, I'm estimating that this only occurs about 5% of the time.

Here's the code:

private TopWrestlers FillTopWrestlers()
{
    try
    {
        var mostRecent = _wrestlerRankingRepo.Query().OrderByDescending(wr => wr.Season).First().Season;
        var currentSeason = _configService.GetCurrentSeason();

        if (mostRecent > currentSeason)
            return null;

        var tws =
            _wrestlerRankingRepo.GetRankedWrestlers(currentSeason)
                .Where(wr => wr.Rank <= 3)
                .OrderBy(wr => wr.Rank)
                .Select(wr => wr.Wrestler);

        return new TopWrestlers
        {
            _125 = tws.FirstOrDefault(w => w.Roster.WeightClass == 125),
            _133 = tws.FirstOrDefault(w => w.Roster.WeightClass == 133),
            _141 = tws.FirstOrDefault(w => w.Roster.WeightClass == 141),
            _149 = tws.FirstOrDefault(w => w.Roster.WeightClass == 149),
            _157 = tws.FirstOrDefault(w => w.Roster.WeightClass == 157),
            _165 = tws.FirstOrDefault(w => w.Roster.WeightClass == 165),
            _174 = tws.FirstOrDefault(w => w.Roster.WeightClass == 174),
            _184 = tws.FirstOrDefault(w => w.Roster.WeightClass == 184),
            _197 = tws.FirstOrDefault(w => w.Roster.WeightClass == 197),
            _285 = tws.FirstOrDefault(w => w.Roster.WeightClass == 285)
        };
    }
    catch (Exception ex)
    {
        _errorService.LogError(new Exception("Exception occurred trying to retrieve the top wrestlers for each weight on the home page. " + ex.Message, ex));
        return null;
    }
}

I'm getting the error on the very first line:

_wrestlerRankingRepo.Query().OrderByDescending(wr => wr.Season).First().Season;

I KNOW that repo has data. Especially since it returns just fine 95% of the time.

Any help on how I can figure out how to fix this bug? I'm not even able to re-create the issue. This is on the homepage of the site. So I don't believe it has to do with a new session firing up....I'm at a loss here.

Here's the base repo code:

public abstract class BaseRepository<TRecord, TMap> : IBaseRepository<TRecord>
    where TRecord : class, IEntity
    where TMap : ClassMap<TRecord>, new()
{
    private static Member _primaryKeyMember;
    protected ISession Session;
    protected IUserIdentity UserIdentity;

    public BaseRepository(ISession session)
    {
        Session = session;
    }

    public BaseRepository(ISession session, IUserIdentity userIdentity)
    {
        Session = session;
        UserIdentity = userIdentity;
    }

    public void Delete(TRecord obj)
    {
        Session.Delete(obj);
        Session.Flush();
    }

    public void Save(TRecord value)
    {
        Session.SaveOrUpdate(value);
        Session.Flush();
    }

    public void Save(IEnumerable<TRecord> values)
    {
        using (ITransaction sessionTransaction = Session.BeginTransaction())
        {
            try
            {
                foreach (TRecord value in values)
                    Session.SaveOrUpdate(value);

                sessionTransaction.Commit();
                Session.Flush();
            }
            catch
            {
                sessionTransaction.Rollback();
                throw;
            }
        }
    }

    public virtual IQueryable<TRecord> Query()
    {
        return Session.Query<TRecord>();
    }
}

public interface IBaseRepository<TRecord>
    where TRecord : IEntity
{
    void Delete(TRecord obj);

    void Save(TRecord value);
    void Save(IEnumerable<TRecord> values);
    IQueryable<TRecord> Query();
}

And here's the WrestlerRankingRepo code:

public class WrestlerRankingRepository : BaseRepository<WrestlerRanking, WrestlerRankingMap>, IWrestlerRankingRepository
{
    public WrestlerRankingRepository(ISession session) : base(session)
    {
    }

    public IQueryable<WrestlerRanking> GetRankedWrestlers(int season)
    {
        return base.Query()
            .Where(wr => wr.Rank != null)
            .Where(wr => wr.Season == season)
            .Where(wr => wr.IsCurrent)
            .Where(wr => !wr.Wrestler.LastName.StartsWith("("));
    }

    public IQueryable<WrestlerRanking> GetRankedWrestlers(int season, int weight)
    {
        return GetRankedWrestlers(season)
            .Where(wr => wr.WeightClass == weight);
    }

    public IQueryable<WrestlerRanking> GetRankedWrestlersWithMatches(int season)
    {
        return GetRankedWrestlers(season);
        // for some reason it hates this: .Where(w => w.CurrentStats != null)
    }

    public IQueryable<WrestlerRanking> GetRankedWrestlersWithMatches(int season, int weight)
    {
        return GetRankedWrestlers(season)
            .Where(w => w.WeightClass == weight);
        // for some reason it hates this: .Where(w => w.CurrentStats != null)
    }
}

public interface IWrestlerRankingRepository : IBaseRepository<WrestlerRanking>
{
    IQueryable<WrestlerRanking> GetRankedWrestlers(int season);
    IQueryable<WrestlerRanking> GetRankedWrestlers(int season, int weight);
    IQueryable<WrestlerRanking> GetRankedWrestlersWithMatches(int season);
    IQueryable<WrestlerRanking> GetRankedWrestlersWithMatches(int season, int weight);
}

SessionFactory code:

public static class SessionFactory
{
    private static ISessionProvider _instance;

    public static ISessionProvider Instance
    {
        get
        {
            if (_instance == null)
            {
                string sessionClass = ConfigurationManager.AppSettings["SessionProvider"];

                if (string.IsNullOrWhiteSpace(sessionClass))
                    throw new ConfigurationErrorsException("Session Provider must be specified in the app.config");

                _instance = (ISessionProvider)Activator.CreateInstance(Type.GetType(sessionClass));
            }

            return _instance;
        }
    }

    [Obsolete]
    public static ISession GetCurrentSession()
    {
        return GetSession();
    }

    public static ISession GetSession()
    {
        return Instance.GetSession();
    }

    public static string GetConnectionString()
    {
        return ConfigurationManager.ConnectionStrings["WrestleStat"].ConnectionString;
    }

    public static IPersistenceConfigurer BuildConfig()
    {
        return MsSqlConfiguration.MsSql2008.ConnectionString(GetConnectionString());
    }

    public static void BuildMappings(MappingConfiguration mappings)
    {
        mappings.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly());
    }
}

public class SqlStatementInterceptor : EmptyInterceptor
{
    public override SqlString OnPrepareStatement(SqlString sql)
    {
        Trace.WriteLine(sql.ToString());
        return sql;
    }
}

public interface ISessionProvider
{
    ISession GetSession();
    ISession OpenSession();
    void CloseSession();
}

Here's the Ninject impelementation:

private static void RegisterServices(IKernel kernel)
    {
        kernel.Bind(scanner => scanner
                                   .From(new List<Assembly>
                                             {
                                                 typeof (HomeController).Assembly,
                                                 typeof (Match).Assembly
                                             })
                                   .SelectAllClasses()
                                   .BindAllInterfaces()
                                   .Configure(b => b.InTransientScope())
            );

        kernel.Rebind<ISession>().ToMethod(icontext => SessionFactory.GetSession()).InRequestScope();
        //kernel.Rebind<IUserIdentity>().ToMethod(i => MvcApplication.GetWebIdentity()).InRequestScope();
    }

Another spot that's relevant...

public class CoreWebSessionModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += OpenSession;
        context.EndRequest += CloseSession;
    }

    private static void OpenSession(object sender, EventArgs e)
    {
        SessionFactory.Instance.OpenSession();
    }

    private static void CloseSession(object sender, EventArgs e)
    {
        SessionFactory.Instance.CloseSession();
    }

    public void Dispose()
    {
    }
}

One more

public class CoreWebSessionProvider : ISessionProvider
    {
        private static ISessionFactory _holder;

        private static ISessionFactory MySessionFactory
        {
            get
            {
                if (_holder == null)
                {
                    _holder = GetFluentConfiguration().BuildSessionFactory();
                }

                return _holder;
            }
        }

        public ISession GetSession()
        {
            return HttpContext.Current != null ? MySessionFactory.GetCurrentSession() : null;
        }

        public ISession OpenSession()
        {
            var session = MySessionFactory.OpenSession();
            ManagedWebSessionContext.Bind(HttpContext.Current, session);
            return session;
        }

        public void CloseSession()
        {
            var session = ManagedWebSessionContext.Unbind(HttpContext.Current, MySessionFactory);

            if (session != null)
            {
                if (session.Transaction != null && session.Transaction.IsActive)
                    session.Transaction.Rollback();
                //else
                //    session.Flush();

                if (session.IsOpen)
                    session.Close();
            }
        }

        private static FluentConfiguration GetFluentConfiguration()
        {
            return
                Fluently.Configure()
                        .Database(SessionFactory.BuildConfig())
                        .ExposeConfiguration(BuildSchema)
                        .Mappings(SessionFactory.BuildMappings);
        }

        public static Configuration GetConfiguration()
        {
            return GetFluentConfiguration().BuildConfiguration();
        }

        private static void BuildSchema(Configuration config)
        {
            config.SetProperty("current_session_context_class", "managed_web");
        }

        public IStatelessSession GetStatelessSession()
        {
            return MySessionFactory.OpenStatelessSession();
        }
    }
ganders
  • 7,285
  • 17
  • 66
  • 114
  • Is the data shared in some way (static)? If it is shared then it is possible that this is caused by concurrency. I am not sure what is happening inside the Query but let's say it was shared data. One person hits this code and it "news up" a collection. by the time they got back to executing the orderby, someone else also came into this code and "newed up" the collection. If it is static then the first person's list just became empty. – Qudoos Aug 19 '15 at 13:11
  • Can we see the repo code? – DavidG Aug 19 '15 at 13:13
  • @DavidG question is updated – ganders Aug 19 '15 at 13:21
  • @Qudoos I think the answer is no (static stuff), but see my edit above with the repo code to see if it answers your question. – ganders Aug 19 '15 at 13:22
  • I know we are going down the rabbit hole here, but what about `ISession` and it's implementation? How is that created/injected? – DavidG Aug 19 '15 at 13:44
  • Your session instance looks to be a singleton class, perhaps that is your issue. – DavidG Aug 19 '15 at 13:56
  • @DavidG thanks. Do you foresee any repercussions to making this not static? I'm assuming that this is just the SqlConnection's that nhibernate uses to connect to the database. Is there a limit on those connections? – ganders Aug 19 '15 at 14:18
  • Agreed with @DavidG. Does session represent your Nhibernate context? If yes, then I think it is problematic for it to be singleton (the whole application has one context). I would make that non static (and per session) instead of per application. – Qudoos Aug 19 '15 at 14:19
  • I don't know much about NHibernate, but assuming it's not too different to Entity Framework, then you should never have a shared context as they are not designed to be used concurrently. It could certainly explain the intermittent errors. – DavidG Aug 19 '15 at 14:31
  • For the SQL connections, the general rule of thumb is connect as late as possible and disconnect as soon as possible, so don't let your context hang around. – DavidG Aug 19 '15 at 14:32
  • Thanks guys, could you both add these as "answers" so I can give you credit. Then I'll get it tested either tonight or tomorrow night and let you know the results. – ganders Aug 19 '15 at 15:08
  • Has "making session non-static" fixed the issues for you? – Suhas Aug 19 '15 at 21:13
  • Didn't have a chance to work on it last night, kids got in the way. Will be making the fix tonight. – ganders Aug 20 '15 at 12:53

2 Answers2

1

From the NHibernate documentation (emphasis mine):

An ISessionFactory is an expensive-to-create, threadsafe object intended to be shared by all application threads. An ISession is an inexpensive, non-threadsafe object that should be used once, for a single business process, and then discarded

So you should not be sharing your session object around like this. If possible, create one only when needed and destroy it as soon as possible.

Some excellent further reading here that talks about context and lifetime, including NHibernate.

Community
  • 1
  • 1
DavidG
  • 113,891
  • 12
  • 217
  • 223
1

The problem is most likely using a single session,

public static ISession GetSession()
{
    return Instance.GetSession();
}

and since your web application uses multiple threads, and each time this will return the same session. By definition session is not thread safe.

Best option is to configure your DI to inject new session per request (if you are using DI), for example for Autofac

builder.Register(x => x.Resolve<ISessionFactory>().OpenSession())
    .InstancePerHttpRequest();
Low Flying Pelican
  • 5,974
  • 1
  • 32
  • 43
  • Thank you. I added two additional sections to my original question at the bottom. I'm using Ninject for my DI. Does that look like the place that I should be making the changes? – ganders Aug 21 '15 at 12:59
  • Yes, make sure to create a new session instead of using the same session – Low Flying Pelican Aug 22 '15 at 03:20