PackageDescription: ForkedUI


Forked UI

Last published: January 13, 2003 by 'dmurphy'

Defines 6 Classes
Extends 9 Classes


From ObjectShare tech support solution 1607, this parcel allows a forked block to pass blocks whose evaluation result in UI activity to the UI process using Processor #performUIBlock:. This prevents undesired side effects of doing UI activity in a forked process. The text of the solution follows:




ParcPlace-Digitalk ObjectSupport

Forked UI activity in an event-driven image

Products : VW2.5
Platforms: ALL
Keywords : ui, process, events, fork

Released: 5/6/96
Last Revised: 8/7/98


Forked UI activity in an event-driven image


Q.
Why do I see strange side effects when forking dialogs or other UI activity?


A.
This problem is more apparent in VisualWorks 2.0 through 2.5.1, and
VisualWave 1.0 through 2.0. Side effects have been reduced significantly
in VisualWorks 2.5.2 and VisualWave 2.1, but there are still dangers to
avoid. A mechanism for improving the safety of forked UI activity follows
the explanatory text. Please note that this text describes behavior of a
2.5.2 event-driven image. The code provided works with 2.5, 2.5.1, and
2.5.2 images, but does not apply to polling images.

To answer this question we must first provide some background information.
An understanding of general thread-safety issues in VisualWorks may be a
useful introduction to this topic of forked UI activity. We will start by
describing the InputProcess to understand how input events are delivered to
the appropriate UI objects in the Smalltalk image, and then cover the
workings of the UI Process. Please note that the term 'process' in this
text refers to an instance of the class Process. These Smalltalk processes
are called "lightweight processes", and are managed by Smalltalk rather
than by the host Operating System (OS).


Background: the InputProcess

The InputProcess is held in a class variable, 'InputProcess' in class
'InputState'. This process runs at 'Processor lowIOPriority' (90), which
is relatively high.

The InputProcess handles all of the keyboard and mouse activity that
VisualWorks receives as input from the OS. This input is sent as a host
event to the Object Engine (OE), which creates a Smalltalk event object
from the host event. (The term "event" will refer to a Smalltalk object
unless preceded by the word host.) This event is processed by
InputProcess. The InputProcess takes the events that it receives and
dispatches them to the sensor object of the appropriate window.

For example, a host event has been delivered to the OE. The OE creates a
Smalltalk event and signals the InputSemaphore to indicate that an event
has been received. After the InputSemaphore is signaled by the OE, the
InputProcess fetches the event from the OE by calling
Screen #primReadEvent:. Then, in InputState #process:, the
InputProcess
examines the event and decides to dispatch it to a particular window. The
event is then sent to the sensor of the destination window. It is helpful
to think of these sensors as 'In-Boxes', and the InputProcess as a 'Mail-
Sorter', dispatching each event to the appropriate In-Box. After
dispatching the event, the InputProcess waits on the InputSemaphore again
to be notified of the arrival of the next event. If all currently
available events have been dispatched, the InputSemaphore suspends the
InputProcess to allow other scheduled processes (like the UI Process) to
run. The main InputProcess loop is found in InputState #run.

Some damage events are dispatched in the same manner. For example, suppose
you drag a window over another Smalltalk window, then drag the window back
to its previous position. The window that is uncovered has received
'damage' and must be told to redraw the uncovered areas. The OE signals
the Input Process, which fetches the damage event (this type of event
describes a rectangle that must be redrawn) from the OE and dispatches it
to the sensor of the appropriate window. Again, the sensor acts as an 'In-
Box'.

The basic activity of the InputProcess can be summarized very simply:
- wait on a semaphore
- fetch an event from the OE
- dispatch the event to a sensor
- repeat


Background: the UI Process

The UI Process controls UI activity. It is responsible for allowing
controllers to process events that were dispatched by the InputProcess to
the window sensors (In-Boxes). The UI Process is held in the distinguished
(meaning there is only one) instance of the class ControlManager. This
instance is stored as a global in the SystemDictionary, and is named
ScheduledControllers. Sending #activeControllerProcess to
ScheduledControllers will return the current UI Process as in

ScheduledControllers activeControllerProcess

The UI Process runs at 'Processor userSchedulingPriority' (50), a much
lower priority than that of the InputProcess.

Much of the UI management is processed in ControlManager #activeControllerLoop.

This loop calls ControlManager #checkForEvents, which sends #checkForEvents

to each of the scheduledControllers as long as there are outstanding damage
events or input events to process. There are a few strategic places in the
system where ControlManager #checkForEvents is called, so that window
damage
and window events are processed quickly. This helps to keep a smooth, crisp
look to the user interface.

After the active (scheduled) controllers are asked to checkForEvents in the
activeControllerLoop, each is asked in turn whether it wants control.
Typically, a controller wants control when its window is active and events
to which it must respond are placed in its window's sensor. For PC, Mac, and
some UNIX platforms a window is active and brought to the top when clicked on.
On other UNIX platforms based on X-Windows, a window becomes active but not
brought to the top when the mouse cursor is within its visible boundaries.
VisualWorks controllers respond appropriately to #isControlWanted for either
type of window activation. When a controller responds to #isControlWanted
with true, then the UI Process will send #startUp to that controller. The
controller performs its needed updates, and checks to see if it still wants
to retain control. Typically, the controller no longer wants control when
its window is no longer active, so it returns control to the
activeControllerLoop which repeats the control cycle.

When a controller is sent the message #startUp, it enters a loop in
Controller #controlLoop where it responds appropriately to events in its
sensor. For example, a controller on anActionButton will send its action
message to the applicationModel, invoking application code. Because this
code is invoked without forking, application code by default runs in the UI
Process. The activeControllerLoop is waiting for the controller to return
control from #startUp, and the startUp method is waiting for the application
code to complete. Understanding this concept is important in understanding
the dangers of forked UI updates.

Side effects of running application code within the UI Process can be seen
if the application code is computationally intensive, waits on a semaphore
for a substantial amount of time, or makes a long blocking call
to a database or DLL. Such long-running code will prevent the UI Process
from completing its loop through Controller #controlLoop (called from
#startUp) or ControlManager #activeControllerLoop when needed (for
example,
when there is damage to redraw or when another window is made active). This
starvation of the activeControllerLoop can cause the UI to appear to hang,
since input and damage events cannot be processed until the application code
completes.


The Problem: Forked UI Activity

In view of the effects of time-intensive application code in the UI
Process, many users fork off portions of the application code, without
realizing the problems that could arise from UI updates in that forked
code.

When UI are processed in the UI Process, the activeControllerLoop is
always waiting at the same point, specifically, at the point when the
Controller #startUp message is sent. When forked UI activity occurs,
the
activeControllerLoop is not necessarily waiting at that point. Instead,
the forked UI activity may run when the activeControllerLoop is at any
random point in the loop. Since the activeControllerLoop was not
designed for such interruption, the results are undefined. Even
if side effects had not been noticed for some time, they might suddenly
become apparent after some slight change to the application, or in the
way the application is used. Opening a dialog often aggravates the
problem.

An ApplicationDialogController's behavior is different from controllers
of normal application windows. It will not return control to the
activeControllerLoop until the dialog is dismissed, even if the user tries
to activate another VW window. It "grabs" all input events, even if
intended for other VW windows, and refuses to let the user do anything else
until the dialog is dismissed. If a dialog is forked, it continues
grabbing events while the activeControllerLoop continues to run! Again,
the results are undefined. This problem is further compounded when
multiple forked dialogs are raised concurrently, or when the user tries to
use other windows without dismissing the dialog.


The Solution: #performUIBlock:forWindow:

There are dangers of doing UI activity in forked processes, but there are
also legitimate needs to do so. The code below resolves this conflict by
providing a mechanism whereby a forked process may dispatch its UI activity
to the UI Process, where our current UI architecture expects it to be.

The file-in below contains ProcessorScheduler #performUIBlock:forWindow:
method, which provides the interface for forked processes to dispatch UI
activity to the UI Process. The class ComputationEvent is also defined.
Instances of this class hold onto the BlockClosure that contains UI code.
Additional code is needed to allow the system to handle this new type of
event. The processEvent: methods in ScheduledWindow and ApplicationWindow
are modified to handle this type of event, and the #eventComputation:
method is added to the classes EventSensor and WindowSensor.

To see how a forked process can take advantage of this new mechanism, see
the comment in the method ProcessorScheduler #performUIBlock:forWindow:.

We recognize that our product documentation does not address this issue in
detail, and hope this discussion is helpful. We also hope to package
forked UI support into a future version of VisualWorks.

Notes:
Even with the mechanism provided below, problems can occur when
multiple dialogs are forked concurrently. If your application
forks dialogs, consider protecting against the possibility of
raising them concurrently.

To return values from the forked UI block to the forked process,
your UI block should store any return values in a variable
accessible by the forked process. An example follows:
...
| aBlock aWindow confirmation |
aBlock := [confirmation := Dialog confirm: 'confirm'].
aWindow := self builder window.
Processor performUIBlock: aBlock forWindow: aWindow.
confirmation ifTrue: [ ...
Since the forked process waits on a semaphore in
ProcessorScheduler #performUIBlock:forWindow: until the UI block
gets evaluated in the UI Process, the value of the #confirmation
variable can be accessed immediately after sending
#performUIBlock:forWindow:.

A revision to this solution included below adds fork protection to dialogs
so that all VisualWorks dialogs inherently open using the above method.