Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Best practices on closing vfp app on inactivity 3

Status
Not open for further replies.

cfsjohn

Programmer
Sep 1, 2016
66
US
First, Let me say it has taken 10 years to get back to where I could post on this forum. Before then I was a regular poster (more questions that help). Not sure if anyone is still here that used to post regularly and that helped me over the years but if they are I want to first say Thank You. For 30+ years now I have an app that I developed and maintain. At some point many years ago I made an attempt to shutdown my app when users stay logged in for hours and days. I really need to reindex and things like that overnight. I do have users legitimately in the system 24/7. I am revisiting this issue. My question has to do with an overall concept. I have decided in order to close my app on inactivity, it really has to be context sensitive, in other words, it depends what the user is doing. For example, a user may be adding a record in one of the data entry screens. If that is the case, I would need to release the views/cursors, etc that are in use at that time and THEN quit the app. At the same time, that makes doing the shutdown a lot more complex because my app has over 100 tables and 1200 views. I am asking if I am making this too complex? All thoughts appreciated.
 
First, welcome back. I don't remember you as regular, though I am also here since 2004.

Code:
QUIT

It can be as simple as that, as that doesn't do, what people think it does and quit right away. Quit causes the destroy of any object, which means every object can tidy up itself and its resources. In regard of forms, Quit causes QueryUnload. within that method you can find out from THISFORM.ReleaseType, that the unload is queries by Foxpro shutting down. Which either means a system shutdown or a QUIT happened. In both cases you can have a specific auotmatic reaction differing from a normal interactive or programmatic release of a form.

Because of the nature of system shutdowns to enforce processes quitting, even though that may never happen automatically in your case, it is best practice to program defensive and have automatic rollbacks. Think alone about automatic restarts after automatic windows updates.

In one or two aspects VFP has some automatic behavior anyway. In regard of remote data there is the DisconnectRollback setting of SQLSETPROP to specify, what happens with ongoing transactions when disconnecting. Likewise closing a table as a to be destroyed datasession must do, will normally revert buffered changes. I'm not 100% sure, but if the VFP team had decided for autoupdate/commit intead of autrever/trollback, they would risk FoxPro sessions to get into conflicts and thus failing to end the datasession.

Filehandles are released on their own and databases and any involved files are closed anyway, with or without your intervention. And this happens thoroughly and stable, unless the reason for the runtime shutown is a fatal error (C5), which is not the case with a QUIT.

Also, QUIT does trigger what you define via ON SHUTDOWN, and as already stated while explaining the ReleaseType property of forms, that also is triggered by a system shutdown.

Simply make a few experiments especially about what happens to buffered changes.

There are reasons to "manually" let your code close single tables and in case errors are triggered log them, as such things would happen unnoticed when you jus rely on automatic tidying up of the VFP runtime. But it doesn't pay to have a general shutdown code acting on all currently open forms and memory variables. It is good practice to QUIT to let each object and form act on its own behalf and then only be concerned about some very general things like ASSESSIONS telling you about any data sessions, TXNLEVEL telling about any uncommitted transactions etc. One of the pains is message boxes without a timeout. As they are not VFP forms, they do not have a QueryUnload happening, of course. Same goes for the legacy native report preview AFAIK. So there are a few bummers to take care for, but very broadly you still can use QUIT because of the ON SHUTDOWN procedure you can define, that is your resort for such things. I have to lie about it, but I think whatever you define to happen ON SHUTDOWN will happen after all single forms and object should already be terminated. It's easy enough to test. If I am right, any remaining forms then indicate something has dangling references or similar nasty bugs.

Bye, Olaf.
 
Another topic is how to decide about inactivity. As I understand your question, this is not your major topic, you have ideas about that. A typical solution is an inactivity timer resetting whenever there is something you call activity. Not limited to keyboard and mouse events.

Bye, Olaf.
 
Hi and welcome back. I'm sorry I don't remember you, but then I often don't remember people I met last week.

You say that you need the solution to be context-sensitive. If a user is in the middle of an edit, you need to recognise that fact. You can't simply kill their instance of the application, because that would cause them to lose data.

In that case, what you really need is to broadcast some sort of warning. Tell all logged-in users that you are going to close the system in so-many minutes, and they had better log out before then or else (of course, you will choose suitably diplomatic language).

To make that happen, each instance of the app would have a timer that fires every, say, 60 seconds. The timer checks for the presence of some sort of "signal". The signal could be a flag within a table, or it could be the presence of a certain file in a given directory. The point is that you (the administrator) creates the signal. The individual timers check to see if it exists. If it does, the warning message is displayed on the user's screen. At that point, another timer is set, with an interval equal to the length of the warning period. When that timer fires, you shut down the user's instance.

At the point of shutting down, you can't simply issue a QUIT. You have to worry about those unsaved edits, etc. Also, when you issue a QUIT, VFP will systematically close each of the open forms, firing all the code in the various Destroy, QueryUnload, etc. methods. Any of that code could interfere with the shutdown process. For example, it might simply ask the user if he is sure he wants to quit, in which case he might well answer no.

To deal with that, what you need to do is to first revert all unsaved buffers and rollback all incomplete transactions. Then issue these commands:

Code:
DECLARE Integer ExitProcess IN WIN32API 
ExitProcess()

That will kill the application stone dead - no arguments.

Of course, there are many other issues. What do you do about a user who is performing some critical operation which cannot be interrupted? What happens if the administrator changes his mind? - you need a mechanism for cancelling the shutdown warning. What if a user logs in after you have issued the warning but before you have shut down? Or if a user tries to log in after you have shut down but before you have completed the task that requires exclusive access? Those issues all have to be dealt with, but none of them affects the overall concept.

Mike



__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
OK, so now you have two opposing recommendations.

Mike argues QUIT is unfortunate, as any code could hinder the shutdown. I argue you have to program for the case and don't hinder but support the shutdown with your code. ExitProcess is a fallback to me, that should only be done as a last resort solution.

The situation of a user in an edit is not something complicated, as that simply means your QueryUnload code will depend on ReleaseType and in the normal case ask the user, whether to cancel changes or stay, while a shutdown will cause code to automatically rollback/revert and let the shutdown proceed.

The idea to warn users is a good one, so they can cooperate, but the main issue is users leaving open an application after the end of work. In these cases, such warnings go unread anyway. But it wouldn't matter, if they also just are having a timeout, this just means a delay of the shutdown. You can't do so in case Windows decides to shutdown, after an update, though you can roughly define windows update behaviour.

I would still go for QUIT and just ensure no code you do interferes with an automatic shutdown and causes any questions and dialogs when form.ReleaseType is 2.

A compromise is to start a timer doing an EXITPROCESS after a longer interval. Then QUIT and give the normal unloading and destroy mechanisms and code a chance to quit before the timer event calls the ExitProcess to enforce it.

Bye, Olaf.
 
Olaf and Mike, I can see good advice in both your posts and I thank you for both.

I do believe both your posts tend to make me believe that Yes, it does depend on what the user is doing, at least to some extent. I've decided to greatly limit use of this shutting down process. It will only take place in a couple of forms that are specifically used by non administrative personnel (my least literate users) to perform a specific task during "non business hours". That will make accomplishing this a lot easier. I am going to do some testing with the Quit command. I was using KEYBOARD'(ALT+F4)'. I want to see if QUIT does some of the work for me with regard to shutting down these particular forms gracefully.

I do have a lot of messagebox's and until now I have not used the timeout setting. I am going to look into how I might make use of that as well. These are mostly modal forms and the aforementioned keyboard shortcut will not work on modal forms and on messageboxes. I will post my findings.

Thanks again,
John
 
Yes, we've had this discussion about KillProcess before. I agree that you should give the user the opportunity to cleanly close down first. KillProcess mainly covers the case where the user had gone home and left the application running, or for some reason simply refuses to close the application properly.

By all means take steps to ensure that your code won't interfere with a QUIT-triggered shutdown. But it's complicated. Just one issue that comes to mind is if a modal form is active when QUIT is executed. That would normally give rise to a "Cannot quit Visual FoxPro" message. True, you could program round that. But there are probably several such cases, and the relevant code could be quite far-reaching.

We haven't touched on the issue of detecting a period of inactivity. I've never found a satisfactory way of doing that. That's why I favour the idea of broadcasting a warning message. But that's just one approach that can be taken.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Modal forms are a showstopper, too. At least normal modal forms also get the QueryUnload event triggered and if you don't do anything to block the unload the modal forms do unload.

Adding a timeout to messageboxes also only is half a solution. You then still hope the shutdown is not triggered when a messagebox is currently displayed, you hope any last messagebox a user left open and went away is already gone by timeout, when you do the QUIT by some external signal. That also makes a pre shutdown phase useful, in which you first only announce to want to shutdown in say 5 minutes. Such additional messagex could come from a timer starting an alert form when the timer event finds a shutdown signal like a boolean value in a table or a file existing.

Bye, Olaf.

 
Mike, I think it's just a matter of discipline to have QueryUnload code in your base form and specialize it in case you need to in derived classes. Same goes for any other class destroy.

Also simply have ON SHUTDOWN QUIT and also modal forms will not cause the "Foxpro Cannot Quit" message. Indeed whatever you define as ON SHUTDOWN action has priority over this native behaviour and I now think after experimenting happens before any form and objects are unloaded, released, and destroyed.

Bye, Olaf.
 
Here a demonstration about QUIT with modal forms:

Code:
ON KEY LABEL F4 goForm.Release()
ON KEY LABEL F5 QUIT
ON SHUTDOWN QUIT

PUBLIC goForm
goForm = CREATEOBJECT("modform")
goForm.Show(1)

DEFINE CLASS modform as Form
   WindowType =  1
   
   PROCEDURE QueryUnload()
      DO Case
         CASE thisform.ReleaseType = 1
            MESSAGEBOX("you tried interactive closing (you clicked close [X]) Not allowed")
            NODEFAULT
         CASE thisform.ReleaseType = 2
            This.Closable = .T.
            MESSAGEBOX("shutdown (you pressed F5)")
      ENDCASE
   ENDPROC
   
   PROCEDURE Release()
       MESSAGEBOX("form release (you pressed F4), ReleaseType is "+TRANSFORM(thisform.ReleaseType))
   ENDPROC 
ENDDEFINE

Notice 1: ReleaseType 0 does not cause a QueryUnload, so form.Release() circumvents QueryUnload. You could call QueryUnload from the Release method to have one place of reacting to form closing requests and add the CASE ThisForm.ReleaseType = 0.

Notice 2: This demos, that you can also reject the closing via NODEFAULT. You can of course also prevent closing by setting form.Closable = .F., so the [X] close button in the title bar is disabled.
That also is the reason I set Closable = .T. to not have anything in the way of quitting the form.

Notice 3: If you comment out ON SHUTDOWN you get the "Foxpro cannot quit" message Mike warns about. So defining what happens ON SHUTDOWN is solving that.

Notice 4: pressing F5 causes QUIT, which in turn causes the ON SHUTDOWN definition to come into effect. So doing QUIT is causing the same events within the VFP session as quitting VFP by closing the _SCREEN and also the same events, a computer shutdown triggers in VFP. Though this is not two-way, i.e. a QUIT does not shut down the PC, of course. It only shuts down the VFP session.

Now you can add more cleanup code as you like in each case. The behavior surely should differ in case of a click of [X] to show warning messages for unsaved changes, while you want to automatically revert them in the case of ReleaseType 2.

In case you want to react with more cleanup code in ON SHUTDOWN notice I found out this happens first, see via this sample usage:

Code:
ON KEY LABEL F4 goForm.Release()
ON KEY LABEL F5 QUIT
ON SHUTDOWN myQUIT()

PUBLIC goForm
goForm = CREATEOBJECT("modform")
goForm.Show(1)

DEFINE CLASS modform as Form
   WindowType =  1
   
   PROCEDURE QueryUnload()
      DO Case
         CASE thisform.ReleaseType = 1
            MESSAGEBOX("you tried interactive closing (you clicked close [X]) Not allowed")
            NODEFAULT
         CASE thisform.ReleaseType = 2
            This.Closable = .T.
            MESSAGEBOX("shutdown (you pressed F5)")
      ENDCASE
   ENDPROC
   
   PROCEDURE Release()
       MESSAGEBOX("form release (you pressed F4), ReleaseType is "+TRANSFORM(thisform.ReleaseType))
   ENDPROC 
ENDDEFINE

PROCEDURE myQUIT()
    MESSAGEBOX("myQuit was triggered")
    QUIT
ENDPROC

Press F5 and the "myQuit was triggered" message is displayed first, then the message from the form QueryUnload. Also, try out to shut down your computer as a final test.

I don't just recommend ending your own myQUIT procedure with the QUIT command. The shutdown procedure stops and the application does not QUIT if you don't call QUIT again. That also means doing QUIT in the routine initially triggered by QUIT or by any other shutdown event trigger is, fortunately, not recursive, so doing QUIT inside myQUIT won't trigger myQUIT again and cause an endless loop. That's also why ON SHUTDOWN QUIT in the simplest case works and does not cause endless calls of QUIT itself. It's worth mentioning in that detail level, as QUIT really triggers ON SHUTDOWN and still can be used as the reaction to the shutdown in the simplest case, to make it circumvent VFPs native "Cannot Quit" message.

Last, not least: The ON KEY definition, of course, is not, what you use in your application, but a timer eventually doing a QUIT, when it detects a reason/signal to quit and after it alerted about the upcoming shutdown.

Bye, Olaf.
 
One further idea:

If only some forms hinder you to administratively open tables exclusive, you will not need to trigger the whole application shutdown by a QUIT but only release certain forms. For that use case a form handler is good, in which you (by definition of the form handler meaning) define a method to start forms and keep form references and related metadata like form class and type and also define a method to close certain form types based on that metadata you maintain about running forms.´QueryUnload and he ReleaseType still comes in handy in these cases, too, to differentiate between normal users closing forms interactively and your shutdown timer acting programmatically.

Bye, Olaf.
 
Olaf,

One more thing:
Suppose I have an errorlogging procedure which will show the current error (modal form) with an option to either 1)read the error or 2)mail the error or 3)ignore the error and continue to work.
However if the error is a recursive one, user would like to quit the application and clicks the (X).
What do you suppose to code in the QueryUnload?
Calling the normal EndApplication.prg viz
Code:
If !Version(2) = 2  && development mode
clear events
	Quit
Else
	clear events
Endif
will not work

Regards,
Jockey2
 
I showed how you can act within a modal form, that's applicable for any modal form except a Messagebox, in which you can't code. Have you put the line ON SHUTDOWN QUIT?

Besides that, in your case the X is not closing the app, it is closing the form. It is questionable to continue running any code after an error, so that's more a question about how to handle errors in general.

If you want the X of a form to quit the app, you have to have a QueryUnload CASE thisform.ReleaseType = 1 with a Thisform.Release and put CLEAR EVENTS and QUIT into the Release, perhaps. Play around with the idea to close the error form via Release. In the worst case the QUIT would recall into QueryUnload, but that time with ReleaseType=2, in this case, you know the QUIT already was done and you should only not hinder the form to close. You may close tables, unbind controls act on any special settings of ActiveX not automatically handled by the VFP runtime shutdown, eg close still open OLPOSPrinter sessions, though that could also be programmed into the destroy event of the ole control you may have done as VFP wrapper or within the methods and events VFP even provides as part of the native ActiveX controls.

The main idea is every form and class acts for itself. Encapsulation.

Bye, Olaf.
 
Also notice: The sample QueryUnload code is not meant to be uased as is 1:1 for copy&paste and use as base form code. It is giving examples of how you could react.

Code:
         CASE thisform.ReleaseType = 1
            MESSAGEBOX("you tried interactive closing (you clicked close [X]) Not allowed")
            NODEFAULT

You don't have to disallow closing the form with X. I just show how that is possible with NODEFAULT. So this is just documenting how to act within QueryUnload to prevent the form form closing. It is easier to do this by setting Closable=.f. anyway. I hinted on this, already.

The major bullet points are:

1. QUIT is the core exit, but not sufficient alone, every aspect of your application has to play nice, which makes ExitProcess the easier quit, but it is a KILL, which does not enable you to log some diagnostic things, for example. Besides I already hinted you could trigger an ExitProcess just before QUIT via a Timer running in 5 Seconds. If it ever gets to its Timer event, before the process exits, you kill what is left over.
2. ON SHUTDOWN QUIT is enough to already get rid of "Visual FoxPro cannot close"
3. QUIT causes objects destroy in general. Within forms it triggers QueryUnload with ReleaseType=2, that must be automatic non-blocking behavior. Eg an error message form would pick to log the error and exit. Mail the log on next startup, when you have a clean situation.

Bye, Olaf.
 
Suppose I have an errorlogging procedure which will show the current error (modal form) with an option to either 1)read the error or 2)mail the error or 3)ignore the error and continue to work.

Jockey,

In my opinion, that is not a good approach to error-handling. You should never give the user any choice as to how to handle an error. The user simply isn't equipped to make the choice.

In particular, letting the user ignore the error and continuing to work is a very bad idea. Once an error has been triggered, you have to assume that the environment is no longer stable. You can't assume that any particular variables are set correctly, or that any tables are still open, or any settings still apply. Allowing the user to ignore the error will usually result in just more errors - or, worse, that the application will produce incorrect results without further errors being reported.

It's much better not to give the user a choice. Simply notify the user that an error has occurred, then log the error, then close down.

Of course, I'm assuming that this is happening in a production environment. When working in the development environment, the above does not apply. Also, I'm assuming that we are talking about "real" errors - those that are beyond the user's control. If the situation is something the user can do something about - for example, waiting for another user to release a lock - then that should not be treated as a fatal error and the correct action is to give the user the option to wait and retry. But the vast majority of errors should be treated as fatal.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top