NH Prof New FeatureDetect Cross Thread Session Usage
NHibernate sessions are not thread safe, and attempting to use them in multiple threads requires careful synchronization. It is generally better to consider a session only useful within the thread that created it.
There are valid scenarios for cross thread session usage (background loading with careful sync, multi request spanning session), but the invalid scenarios are far more common.
Let us take a look at the following code:
public class CrossThreadSession : IScenario { public void Execute(ISessionFactory factory) { using(var session = factory.OpenSession()) { var @event = new ManualResetEvent(false); ThreadPool.QueueUserWorkItem(state => { using(var tx = session.BeginTransaction()) { session.Get<Blog>(1); tx.Commit(); } @event.Set(); }); @event.WaitOne(); } } }
If you’ll try to execute it with NH Prof listening, you are going to see this:
NH Prof is properly detecting and warning that we are using a single session in multiple threads.
I did mention that there are valid scenarios for that, so you can either just tell NHibernate to ignore this specific alert by clicking the link, or disable this alert completely if you are doing cross thread session usage extensively and you know it to be safe:
Nice enough feature, for about 30 minutes of work. I love working on NH Prof code base.
And just for fun, here is the test for this:
public class DetectCrossThreadSessionUsage : IntegrationTestBase { [Fact] public void CanDetectCrossThreadSessionUsage() { ExecuteScenarioInDifferentAppDomain<CrossThreadSession>(); var first = model.Sessions.First(); Assert.Equal(1, first.Statistics.NumberOfStatements); Assert.Equal(2, first.Statistics.NumberOfTransactionsStatements); foreach (var statement in first.StatementsModel.Statements) { Assert.True( statement.Alerts.Any(alert => alert.HelpTopic == "CrossThreadSessionUsage")); } } }
And for additional fun, here is the code to implement the feature:
public class CrossThreadSessionUsage : AbstractStatementProcessor { public override void AfterAttachingToSession(SessionInformation session, TransactionStatement transaction) { AfterAttachingToSession(session, (SqlStatement)transaction); } public override void AfterAttachingToSession(SessionInformation sessionInformation, SqlStatement statement) { if (sessionInformation.SessionId.IdentifierId.ThreadId == statement.SessionId.IdentifierId.ThreadId) return; statement.AcceptAlert(AlertInformation.CrossThreadSessionUsage()); } }
I did have to change some of the innards so NH Prof will track both the session id and the thread id, but that was quite easy to do and the only thing that actually took any time at all.
More posts in "NH Prof New Feature" series:
- (09 Dec 2010) Alert on bad ‘like’ query
- (10 Dec 2009) Filter static files
- (16 Nov 2009) Exporting Reports
- (08 Oct 2009) NHibernate Search Integration
- (19 Aug 2009) Multiple Session Factory Support
- (07 Aug 2009) Diffing Sessions
- (06 Aug 2009) Capturing DDL
- (05 Aug 2009) Detect Cross Thread Session Usage
- (22 May 2009) Detecting 2nd cache collection loads
- (15 May 2009) Error Detection
- (12 May 2009) Queries by Url
- (04 Feb 2009) View Query Results
- (18 Jan 2009) Superfluous <many-to-one> update
- (18 Jan 2009) URL tracking
- (10 Jan 2009) Detecting distributed transactions (System.Transactions)
- (06 Jan 2009) The Query Cache
- (05 Jan 2009) Query Duration
- (24 Dec 2008) Unbounded result sets
- (24 Dec 2008) Row Counts
Comments
Does NHProf update itself or do I need to get the latest version? How will I know when new geatures are added?
I have a license and wondered about this before.
Thanks for all the effort on this, its help my NH understanding loads
Anthony
Anthony,
No, NH Prof doesn't update itself.
You can follow the changes to NH Prof here:
http://builds.hibernatingrhinos.com/builds/NHProf
Just a style question (I'm curious)...
Why do you do this:
if (sessionInformation.SessionId.IdentifierId.ThreadId ==
statement.AcceptAlert(AlertInformation.CrossThreadSessionUsage());
instead of:
if (sessionInformation.SessionId.IdentifierId.ThreadId !=
Diego,
I prefer using exit conditions rather than conditional
Makes sense once you have to test for more conditions and don't want lots of complex nested if's..
Comment preview