User interruption handling
Allow the end user to cancel the execution of a procedure in the program.
When do we need interruption handling?
If the program executes an interactive instruction, the GUI front end can send action events based on user actions. When the program performs a long process like a loop, a report, or a database query, the front end has no control. You might want to permit the user to stop a long-running process in the such case.
Detecting user interruptions in programs
BUTTON sb: interrupt, TEXT="Stop";
When the runtime system takes control to process program code or execute a long running SQL query, the front end automatically enables the local 'interrupt' action to let the user send an asynchronous interruption request to the program.
A program (i.e. the runtime system) can also receive a SIGINT interruption signal from the operating system. The interruption request that comes from the front-end is a different source. However, the runtime system handles both type of interruption events the same way.
When receiving an interrupt event from the front-end with an 'interrupt' special action, or
from the system (SIGINT) the runtime system sets the INT_FLAG register to TRUE
.
DEFER INTERRUPT
and test the INT_FLAG register to properly
handle user interruptions, and avoid immediate program termination: If the DEFER
INTERRUPT
instruction is not used, the program will stop immediately when an
interruption event is caught. With DEFER INTERRUPT
, the program continues,
and can test INT_FLAG to check if an interruption event occurred. It is good practice to
reset INT_FLAG to FALSE
after detecting
interruption:WHILE ...
IF INT_FLAG THEN
LET INT_FLAG=FALSE
ERROR "Procedure was interrupted by the user"
EXIT WHILE
END IF
...
END WHILE
WHENEVER ERROR CONTINUE
-- Long running SQL statement
WHENEVER ERROR STOP
IF SQLCA.SQLCODE == -213 THEN
ERROR "Database query interrupted by user"
...
END IF
When not using DEFER INTERRUPT
, if the program enters in a long running
procedure, a button with the action name 'interrupt' will become active. The user can then
press that button, and the runtime system will stop the program, since DEFER
INTERRUPT
is not used. However, this will not happen when a dialog is active,
because the 'interrupt' button will be automatically disabled in that context. Such
situation can confuse the end user, expecting that the 'interrupt' button can stop the
program in any context.
Note that the front end can not handle interruption requests properly if the display generates a lot of network traffic. In this case, the front end has to process a lot of user interface modifications and has no time to detect a mouse click on the 'interrupt' action view. A typical example is a program doing a loop from 1 to 10000, just displaying the value of the counter to a field and doing a refresh. This would generate hundreds of AUI tree modifications in a short period of time. In such a case, we recommended that you calculate a modulo and display steps 10 by 10 or 100 by 100.
Implementing interruption of a long running SQL query
-- db_busy.per
LAYOUT
GRID
{
Database query in progress...
[sb ]
}
END
END
ATTRIBUTES
BUTTON sb: interrupt, TEXT="Stop";
END
MAIN
DEFINE oc INT
DEFER INTERRUPT
OPTIONS SQL INTERRUPT ON
DATABASE stores
OPEN FORM f FROM "db_busy"
DISPLAY FORM f
CALL ui.Interface.refresh()
WHENEVER ERROR CONTINUE
SELECT COUNT(*) INTO oc FROM orders
WHENEVER ERROR STOP
IF SQLCA.SQLCODE == -213 THEN
ERROR "Database query has been interrupted..."
END IF
END MAIN