Saturday, July 23, 2011

Framework Reconnect Following Loss of Connection

Framework Reconnect Following Loss of Connection



This involves any of the various protocols; currently the Framework MS Pipe driver protocol, the Framework WinSock driver protocol, and the MS Pipe protocol between an user application and the Display Application.  This latter protocol currently is implemented via component driver rather than being part of the Framework.

Of these connection protocols, I have now modified the Framework to reconnect with a remote user application if they communicate via MS Pipes.

A Watch method has now been implemented as part of a previously existing Remote thread that runs in a forever loop at particular rate.  This method determines whether messages are being received from the various remote applications of the configuration.  (Note: Heartbeat messages should be received even if no other activity between the applications is necessary.) 

The Watch method would indicate a connection whether the activity is via either of the two inter-user application protocols.  In order to make testing easier (that is, the need recognize a new connection), the supported connection protocols for each user application of the configuration was set to only MS Pipe so that WinSock connections were not attempted.

When the Watch method detects a disconnect between the local application and a particular remote application, it performs the Windows interface functions to close the driver and then signals the Remote Main thread that the topic tables indicating which topics were being satisfied by the remote application need to be purged.  The Main thread then calls a new procedure to do so.  The remotely registered topics are retained however since they are links in chains in Framework Memory that doesn't use garbage collection.  Instead, upon reconnect with the remote application, reconnect is indicated when the remote topic is again registered so as to ignore what would be a duplicate registration.

Currently both possible drivers are closed – if previously opened.  This will have to be changed so that the watch is by connection method with a particular method driver closed if it stops receiving messages while not signaling the Remote Main thread unless there was no other working protocol.  This being so that if one protocol fails but the other continues, the failed protocol will be reverted to its initial condition in case the connection can be reestablished.

The building and sending of the Remote Register Request message was changed so that no longer happens as soon as startup is finished and continues to be attempted to any possible remote application satisfied. 

Since with disconnect from an application, some of the remote topics can continue to be satisfied by another remote application there is no need to attempt to determine what remote applications can satisfy those topics.  Therefore, a different Remote Register Request message is needed.  Although not addressed before, the same is true if one remote application is launched and establishes a connection early on and another not until much later.  Then some of the needed topics of the local application will already have been satisfied and there is no need for them to be included in the Remote Register Request message sent to the late connection.

Therefore, the new Watch method detects when a connection has been established and sends an event to the Remote Main thread.  The Main thread then does a modified build of the Remote Register Request that is only meant to be sent to the newly connected remote application.  The first time the message will contain all the topics that can't be satisfied by the components of the local application – the same as occurred before.  However, when another remote application is established, the Remote Register Request message to be sent to it won't contain the topics that were satisfied by previously connected applications.  (Note, if the next connection occurs before the previously connected applications can complete the remote registration sequence, then the message will still contain topics that will be satisfied by an earlier connection.)

The Remote Register Request message is then only transmitted by the Remote Transmit thread to the particular remote application rather than broadcast to all possible applications of the configuration.  Since it is now known that the remote application does have a connection, the Remote Register Request message is deleted after it has been transmitted and not attempted each time the Remote Transmit thread is run.

This new connection and remote topic registration was tested by quitting/stopping a particular application to break the connection and then restarting/relaunching it.  The other running applications detected the disconnect by the lack of received messages and did the reconnect sequence explained above.  The relaunched application does its normal startup and sends its own Remote Register Request message when the connections with the other user applications is detected.

In order to see the console log of the relaunched application, I changed the open of the console log to append to the existing log if less than 10 seconds had elapsed since the log was last modified.

Saturday, July 16, 2011

Use of Windows Thread Priorities

Use of Windows Thread Priorities


General

While looking up Windows CE I came across a statement that it had 255 priority levels.  This caused me to think again on how few levels could be set for Windows by the SetThreadPriority function of Win32Ada and WinBase.h so I Googled the subject and found Microsoft material that I’d overlooked when just using Win32Ada.

For instance, the following, which is directly taken from Microsoft material:

Scheduling Priorities

Threads are scheduled to run based on their scheduling priority. Each thread is assigned a scheduling priority. The priority levels range from zero (lowest priority) to 31 (highest priority). Only the zero-page thread can have a priority of zero. (The zero-page thread is a system thread responsible for zeroing any free pages when there are no other threads that need to run.)

The system treats all threads with the same priority as equal. The system assigns time slices in a round-robin fashion to all threads with the highest priority. If none of these threads are ready to run, the system assigns time slices in a round-robin fashion to all threads with the next highest priority. If a higher-priority thread becomes available to run, the system ceases to execute the lower-priority thread (without allowing it to finish using its time slice), and assigns a full time slice to the higher-priority thread. For more information, see Context Switches.

The priority of each thread is determined by the following criteria:
  • The priority class of its process
  • The priority level of the thread within the priority class of its process
The priority class and priority level are combined to form the base priority of a thread. For information on the dynamic priority of a thread, see Priority Boosts.

Priority Class

Each process belongs to one of the following priority classes:
IDLE_PRIORITY_CLASS
BELOW_NORMAL_PRIORITY_CLASS
NORMAL_PRIORITY_CLASS
ABOVE_NORMAL_PRIORITY_CLASS
HIGH_PRIORITY_CLASS
REALTIME_PRIORITY_CLASS

By default, the priority class of a process is NORMAL_PRIORITY_CLASS. Use the CreateProcess function to specify the priority class of a child process when you create it. If the calling process is IDLE_PRIORITY_CLASS or BELOW_NORMAL_PRIORITY_CLASS, the new process will inherit this class. Use the GetPriorityClass
function to determine the current priority class of a process and the SetPriorityClass function to change the priority class of a process.

Processes that monitor the system, such as screen savers or applications that periodically update a display, should use IDLE_PRIORITY_CLASS. This prevents the threads of this process, which do not have high priority, from interfering with higher priority threads.

Use HIGH_PRIORITY_CLASS with care. If a thread runs at the highest priority level for extended periods, other threads in the system will not get processor time. If several threads are set at high priority at the same time, the threads lose their effectiveness. The high-priority class should be reserved for threads that must respond to time-critical events. If your application performs one task that requires the high-priority class while the rest of its tasks are normal priority, use SetPriorityClass to raise the priority class of the application temporarily; then reduce it after the time-critical task has been completed. Another strategy is to create a high-priority process that has all of its threads blocked most of the time, awakening threads only when critical tasks are needed. The important point is that a high-priority thread should execute for a brief time, and only when it has time-critical work to perform.

You should almost never use REALTIME_PRIORITY_CLASS, because this interrupts system threads that manage mouse input, keyboard input, and background disk flushing. This class can be appropriate for applications that "talk" directly to hardware or that perform brief tasks that should have limited interruptions.

Priority Level

The following are priority levels within each priority class:
THREAD_PRIORITY_IDLE
THREAD_PRIORITY_LOWEST
THREAD_PRIORITY_BELOW_NORMAL
THREAD_PRIORITY_NORMAL
THREAD_PRIORITY_ABOVE_NORMAL
THREAD_PRIORITY_HIGHEST
THREAD_PRIORITY_TIME_CRITICAL

All threads are created using THREAD_PRIORITY_NORMAL. This means that the thread priority is the same as the process priority class. After you create a thread, use the SetThreadPriority function to adjust its priority relative to other threads in the process.

A typical strategy is to use THREAD_PRIORITY_ABOVE_NORMAL or THREAD_PRIORITY_HIGHEST for the process's input thread, to ensure that the application is responsive to the user. Background threads, particularly those that are processor intensive, can be set to THREAD_PRIORITY_BELOW_NORMAL or THREAD_PRIORITY_LOWEST, to ensure that they can be preempted when necessary. However, if you have a thread waiting for another thread with a lower priority to complete some task, be sure to block the execution of the waiting high-priority thread. To do this, use a wait function, critical section, or the Sleep function, SleepEx, or SwitchToThread function. This is preferable to having the thread execute a loop. Otherwise, the process may become deadlocked, because the thread with lower priority is never scheduled.

To determine the current priority level of a thread, use the GetThreadPriority function.

Base Priority

The process priority class and thread priority level are combined to form the base priority of each thread.
The following table shows the base priority for combinations of process priority class and thread priority value.

Process priority class
Thread priority level
Base priority
IDLE_PRIORITY_CLASS
THREAD_PRIORITY_IDLE
1
THREAD_PRIORITY_LOWEST
2
THREAD_PRIORITY_BELOW_NORMAL
3
THREAD_PRIORITY_NORMAL
4
THREAD_PRIORITY_ABOVE_NORMAL
5
THREAD_PRIORITY_HIGHEST
6
THREAD_PRIORITY_TIME_CRITICAL
15
BELOW_NORMAL_PRIORITY_CLASS
THREAD_PRIORITY_IDLE
1
THREAD_PRIORITY_LOWEST
4
THREAD_PRIORITY_BELOW_NORMAL
5
THREAD_PRIORITY_NORMAL
6
THREAD_PRIORITY_ABOVE_NORMAL
7
THREAD_PRIORITY_HIGHEST
8
THREAD_PRIORITY_TIME_CRITICAL
15
NORMAL_PRIORITY_CLASS
THREAD_PRIORITY_IDLE
1
THREAD_PRIORITY_LOWEST
6
THREAD_PRIORITY_BELOW_NORMAL
7
THREAD_PRIORITY_NORMAL
8
THREAD_PRIORITY_ABOVE_NORMAL
9
THREAD_PRIORITY_HIGHEST
10
THREAD_PRIORITY_TIME_CRITICAL
15
ABOVE_NORMAL_PRIORITY_CLASS
THREAD_PRIORITY_IDLE
1
THREAD_PRIORITY_LOWEST
8
THREAD_PRIORITY_BELOW_NORMAL
9
THREAD_PRIORITY_NORMAL
10
THREAD_PRIORITY_ABOVE_NORMAL
11
THREAD_PRIORITY_HIGHEST
12
THREAD_PRIORITY_TIME_CRITICAL
15
HIGH_PRIORITY_CLASS
THREAD_PRIORITY_IDLE
1
THREAD_PRIORITY_LOWEST
11
THREAD_PRIORITY_BELOW_NORMAL
12
THREAD_PRIORITY_NORMAL
13
THREAD_PRIORITY_ABOVE_NORMAL
14
THREAD_PRIORITY_HIGHEST
15
THREAD_PRIORITY_TIME_CRITICAL
15
REALTIME_PRIORITY_CLASS
THREAD_PRIORITY_IDLE
16
THREAD_PRIORITY_LOWEST
22
THREAD_PRIORITY_BELOW_NORMAL
23
THREAD_PRIORITY_NORMAL
24
THREAD_PRIORITY_ABOVE_NORMAL
25
THREAD_PRIORITY_HIGHEST
26
THREAD_PRIORITY_TIME_CRITICAL
31



I fail to understand some of the above Microsoft material.  That is, the statement “The priority levels range from zero (lowest priority) to 31 (highest priority)” make it seem like there are 32 scheduling priorities with priority level 0 only possible in a particular situation.  On the other hand, except for the REALTIME_PRIORITY_CLASS, the tables seem to show a good deal of overlap in the “base priority” from one priority class to another.  Thus it would seem that the first five priority classes only provide 15 different thread priorities from 1 through 15 with the highest priority class providing 7 more of 16, 22, 23, 24, 25, 26, and 31 for a total of 22 different priorities (excluding the special zero-page thread case of level 0).

Hence, I assumed that there are really only 15 different scheduling priority levels available for use by the Exploratory Project framework – since it doesn’t have any device drivers to use the Realtime Priority Class – thus just about double the 7 that I had when I hadn’t known about classes and, hence, was using only the default normal class.

Of these available scheduling priorities I have assigned four to the use of the framework (15, 14, 13 and 1) with the remaining 11 to be assigned to user components.  Since I have not, as yet, changed the framework categories, scheduling priorities 14 and 1 aren’t used.

The user components are assigned their priority based upon the expected duration of their execution as supplied when they register themselves.  This value could be modified based upon monitoring as they run but this has yet to be done.  Also, the exploratory project user components are merely artificial to illustrate execution possibilities and provide message traffic so they are mostly cloned had assigned the same duration so the components assigned the same won’t change much.  Therefore many components continue to be assigned the same scheduling priority and execute round robin.

Code Examples


The modified implementation occurs in two framework methods.  The first is to assign a scheduling priority based upon the provided estimated execution interval that the component provides when it registers with the framework.  The units provided in the code example are meaningless since they assume an embedded application rather than a Windows application the method takes this into account.  That is, the conversion would be changed for a different platform as well as the number of priority levels.

The second method sets the thread priority based upon the scheduling priority.

  function Process_Priority
  ( Component : in mC.Itf_Types.Component_Category_Type;
    Interval  : in Timing_Interval_Type
  ) return Process_Priority_Type is
  -- ++
  --| Logic_Flow:
  --|   This function assigns the process priority to be used with the
  --|   component time Interval for the particular operating system.
  --|
  --|   See table in Set_Thread_Priority.
  -- --

    Base_Priority
    --| Priority obtained from Time Interval
    : Process_Priority_Type;

    use type mC.Itf_Types.Component_Category_Type;

  begin -- Process_Priority

    --| Logic_Step:
    --|   Set the process priority while reserving the highest PC priority for
    --|   use by the Message Controller framework.
    --| Notes:
    --|   Only eleven priority levels available for user component use using
    --|   Win32Ada (that is, Windows).  Therefore, the following computation
    --|   has been modified to end up with priorities from 2 to 12 with 13
    --|   and 1 still available for future Framework use.

    if Component = mC.Itf_Types.Time_Critical then
      Base_Priority := 15; -- Executive Component Time Critical Priority
    elsif Component = mC.Itf_Types.Framework then
      Base_Priority := 13; -- Executive Component normal Priority
    elsif Interval <= 1 then   --  50msec
      Base_Priority := 12; -- Top User Component Priority
    elsif Interval <= 2 then   -- 100msec
      Base_Priority := 11; -- Top User Component Priority less one
    elsif Interval <= 4 then   -- 200msec
      Base_Priority := 10;
    elsif Interval <= 8 then   -- 400msec
      Base_Priority := 9;
    elsif Interval <= 16 then  -- 800msec
      Base_Priority := 8;
    elsif Interval <= 24 then  -- 1.2sec
      Base_Priority := 7;
    elsif Interval <= 50 then  -- 2.5sec
      Base_Priority := 6;
    elsif Interval <= 100 then -- 5 sec
      Base_Priority := 5;
    elsif Interval <= 200 then -- 10 sec (10,000msec)
      Base_Priority := 4;
    elsif Interval <= 400 then -- 20 sec
      Base_Priority := 3;
    else                       -- > 20 sec
      Base_Priority := 2; -- Lowest User Component Priority
    end if;

    return Base_Priority;

  end Process_Priority;

procedure Set_Thread_Priority
( Priority        : in Process_Priority_Type;
  Thread_Class    : in out Win32.DWORD;
  Thread_Priority : in out Win32.INT;
  Thread_Handle   : in Win32.Winnt.HANDLE;
  Success         : out Boolean
) is
-- ++
--| Logic_Flow:
--|   Set new scheduling priority using a combination of priority class and
--|   thread priority.
--|
--|     Priority Class                Thread Priority            Scheduling
--|                                                               Priority
--|  ---------------------------  ---------------------------  -------------------
--|  IDLE_PRIORITY_CLASS          THREAD_PRIORITY_IDLE             1     mC
--|  BELOW_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_IDLE             1     mC
--|  IDLE_PRIORITY_CLASS          THREAD_PRIORITY_LOWEST           2    user
--|  IDLE_PRIORITY_CLASS          THREAD_PRIORITY_BELOW_NORMAL     3    user
--|  BELOW_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_LOWEST           4    user
--|  BELOW_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_BELOW_NORMAL     5    user
--|  BELOW_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_NORMAL           6    user
--|  NORMAL_PRIORITY_CLASS        THREAD_PRIORITY_LOWEST           6    user
--|  BELOW_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_ABOVE_NORMAL     7    user
--|  NORMAL_PRIORITY_CLASS        THREAD_PRIORITY_BELOW_NORMAL     7    user
--|  BELOW_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_HIGHEST          8    user
--|  NORMAL_PRIORITY_CLASS        THREAD_PRIORITY_NORMAL           8    user
--|  ABOVE_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_LOWEST           8    user
--|  NORMAL_PRIORITY_CLASS        THREAD_PRIORITY_ABOVE_NORMAL     9    user
--|  ABOVE_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_BELOW_NORMAL     9    user
--|  NORMAL_PRIORITY_CLASS        THREAD_PRIORITY_HIGHEST         10    user
--|  ABOVE_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_ABOVE_NORMAL    11    user
--|  ABOVE_NORMAL_PRIORITY_CLASS  THREAD_PRIORITY_HIGHEST         12    user
--|  HIGH_PRIORITY_CLASS          THREAD_PRIORITY_NORMAL          13     mC
--|  HIGH_PRIORITY_CLASS          THREAD_PRIORITY_ABOVE_NORMAL    14     mC
--|  HIGH_PRIORITY_CLASS          THREAD_PRIORITY_HIGHEST         15     mC
--|  HIGH_PRIORITY_CLASS          THREAD_PRIORITY_TIME_CRITICAL   15 dup
--|  REALTIME_PRIORITY_CLASS      unused by Framework
--|
--|   See Process_Priority for assignment of Windows Scheduling Priorities.
-- --

  Class_Set
  --| True if thread's class was set successfully
  : Win32.BOOL;

  CurrentProcess
  --| Handle of currently running process
  : Win32.WinNT.HANDLE;

  Priority_Set
  --| True if thread's priority was set successfully
  : Win32.BOOL;

  type Windows_Scheduling_Priority_Item_Type
  is record
    Class    : Win32.DWORD;
    Priority : Win32.INT;
  end record;

  type Windows_Scheduling_Priority_Table_Type
  is array( 1..Process_Priority_Type'last )
  of Windows_Scheduling_Priority_Item_Type;

  Windows_Scheduling_Priority_Table
  --| Table of Windows Priority Classes and relative Priorities within class
  --| to obtain Scheduling Priority values
  : constant Windows_Scheduling_Priority_Table_Type
  := ( 1 => ( Class    => Win32.Winbase.IDLE_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_IDLE ),
       2 => ( Class    => Win32.Winbase.IDLE_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_LOWEST ),
       3 => ( Class    => Win32.Winbase.IDLE_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_BELOW_NORMAL ),
       4 => ( Class    => Win32.Winbase.BELOW_NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_LOWEST ),
       5 => ( Class    => Win32.Winbase.BELOW_NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_BELOW_NORMAL ),
       6 => ( Class    => Win32.Winbase.NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_LOWEST ),
       7 => ( Class    => Win32.Winbase.NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_BELOW_NORMAL ),
       8 => ( Class    => Win32.Winbase.NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_NORMAL ),
       9 => ( Class    => Win32.Winbase.NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_ABOVE_NORMAL ),
      10 => ( Class    => Win32.Winbase.NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_HIGHEST ),
      11 => ( Class    => Win32.Winbase.ABOVE_NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_ABOVE_NORMAL ),
      12 => ( Class    => Win32.Winbase.ABOVE_NORMAL_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_HIGHEST ),
      13 => ( Class    => Win32.Winbase.HIGH_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_NORMAL ),
      14 => ( Class    => Win32.Winbase.HIGH_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_ABOVE_NORMAL ),
      15 => ( Class    => Win32.Winbase.HIGH_PRIORITY_CLASS,
              Priority => Win32.Winbase.THREAD_PRIORITY_TIME_CRITICAL )
     );

  use type Win32.BOOL;

begin -- Set_Thread_Priority

  --| Logic_Step:
  --|   Obtain handle of current process/application.

  CurrentProcess := Win32.Winbase.GetCurrentProcess;

  --| Logic_Step:
  --|   Set Windows priority class and relative priority with class.

  Thread_Class    := Windows_Scheduling_Priority_Table(Priority).Class;
  Thread_Priority := Windows_Scheduling_Priority_Table(Priority).Priority;

  --| Logic_Step:
  --|   Set new Windows priority class and relative priority with class for
  --|   the particular application thread.

  if Priority /= 0 then

    Process_Lock;

    Class_Set := Win32.Winbase.SetPriorityClass
                 ( hProcess        => CurrentProcess,
                   dwPriorityClass => Thread_Class );
    Priority_Set := Win32.Winbase.SetThreadPriority
                    ( hThread   => Thread_Handle,
                      nPriority => Thread_Priority  );

    Process_Unlock;

    if Priority_Set = Win32.True then
      Success := True;
    else
      Success := False;
    end if;

  else -- leave priority as is

    Thread_Class := Win32.Winbase.GetPriorityClass
                    ( hProcess => CurrentProcess );
    Thread_Priority := Win32.Winbase.GetThreadPriority
                       ( hThread => Thread_Handle );
    Success := True;

  end if;

end Set_Thread_Priority;