WebSessions still won't die (sometimes!)

2014r2.1
Further to previous similar thread, I am still having an odd issue with Sessions not closing when user navigates away from webapp.

I have gone through my code, and to the best of my knowledge, I have no circular references.

When the cgi app is first run, this is what happens:-
At first, every session that is created, correctly ends with a Session.Close event. Until …

If the website sees a lull in traffic (ie NO sessions are created for a period of time - seems to be about 30mins or more), then …

The next user session to be created after the lull will never see its Close event fire, and ALL subsequent sessions will also never fire a Close event.

If I manually reset the cgi App (by quitting & restarting it), the above process will repeat.

This is incredibly irritating as I do not see this issue with Standalone builds, only cgi ones.
Any ideas how I can further analyze this app behaviour to work out how to fix it?

During that idle time, are there any active sessions?

To my knowledge, no, there would be no active sessions at that time.

Today’s session log is confusing, however, and I am not seeing a correlation of idle time-to-failure. For example:-
Log starts at 08:41 (immediately following a manual re-start).
The Close event works perfectly for 9 sessions up until 10:22 when the last active session closed correctly.
The app was only idle until 10:26
The next session to open at 10:26 failed to close, and so do next 29 sessions… Until 13:26, when, strangely, the Close event starts working again. (I did not manually restart the app - maybe it crashed and restarted itself??)
The Close event then works correctly for the next 6 Sessions up until 14:04
The next session (at 14:11) then fails to run a Close event, … and so on.

Yesterday I saw large idle times (I guess it was Sunday!) between success and failure sessions.
Also cannot see any common user action that appears near a “failure” session - so it seems very hard to reproduce at will.
Very odd.

I’m sorry to be a pain - but just frustrated with the thought that there must still be a bug in my code - but I can’t identify it - Grrr!

Since you seem to be logging sessions, how about logging when App.Open fires.

Also, I’d be curious to know whether you have:

App.AutoQuit = False

Anywhere in your project. You shouldn’t if you’re running as CGI unless you’re absolutely sure everything else is working properly.

No sign of App.AutoQuit anywhere in my code.

The only interaction I have with the App object is App.Quit which is called by a timer event whenever I remove the existence of the appname.cgi file to manually kill the app.

Logging App.Open might be interesting…
Thanks.

Greg,
I have implemented App.Open and App.Close logging.

Today’s log is interesting…
The day starts well, we see regular App.Open, followed by various Session.Open and .Close events, and App.Close whenever there is nil active sessions - all as expected.

At 11:30 we see a normal App.Open and Session.Open events, however, we then see another Session open at 11:30 which fails to execute a Close event.
From this point, we see a series of Sessions with no Close events. After 11:36 there is a lull of activity until 12:18 when all sessions “should” have been dead, but they are not, and there is no App.Close event.

This abnormal situation continues until 19:08, when we see an App.Open event (note that previous App.Open was at 11:30 and NO App.Close was reported between these two times.)

From 19:08 we see a session Open, and then Close at 19:12
At 19:12 we see App.Close
At 19:26 we see App.Open but NO Session is Opened until 19:41 - very strange? Why did the App Open?
The Session that opened at 19:41 then fails to Close. All further Sessions also failed to close, and no App.Close reported up to the time of writing this report (21:49).

The app will open for any request, whether or not it’s going to open a session.

We still don’t have enough information to tell what’s going on with the Sessions in my opinion. It’d still be helpful to have more logging, specifically the path that users are taking through your app. It may help you figure out what’s triggering the problem.

@Tony Davies
I’ve got an idea about how we might be able to figure out what’s going on.

  1. Please download the following file: SessionCheck
  2. Import the class into your project
  3. In Session.Open, insert the following code: SessionCheck.Run()

One way or another, this helps me narrow down if this problem lies in our framework, and if it does, where the problem might be.

Hi Greg,
OK SessionCheck is installed and running. . .

Today’s log is curious.
Several App.Open & Close events, but Session.Close only functioned correctly for about 3hrs between 14:11 & 17:04

The rest of the day saw no Session.Close events working.

Just out of curiosity, does the memory usage of the app continue to rise while these sessions are “not” closing properly? Or is it possible that the Close event simply is not firing.

Also, I notice that when the Session.Close “failure” is happening, we are not seeing any App.Close events too.

Hopefully Phillip will help to answer the memory usage question…

Well, that would indicates a circular reference somewhere in the code, either yours or ours. I reviewed our web framework code changes for 2014r2, and I don’t see anything specific that might have changed this, but since we have received no other reports of this yet, I can’t even begin to test it without seeing your source code.

Yipee!! Seems like I have solved this issue (Thanks Greg for your assistance).

So now all client sessions action their Close Event whenever the user closes their browser or navigates away.

What was the solution?
Well my Session object contained Properties which were references to SQLiteDatabases and various Recordsets. The Session.Open event contained code which cast object references to those Properties. (The reason for this technique was partly laziness and convenience on my part, because I wanted certain databases and some records from those databases to be easily accessible to any Method in any WebPage). My Session.Close event called .close on those Properties (but it seems that did not destroy those object connections, so the Close event was not allowed to trigger).
The solution was to remove ALL of the Session Properties which referred to database and recordset objects. Then re-code all the relevant Methods throughout the project to Create-and-then-Close each object as and when needed - thereby scoping those data objects locally within the calling Method.
This sounds like a big job on a complex project - but in reality it was extremely simple. Instead of referring to a Session Property to retrieve the data, it simply means calling a Session Method which returns the data object, so most code stays the same, with an added line at the end of each Method which Closes the object.
Problem solved!! (Nearly…)

Greg,
My application log is now showing an occasional Session.Close event which appears to be a “phantom” session; ie the Session ID shows no Open event, no user events - just a lone Close event! Also I am seeing an occasional App.Open event with no preceding App.Close event, which might suggest that something is crashing the App?

I have had this problem off and on for a while and I need to better understand the solution.

I am using 2014R2.1. Like Tony described, everything works OK for a while then sessions get abandoned and “stuck” and do not die normally and they cannot be killed explicitly.

QUESTION 1
Currently I do all of my DB Opens in the Session.Open event. Is that a “Bad Thing”? That also means every DB reference begins with “Session.”.

QUESTION 2
Should I be Opening the DB with each needed call and closing it at the completion of that call by using a method and properties in the Webpage/Dialog needing access? Does that method, described above by Tony, slow down the app in general with lots of opens and closes?

I thought I understood scope in Web Apps but I am not 100% sure now. I do understand that Properties used in a Session are in scope only for that session. I have used Properties created in APP to track login by the same user to multiple Sessions (and disallow that to happen).

QUESTION 3
Where I get “fuzzy” is in the creation of Public Shared Properties within a Class. Are these Properties in scope across all sessions or just the opened session?

QUESTION 4
To avoid repeated DB Opening and Closing would it be possible to open them once when a session launches in the Open Event of the first paged launched (like a login page) with a DB Property that is in scope for all Web Pages/Dialogs in that Session? Would those properties be declared in that first page (login page)?

Thanks.

Good questions Mark! I have re-read the Xojo documentation several times, and it does seem difficult to quite pin down the exact “do’s & don’ts”.

I do now have a cgi web app (using Sqlite DB) running on Apache server which has now been up 24/7 since early September, and all Sessions now close reliably. “Exactly” what I did to fix it still remains a bit of a mystery!

Like you, I originally built the DB connect routines into the Session.Open event, and stored the DB objects and Recordset objects as Properties of Session, so that they would remain in scope throughout the Session. I also stored a SMTPSecureSocket object as a Session Property.

Anyway, I have now redesigned the app, so that Session does not contain any properties other than String and Integer variables.
What I have done now, is to create a Funtion (stored as a Method in Session called, let’s say, “DBConnect”) which returns a connected DB object to any calling Method (located in any WebPage or WebContainer). So, whenever a Read or Write to a DB is needed, anywhere in the app, I create a Method in that container that starts with a line like this:-

Dim myDB as SQLiteDatabase = Session.DBConnect

Sometimes I keep record pointers as String or Integer variables within the Session object, so that I can simulate “Find previous / next” records, or to “re-find” the current record prior to updating it, for example.

Once record processing has been completed, I check myDB for any errors, and finally I close the recordset object and close the DB object. I stick religiously to this format in every Method that needs DB access.

I now use similar technique for SMTPSecureSocket.

Runtime speed seems perfectly fast, with Record updates, or Recordset retrievals almost instant, with the server in Holland and the browser in Cyprus.

One other thing that I found caused me trouble with stuck sessions, was storing a reference to a web object as a property in another object. For example, I had a WebDialogue with a Yes button and a No button. The Dialogue had a property called “ButtonClicked” and the type of property was WebButton. In each button’s Action event my code was like:-

ButtonClicked = Me Self.Close
The Dialogue’s Dismissed Event had code like:-

If ButtonClicked.Name = "cmdYes" then do something.... Else do something else End If

However, I subsequently changed this arrangement, and the ButtonClicked property is now just a String, NOT a WebButton.
The Button’s Action event was changed like:-

ButtonClicked = Me.Name Self.Close

I have no real idea, which of the above cured my “stuck sessions” - but I did not see totally trouble-free operation until I had implemented ALL of the above.

It would be really helpful if Xojo could produce some kind of “Best Practice” document specifically aimed at cgi WebApps design.

Tony,

Thanks for the reply.

Before I do much reconstruction of several large programs I wanted to make sure the effort would solve the problem. From what you have said there was not one single thing you specifically found that resolved the “stuck” session issue.

My temporary fix is to kill the running EXE (I am on a Windows server) each night at 3am. I have also added some code to track the last time each Webpage is touched in a session by each user (not by session but an array property in the APP context by user name). Previously my logic said if you had a session open you could not login again. I am now using this last activity in my login code and in the event you try to login and it appears there is an old “stuck” session with no recent activity I will allow you to login again. The logic has not been installed long enough to know if it helps yet. This is not a root cause fix but it should buy me some time to restructure things.

I have some button object references like you mentioned for your YES/NO message box. First I want to clean up that type of code. I did not like my solution but at the time I did not see a better way. Think now I can handle this differently with out any object references. If this does not solve the issue then eventually I will probably move things other than simple variables out of the session context.

If I find anything definitive I will post here for others to find.

Thanks,

Mark

[quote=143813:@Mark Strickland]QUESTION 2
Should I be Opening the DB with each needed call and closing it at the completion of that call by using a method and properties in the Webpage/Dialog needing access? Does that method, described above by Tony, slow down the app in general with lots of opens and closes?

I thought I understood scope in Web Apps but I am not 100% sure now. I do understand that Properties used in a Session are in scope only for that session. I have used Properties created in APP to track login by the same user to multiple Sessions (and disallow that to happen).

[/quote]

While it’s technically possible to get properties from a different session in a current session, you would need to loop through and find the ‘other’ session you were looking for. I often create a class called “ParentSession” and then each session has a parent session property. The parent session goes out of scope and cleans up once there aren’t any remaining (child/regular) sessions pointing to it. Having a parent session is nice for when you want to allow a login and multiple tabs to be open and share information - otherwise its one tab/session.

The technique used to prevent memory leaks in web applications is no different than any other application except that it’s a lot more obvious with a web app because they typically run for so long.

The trick is that when dealing with anything that is not an intrinsic data type, which means basically anything that can be set to Nil, you ned to be careful not to create two objects that can somehow point to one another. For instance… The app keeps a reference to all of the running Sessions. Every Session has a reference to all of the Web Pages that are currently in existence for that session. Every WebPage has a reference to the control instances contained within it. Imagine for a moment that you were to create a subclass of WebLabel which has a property “MySession as WebSession” which was set with the following code in the Open event:

MySession = Session()

Herein lies the problem. This is a circular reference.

App -> Session -> WebPage -> CustomWebLabel1 -> Session

When it’s time for a Session to die, we remove the reference to the Session from the App class. Without the connection between the CustomWebLabel and the Session, the Session’s destructor fires, which closes and destroys all of its webPages, which in turn closes and destroys each of the control instances. This process is totally dependent on there not being any extraneous references to each of the objects from somewhere else. In the case of my example above, where the control instance holds a reference to the Session, it actually prevents the Session’s destructor from firing, which in turn prevents the WebPages from being destroyed, and so on.

THIS is the scenario that most people run into in the projects I’ve examined and the remedy is actually quite simple. You just need to be aware of it and choose the right places to implement it.

In this case, it’s solved by making the MySession property on the CustomWebLabel into a weak reference. Basically, instead of pointing to and holding onto the object, it’s gives you a way to point to the object without holding onto it.

I usually use a computed property for these things so I can continue to use them as objects with some try-catch clauses. Like this:

[code]Private mMySession as WeakRef

Setter:
MySession(value as WebSession)
If value = nil then
mMySession = Nil
Else
mMySession = New WeakRef(value)
End If

Getter:
MySession as WebSession
If mMySession=Nil or mMySession.Value = Nil then
Return Nil
Else
Return WebSession(mMySession.value)
End If[/code]

Now, in every case where you use MySession in code, you’ll need to check for nil or wrap it in a try-catch. There will always be the possibility that the Session is in the middle of being destroyed when your code runs, and so the getter will return Nil.

Don’t get me wrong, this is not an easy concept to wrap your head around. It is also important that you not have too many WeakRefs which can cause objects to get destroyed too early, right out from under your nose. As a matter of fact, i’m running into this right now with some new code in r3. Just be careful, and do lots of testing.