I've written a C# MVC 3 with NHibernate as the ORM and I'm having some odd exceptions thrown on most page loads. They seem to mostly relate to closed sessions and the like and I've checked most of the common issues but found little to help. Some of the exceptions include:
[[NHibernate.Util.ADOExceptionReporter]] : System.InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.
at System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand command)
[[NHibernate.Transaction.AdoTransaction]] : Begin transaction failed
System.Data.SqlClient.SqlException (0x80131904): The server failed to resume the transaction. Desc:3b00000006.
[[NHibernate.Transaction.AdoTransaction]] : Commit failed
System.NullReferenceException: Object reference not set to an instance of an object.
[[NHibernate.Transaction.AdoTransaction]] : Commit failed
System.Data.SqlClient.SqlException (0x80131904): The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
[[NHibernate.Util.ADOExceptionReporter]] : System.InvalidOperationException: The transaction is either not associated with the current connection or has been completed.
at System.Data.SqlClient.SqlCommand.ValidateCommand(String method, Boolean async)
[[NHibernate.Transaction.AdoTransaction]] : Begin transaction failed
System.InvalidOperationException: SqlConnection does not support parallel transactions.
I apologise for the wall of exceptions, I suspect they are related but there could potentially be another error in the code as well causing one or two.
I don't like to use the word random for these things but I can't seem to track down any specific line of code that calls them, they just seem to appear at lines of code relating to ISession objects. I have even had a "Session is closed" exception thrown on the BeginTranscation method in my Global.asax file.
The application uses the web option of current_session_context_class in hibernate.cfg.xml.
My suspicion is that it is related to my session management code. The website usually loads around 10 simultaneous AJAX requests and the errors seem to occur more often when multiple pages are loading at the same time. There are two session factories, one for each database being used.
Here is my relevant Global.asax code:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
_sessionFactory = (new WebClientSessionManager()).MakeSessionFactory();
_sessionFactoryNotWeb = ClientSessionManager.MakeSessionFactory();
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
_session = _sessionFactory.OpenSession();
_sessionNotWeb = _sessionFactoryNotWeb.OpenSession();
CurrentSessionContext.Bind(_sessionNotWeb);
CurrentSessionContext.Bind(_session);
_session.BeginTransaction();
_sessionNotWeb.BeginTransaction();
}
protected void Application_EndRequest(object sender, EventArgs e)
{
//Same code is repeated for the _sessionFactoryNotWeb
ISession session = CurrentSessionContext.Unbind(_sessionFactory);
if (session != null)
{
if (session.Transaction.IsActive)
{
try
{
session.Transaction.Commit();
}
catch
{
session.Transaction.Rollback();
}
}
try
{
session.Dispose();
}
catch
{
}
}
I have had a look at the page running in NHibernate profiler. Sometimes sessions are not started with BeginTranscation, sometimes they are not Committed, sometimes neither; and most puzzlingly, sometimes they are started three times but not finished.
Any calls to the ISession object are managed through this code (there is one for each factory):
public static ISession WebSession()
{
if (CurrentSessionContext.HasBind(MvcApplication._sessionFactory))
{
if (MvcApplication._sessionFactory.GetCurrentSession().IsOpen)
{
return MvcApplication._sessionFactory.GetCurrentSession();
}
else
{
log4net.LogManager.GetLogger(typeof(DBHandler)).Debug("Unbinding NHibernate session");
CurrentSessionContext.Unbind(MvcApplication._sessionFactory);
return WebSession();
}
}
else
{
log4net.LogManager.GetLogger(typeof(DBHandler)).Debug("Initialising NHibernate session");
var session = MvcApplication._sessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
session.BeginTransaction();
return session;
}
}
There are no calls to BeginTransaction or Commit throughout the app without them being flushed, committed, the session disposed of and then being reopened as per the code above.
Any light you guys could shed on this would be much appreciated!
You seem to store your session in a global (application wide) property in your Global.asax.cs. That means, the property contains the last session created, not the session for a specific request. Two requests at the same time and you don't even know if you still access the session you just created, because it might already have been overwritten by another session from the next request, running the same code. You shouldn't store your session somewhere unrelated to the web request if you want to follow a Session-per-Request pattern. You could for instance store your NH session in the HttpContext.Items-Collection. As another way to session management in MVC Ayende posted a nice example on how to wrap session management around a single MVC action instead of the whole request.
Related
According to the ASP.Net Core docs, the behaviour of the session state has changed in that it is now non-locking:
Session state is non-locking. If two requests simultaneously attempt to modify the contents of a session, the last request overrides the first. Session is implemented as a coherent session, which means that all the contents are stored together. When two requests seek to modify different session values, the last request may override session changes made by the first.
My understanding is that this is different to the behaviour of the session in the .Net Framework, where the user's session was locked per request so that whenever you read from/wrote to it, you weren't overwriting another request's data or reading stale data, for that user.
My question(s):
Is there a way to re-enable this per-request locking of the user's session in .Net Core?
If not, is there a reliable way to use the session to prevent duplicate submission of data for a given user? To give a specific example, we have a payment process that involves the user returning from an externally hosted ThreeDSecure (3DS) iFrame (payment card security process). We are noticing that sometimes (somehow) the user is submitting the form within the iFrame multiple times, which we have no control over. As a result this triggers multiple callbacks to our application. In our previous .Net Framework app, we used the session to indicate if a payment was in progress. If this flag was set in the session and you hit the 3DS callback again, the app would stop you proceeding. However, now it seems that because the session isn't locked, when these near simultaneous, duplicate callbacks occur, thread 'A' sets 'payment in progress = true' but thread 'B' doesn't see that in time, it's snapshot of the session is still seeing 'payment in progress = false' and the callback logic is processed twice.
What are some good approaches to handling simultaneous requests accessing the same session, now that the way the session works has changed?
The problem that you have faced with is called Race Condition (stackoverflow, wiki). To cut-through, you'd like to get exclusive access to the session state, you can achieve that in several ways and they highly depend on your architecture.
In-process synchronization
If you have a single machine with a single process handling all requests (for example you use a self-hosted server, Kestrel), you may use lock. Just do it correctly and not how #TMG suggested.
Here is an implementation reference:
Use single global object to lock all threads:
private static object s_locker = new object();
public bool Process(string transaction) {
lock (s_locker) {
if(!HttpContext.Session.TryGetValue("TransactionId", out _)) {
... handle transaction
}
}
}
Pros: a simple solution
Cons: all requests from all users will wait on this lock
use per-session lock object. Idea is similar, but instead of a single object you just use a dictionary:
internal class LockTracker : IDisposable
{
private static Dictionary<string, LockTracker> _locks = new Dictionary<string, LockTracker>();
private int _activeUses = 0;
private readonly string _id;
private LockTracker(string id) => _id = id;
public static LockTracker Get(string id)
{
lock(_locks)
{
if(!_locks.ContainsKey(id))
_locks.Add(id, new LockTracker(id));
var res = _locks[id];
res._activeUses += 1;
return res;
}
}
void IDisposable.Dispose()
{
lock(_locks)
{
_activeUses--;
if(_activeUses == 0)
_locks.Remove(_id);
}
}
}
public bool Process(string transaction)
{
var session = HttpContext.Session;
var locker = LockTracker.Get(session.Id);
using(locker) // remove object after execution if no other sessions use it
lock (locker) // synchronize threads on session specific object
{
// check if current session has already transaction in progress
var transactionInProgress = session.TryGetValue("TransactionId", out _);
if (!transactionInProgress)
{
// if there is no transaction, set and handle it
HttpContext.Session.Set("TransactionId", System.Text.Encoding.UTF8.GetBytes(transaction));
HttpContext.Session.Set("StartTransaction", BitConverter.GetBytes(DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
// handle transaction here
}
// return whatever you need, here is just a boolean.
return transactionInProgress;
}
}
Pros: manages concurrency on the session level
Cons: more complex solution
Remember that lock-based option will work only when the same process on the webserver handling all user's requests - lock is intra-process synchronization mechanism! Depending on what you use as a persistent layer for sessions (like NCache or Redis), this option might be the most performant though.
Cross-process synchronization
If there are several processes on the machine (for example you have IIS and apppool is configured to run multiple worker processes), then you need to use kernel-level synchronization primitive, like Mutex.
Cross-machine synchronization
If you have a load balancer (LB) in front of your webfarm so that any of N machines can handle user's request, then getting exclusive access is not so trivial.
One option here is to simplify the problem by enabling the 'sticky session' option in your LB so that all requests from the same user (session) will be routed to the same machine. In this case, you are fine to use any cross-process or in-process synchronization option (depends on what you have running there).
Another option is to externalize synchronization, for example, move it to the transactional DB, something similar to what #HoomanBahreini suggested. Beware that you need to be very cautious on handling failure scenarios: you may mark your session as in progress and then your webserver which handled it crashed leaving it locked in DB.
Important
In all of these options you must ensure that you obtain lock before reading the state and hold it until you update the state.
Please clarify what option is the closest to your case and I can provide more technical details.
Session is designed to store temporary user data among multiple requests, a good example is login-state... without session you would have to login to stackoverflow.com every time you open a new question... but the website remembers you, because your send them your session state inside a cookie. According to Microsoft:
The session data is backed by a cache and considered ephemeral data.
The site should continue to function without the session data.
Critical application data should be stored in the user database and
cached in session only as a performance optimization.
It is quite simple to implement a locking mechanism to solve your mutex issue, however the session itself is not a reliable storage and you may loose its content at any time.
How to identify duplicate payments?
The problem is you are getting multiple payment requests and you want to discard the duplicate ones... what's your definition of a duplicate payment?
Your current solution discard the second payment while a first one is in progress... let's say your payment takes 2 seconds to complete... what will happen if you receive the duplicate payment after 3 seconds?
Every reliable payment system includes a unique PaymentId in their request... what you need to do is to mark this PaymentId as processed in your DB. This way you won't process the same payment twice, no matter when the duplicate request arrives.
You can use a Unique Constraint on PaymentId to prevent duplicate payments:
public bool ProcessPayment(Payment payment) {
bool res = InsertIntoDb(payment);
if (res == false) {
return false; // <-- insert has failed because PaymentId is not unique
}
Process(payment);
return true;
}
Same example using lock:
public class SafePayment {
private static readonly Object lockObject = new Object();
public bool ProcessPayment(Payment payment) {
lock (lockObject) {
var duplicatePayment = ReadFromDb(payment.Id);
if (duplicatePayment != null) {
return false; // <-- duplicate
}
Process(payment);
WriteToDb(payment);
return true;
}
}
}
I'm hoping to finally get to the very bottom of an ongoing problem with Entity Framework DbContexts. The history of my problem is that sporadically - especially when requests come in in fast succession - my DbContext throws a variety of strange errors, including the following:
System.InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.
System.InvalidOperationException: Internal connection fatal error.
My MVC code is based around a basic pattern where I have a base controller, which looks like this:
public class BaseController : Controller
{
protected readonly DbContext db = new DbContext();
protected override void Dispose(bool Disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
All other controllers derive from this base controller, thereby making the DbContext available as necessary to controller actions, none of which are asynchronous. The only exception is my custom authorization, which also creates a DbContext upon access and is called with virtually every controller action (via attribute):
public class MyAuthorizeAttribute : AuthorizeAttribute
{
private DbContext db;
protected override bool IsAuthorized(HttpActionContext actionContext)
{
db = new DbContext();
var user =
db.Security.FirstOrDefault(u =>
u.Id == actionContext.ControllerContext.Request.Headers.First(h =>
h.Key == "Id").Value);
return (user != null);
}
}
I've also experimented with the following to no avail:
Removed all asynchronicity from my controller actions
Removed lazy loading from DbContext and inserted explicit Include statements with each call
Looking through StackOverflow, other people appear to have had similar issues:
Weird race conditions when I send high frequency requests to my datacontext
Random errors occur with per-request DbContext
Neither answers really helped me get to the bottom of the problem, but the OP-answer of the second SO post said ("After further investigation I found out that request processing thread sometimes steals DbContext from other thread"), but I'm not sure how this really applies.
Is there something fundamentally wrong with my design? Wrapping each controller action's DbContext into a using block can't be right, even though this blog says it is - but doesn't that cause other problems, such as returning objects that are no longer attached to a DbContext (and therefore lose change tracking)...?
when requests come in in fast succession
Made me think about a a problem about a year ago so I don't think it related to EF6. However, it took me quite some time to figure it out.
Allow your databse to have more than one pending request per application. Change your connection string to MultipleActiveResultSets=True
I'm trying to write an async page in asp .net which runs a function in a different thread.
The problem in the following code, is that when I debug it, the function EndAsyncOperation is never called. As a result, the page isn't fully loaded and loads for ever.
I use Action to run the code in a different thread from the thread pool. Is there maybe another way of running the code in a different thread that works?
Where am I going wrong?
And another question. I read that in ASP .Net the pages are ran with a threadpool. So howcome when I debug my site and try to load a few pages together they are loaded one after another syncronously?
public partial class AsyncPage : System.Web.UI.Page
{
void Page_Load(object sender, EventArgs e)
{
AddOnPreRenderCompleteAsync(
new BeginEventHandler(BeginAsyncOperation),
new EndEventHandler(EndAsyncOperation)
);
}
IAsyncResult BeginAsyncOperation(object sender, EventArgs e,
AsyncCallback cb, object state)
{
Action action = () =>
{
Start();
};
IAsyncResult asyncResult = action.BeginInvoke(new AsyncCallback(action.EndInvoke), null);
return asyncResult;
}
void EndAsyncOperation(IAsyncResult ar)
{
// This function isn't reached
}
public void Start()
{
// Do something
}
}
I believe that you need to pass the AsyncCallback object provided to you in the BeginAsyncOperation method parameters to BeginInvoke, not create a new one.
Your pages are loading synchronously because of your session configuration.
Access to ASP.NET session state is exclusive per session, which means that if two different users make concurrent requests, access to each separate session is granted concurrently. However, if two concurrent requests are made for the same session (by using the same SessionID value), the first request gets exclusive access to the session information. The second request executes only after the first request is finished. (The second session can also get access if the exclusive lock on the information is freed because the first request exceeds the lock time-out.) If the EnableSessionState value in the # Page directive is set to ReadOnly, a request for the read-only session information does not result in an exclusive lock on the session data. However, read-only requests for session data might still have to wait for a lock set by a read-write request for session data to clear.
Source: ASP.NET Session State Overview, my highlight.
Is this a nice way to use the LINQ context during one http request? In almost every request i have some selects from the database and some inserts/updates. It seams to work but I dont know how this will work with heavy traffic to the servers and on load balanced servers, anyone have any opinions/ideas about this way to keep the Context during the entire lifespan of the Request?
public static AccountingDataContext Accounting
{
get
{
if (!HttpContext.Current.Items.Contains("AccountingDataContext"))
{
HttpContext.Current.Items.Add("AccountingDataContext", new AccountingDataContext(ConfigurationManager.ConnectionStrings["SQLServer.Accounting"].ConnectionString));
}
return HttpContext.Current.Items["AccountingDataContext"] as AccountingDataContext;
}
}
This is a generally good idea on some levels. But you probably want to push instantiation back from the Begin_Request event. With the integrated pipeline, you will be initializing a rather expensive DB Context for every single request to your site. Including favicon.ico, all your stylesheets and all your images.
Best, simple implementation of something that only instantiates it when something asks for the context is Ayende's example for NHibernate's ISession; you can just replace it with the appropriate bits to instantiate your L2S context.
I'm using Unity for dependency injection, but the idea is the same:
protected void Application_BeginRequest() {
var childContainer = this.Container.CreateChildContainer();
HttpContext.Current.Items["container"] = childContainer;
this.ControllerFactory.RegisterTypes(childContainer);
}
protected void Application_EndRequest() {
var container = HttpContext.Current.Items["container"] as IUnityContainer;
if (container != null) {
container.Dispose();
}
}
The container is responsible for setting up a number of things, one of which is the data context. Works like a charm. I haven't done load balancing, but can't imagine you'd run into issues there either. The request gets its own context, which is wrapping a single user connecting to a database. No different that using old school ADO .NET for data access.
I am trying to show a customer errors page in ASP.NET when the database is down. I use the SQL Server mode to hold the session data. The problem is that the custom errors page is never called.
Since the session data and the database are on the same server, this does not redirect to the custom error page? I’m guessing the web application has not loaded at this point?. The user is presented with the stack trace for the session state connection failure.
It seems that we need something that sits in front of the initial website load to check connectivity to the database. Any ideas on how to implement this?
Add something like this to your web.config?
<customErrors mode="RemoteOnly" defaultRedirect="GenericErrorPage.htm">
<error statusCode="403" redirect="NoAccess.htm" />
<error statusCode="404" redirect="FileNotFound.htm" />
</customErrors>
You can read more information here
If it is your SqlSessionState that is failing, you should handle the corresponding error in the Application_Error event in Global.asax
You can read more information here
I believe the error is coming from the fact that because you're using an out of memory session state provider (being the database), and the database connection has a failure, then there is actually a perceived error in the web configuration (not in the application). I have a similar problem, where I'm using AppFabric Cache for my session state provider, but when the AppFabric Cache Service is down, I get the Configuration Error page.
Because of this, you can't use the customErrors solution as FlyingStreudel has already suggested, since it isn't an error in your application, but rather in the loading of the configuration.
I've looked around for a way to solve this, but couldn't find any. I hope this question gets answered, it's got me confused already with the various error configuration options...
Update: After investigating this for a while now, it appears that my issue comes from the fact that the SessionStateModule causes the AppFabric cache session state provider to try and connect to the DataCache (which is not available), and an exception (probably timeout) is thrown somewhere. Because this happens in the Init of the HTTP module, there seems to be no way around the yellow screen of death.
I wouldn't be surprised if the original poster's problem is the same - the connection to the SQL server occurring in the initialization of the SessionStateModule.
Because the error page is an ASP.NET Page ( I can see this from your comment), It will hit the session database in page life cycle.
You have to set below directive on Error.aspx Page to tell ASP.Net not to load session information for it:
EnableSessionState="false"
Please note this will only work if you are not using any session information in the error page.
Additionally, I also managed Global.asax page as below :
private static Exception startException;
protected void Application_Start()
{
try
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ModelBinders.Binders.Add(typeof(DateTime), new MyDateTimeModelBinder());
}
catch (Exception ex)
{
startException = ex;
}
}
static HashSet<string> allowedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".js", ".css", ".png",".jpg",".jpeg",".gif",".bmp"
};
public bool IsStaticFile(HttpRequest request)
{ //My project was a mix of asp.net web forms & mvc so had to write this
if (Request.RawUrl.ToLower().Contains("/bundles/") || Request.RawUrl.ToLower().Contains("/frontendcss?") ||
Request.RawUrl.ToLower().Contains("/fonts/")
//these are my css & js bundles. a bit of hack but works for me.
)
{
return true;
}
string fileOnDisk = request.PhysicalPath;
if (string.IsNullOrEmpty(fileOnDisk))
{
return false;
}
string extension = Path.GetExtension(fileOnDisk).ToLower();
return allowedExtensions.Contains(extension);
}
protected void Application_BeginRequest()
{
if (startException != null && Request.RawUrl.ToLower() == "/Error.aspx".ToLower())
{
return;
}
if (startException != null && IsStaticFile(Request))
{
return;
}
if (startException != null && Request.RawUrl.ToLower()!= "/Error.aspx".ToLower())
{
//Response.Redirect("Error.aspx");
Server.Transfer("Error.aspx");
this.Context.AddError(startException);
return;
}
}
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
Response.Clear();
Server.ClearError();
Server.Transfer("Error.aspx");
}