Tuesday, August 23, 2011

User / Display Application Generalization of Framework and Connection




User / Display Application Generalization of Framework and Connection


While implementing the ability of the framework to do the reconnect between User Applications when one "failed" (was closed) and then recovered (was reopened), I decided that this was something to also do between the Ada user application framework and the C# Display Application.  In thinking about that, I thought that I should first reorganize the Display App somewhat to make it more general; that is, to have the ability to support more than one communications method between the Display App and the User Apps, be able to support more than one Display App (or non-framework based app), and to have the framework support more than one communications protocol (in this case, move the user app / display app interface support from user components to the framework).

In addition, to support the reconnect monitoring, to have heartbeat messages as part of the communications protocol for the User App, Display App interface.
 


As part of moving the message support between the User Apps and the Display App from cloned user components (one such component for each User App interfacing to a display layer) to a supported communication protocol of the framework it became necessary to be able to differentiate between the framework inter user app supported protocol and the user app to display app protocol.

Since the communications between the user apps and the display app isn't actually ARINC 661, I decided to name it the Display Protocol and the topic based inter User App messaging the Topic Protocol. 

The ARINC 661 protocol specification, as updated in "ARINC 661-Supp 3-Adoption Draft1", adds an extended header.  This extended header allows some message verification that was previously missing.  I have added this extended header to the display event and command messages that I have been using up to now.  It contains a leading pair of bytes to identify the message block, the identifiers of the source and intended destination applications, the number of bytes that should be in the message, a health boolean that specifies the source application's determination of whether the destination application is executing correctly, and, among other fields, a message reference number to allow the destination application to check that the messages are received in order without drops.  Nothing for a CRC.

The A661 supplement 3 draft document states that messages can be sent with only the extended header.  Therefore, I will use that as the heartbeat message to be sent periodically between the two applications and used to monitor the communications bus.  This, has yet to be done.  It will be implemented along with the reconnect between the user application and the display app.
 

Display Application Structure

To allow for multiple communication methods and multiple protocols, the C# communication classes of the display application has been rearranged to mimic those of the user application framework.  That is, instead of just a MS_Pipe class to use Microsoft pipes to support the communications, there are now Remote and Method classes as well. 

The Method class is to select among the supported communication methods for those to be used between the display app and the destination app.  That is, like the user app, the communication methods supported by each application in the network is to be specified by a configuration file.  The Method class, as more methods are supported, will (as with the user apps) perform the communications with each supported method.  For now, Method only selects MS_Pipe as the only supported communications method but it is specified in a configuration file and has to be specified there.  As with the user apps, WinSock for instance, could also be supported or supported instead of MS Pipes and then Method would select the class that implements the new method.

The Remote class is the top level class as it is the top level package in the user app framework.  That is, an instance of it is invoked when a Windows control (widget) event is received.  It contains the common code to add the extended header to the event messages to be transmitted prior to invoking the Method class and to verify the received command messages.  Also like the framework package, it has the Watch function that will be used to monitor the message traffic and control reconnects.

Currently, Remote assumes that the Display protocol will be used so the Display extended header is what is added to messages to be transmitted.  If the supported protocol changes, this is where the change will be made.  Such as if the format of the message data becomes that of the A661 protocol.  Or if another type of application is implemented in C# that uses its own protocol.
 

Configuration File

A display configuration file has been added and the user apps configuration file has been extended.  Eventually these two files will be combined into a single file with multiple sections – to start with a User Application section and a Display Application section – but extendable to allow for additional types of applications in the network.

The display configuration file contains the display application identifier and name, the supported communication method(s), and is now where the display Definition File path is specified so it can be moved.  

In addition, both configuration files now contain the path to the application's executable so that the first application of the network that is running can cause the others to run.  At least those on the local PC and, I suppose, there must be a way to do this over the network as well.

A possible structure of a combined configuration file, might be as follows.  In such a structure, the data is organized into groups with possible multiple lines provided for each group.  Each line begins with the group identifier and the application identifier to which the group belongs.  For easier parsing, the data for a particular groups and applications are in consecutive lines.  Or, better yet, this data could be provided in XML.

In the following example of the possible structure, the Group Identifiers are APP, CM, and AP for Application data, for supported Communication Method data, and AP for Addition Protocol data.

In addition to the application id, the Application data provides the application name, the base protocol of the application (currently Topic or Display), the PC on which the application is located, and the path to the executable.  In addition, for the Display App, the path of the Definition File to be used is provided.

The supported Communication Method data depends upon the base protocol of the application to provide the data needed by that protocol.  Different data is provided for different communication methods.  For the MS Pipe communication method, no other data is provided since the Topic protocol between applications provides the algorithm for creating the MS Pipe names.  If the WinSock communication method is also supported by the application, the IP Address to be used is provided.

For the Display protocol, the MS Pipe communication method also provides the base portion of the pipe name for communication between the Display Application and the User Application for a particular Layer (1 and 2 for application id 15 in the example).  For a Topic Protocol application this data is provided by the Addition Protocol data lines that specify that the additional supported protocol is Display for connection with the application that has 15 as its identifier treating the events of display layer 1.  The example illustrates the future possibility of App 1 also using the Display protocol to connect with another application via a different communication method.

The configuration data for a particular application continues as long as its application id is provided.

+–-–+–-–+–
|Gp |App|
|Id | Id|
+–-–+–-–+–
|APP|  1|App 1|Topic|host PC|executable path|
+––-+–-–+–
| CM|  1|MSPipe|
+––-+–-–+–
| CM|  1|WinSock|IP Address|
+––-+–-–+–
| AP|  1|Display|15| 1|MSPipe|base pipe name|
+––-+–-–+–
| AP|  1|Display|Id|Layer|comm method|
+––-+–-–+–
|APP|  2|App 2|Topic|host PC|executable path|
+––-+–-–+–
| CM|  2|MSPipe|
+––-+–-–+–
| AP|  2|Display|15| 2|MSPipe|base pipe name|
+––-+–-–+–

  . . .

+–-–+–-–+–
|APP| 15|App 15|Display|host PC|definition file path|executable path|
+––-+–-–+–
| CM| 15| 1|MSPipe|base pipe name|
+––-+–-–+–
| CM| 15| 2|MSPipe|base pipe name|
+––-+–-–+–-+–––––-+–-

Note:  Currently, the Display App obtains the supported layers from the Definition File and the communication method and pipe/port name for the layer from the Definition File.  This could be another approach.  That is, in the Configuration File, provide the nodes of the network via the data of the APP group and includes a Definition File path for each User App as well as the Display App(s).  Then provide the communication methods, additional types of applications, etc in the specified Definition File.  Perhaps, also a list of each additional application to which a particular application should connect and use that to look up the base protocol of that application, its supported communication methods, etc via its entry in the configuration file.


Display Protocol in Framework

As part of the generalization of the inter app communications the interface with the Display App was moved from separate user components of App1 and App2 to the framework as a second supported protocol.  This will allow any user app to interface to the display app (or an additional display app) without the need to clone the previous component for the new application.  It will also allow the extension of the monitoring of the connection by the framework and the ability to reconnect as soon as possible.

For the initial shift of this functionality the Com_Display user component was just slightly reworked as the mC-Message-Display framework component with Method and MS_Pipe child packages.  This retains a structure that parallels that of the Remote package of the Topic protocol with the Method selector package and the MS_Pipe and WinSock communication method packages.  Retained are the Client and Response thread packages under the mC-Message-Display package where the retained Client corresponds to the various Receive threads of MS_Pipe and WinSock. 

Response is the thread that awaits responses from user components that register to treat various event messages.  It then translates the response to a Display protocol message, including the A661 extended header and transmits it to the display app.

Client awaits the Receive from Method that, in turn, awaits the receive from the display via the MS_Pipe interface acting as the client.  (The Display App is the server.)  The Method package can be extended to await the message receive of other display methods.  When this happens, a further reorganization will put the receive threads in the method packages with one receive thread for each display app to which the network might be extended.  That is, with multiple supported communication methods for the Display Protocol and/or multiple display apps configured into the network, the framework Display Protocol structure will need to be reworked again to be more similar to that of the Topic Protocol package structure. 

This initial move from individual user components to one framework component structured to retain as much code as possible was only to evaluate the problems that might arise.  The one that did – when App2 was also modified to use the framework – was that the two user components had each registered a different clone of the mT-Topic_Widget_Event_Layer and mT-Topic_Widget_Command_Layer topic (e.g., mT-Topic_Widget_Event_Layer1) when the second app's interface to the Display App was first implemented.  This was because there could be only one publisher of a One-of-N topic that is used to publish the event to deliver it to the requesting component and only one consumer of the response (the display command) topic.  Hence, two versions of the topic were needed.

Therefore, as a workaround, the mC-Message-Display was modified to select the version of these two topics that corresponded to the layer supported by the particular user application that was running.  This allows the received event message to be published and distributed to the user component that has registered to treat the event and for that user component to publish the command to be delivered to the new framework component of the application that treats the layer.  The Topic Widget Event message is a translation of the data of the received Display protocol message while the response Topic Widget Command will be translated by the mC-Message-Display-Response framework component to a Display Protocol message and transmitted to the Display Application using the connection for the layer.


Future Display Protocol in Framework

Although the above mentioned workaround satisfies the Topic protocol it is not an ideal solution since the addition of other layers would require further clones of the two topics and modification of the particular mC-Message-Display packages to select the correct Ada Topic package and topic Reader and Writer for the display layer.

The Remote framework component makes use of its being inside the framework.  When Register Topic messages are received they are evaluated as to whether they apply to the running application.  If so, the remotely supplied or consumed topic is registered by the Remote component.  Instances of such remotely registered topics are then accepted by the Remote Receive (acting as the consumer) and forwarded (acting as the publisher) – sort of an amplifier – to the topic consumer and any response is transmitted by Remote without using the normal notify mechanism back to the sender.  Something similar may be able to done for the widget event and command topics so that the topics are routed correctly without special versions of the topics now that the component is within the framework and has direct access to its private procedures.

Otherwise, there is the example of the Delivery Identifier message exchange delivery style where a delivery id is supplied.  This style is specified for the Widget Event topic where multiple user components can register to consume the topic but also supply the delivery ids that will be treated.  For the Widget Event topic the widget id is supplied as the delivery id and the framework only delivers the topic to the component that has registered to treat that particular delivery id.  This can be considered One-of-N in that there can be N consumers of the topic but only one can receive a particular instance of the topic – the one that registered to treat the delivery id.

In addition, there is the Request/Response message exchange delivery style that is N-to-One; that is, N possible producers but only one consumer.  In addition, the Response published by the consumer is only delivered by the framework to the component the published the request and only if it registered to consume the response.

Therefore, either a new message exchange style that combines these features could be created or the Delivery Identifier style could be extended to allow a response with that response delivered to the publisher of the request.

Either way, the Widget Event and Widget Command topics would be combined into a paired topic with a request (the display event) and a response (the display command).  The request would be delivered to the consumer that registered for the delivery (event) id and the response would be delivered to the component that published the request.  If an extension of the current Delivery Identifier delivery style is selected, then other topics of this style wouldn't be paired and there would continue to be no response to deliver.  This would be a One-of-N-to-One delivery style.

Another possible direction to follow is that developed for the Foreign language components that could register, through another private framework interface, their own version of an Ada topic.  It can be checked whether the topics would get delivered correctly if the Display component used the Foreign interface Register to register C versions of the two topics so that the Ada generic Topic Writer and Reader could be bypassed.

Display Communications Monitoring

Now that both the Display Application communications is in the Remote and associated classes and the User Application communications to the Display Application has been moved to within the framework, the Display Protocol communications can be monitored similarly to the Topic Protocol inter-application communications.  As well as attempts to reconnect after a loss of communications.

Currently the Display app only sends messages when a widget event occurs.  This won't be sufficient to recognize a continuing connection via the Watch procedure of the Remote class / package.  However, now that there can be the Extended Header-only message, it can be sent as a heartbeat message periodically by both the user app and the display app and the Watch procedure can monitor for an ever changing received message count.  If the count stays the same, it can declare a disconnect.

Likewise, a disconnect could also be declared for the MS Pipe class/package when the pipe interface recognizes the disconnect.  This can be signaled to the Watch procedure by the setting of a boolean in the class/package's Comm structure for the Watch procedure to monitor.

After a disconnect, the framework (and Display App) can attempt to get the pipe connected again. 


Other Associated Additions that could to be made

During my ARINC 661 work at an aerospace supplier, the user application was tracking changes to the display.  This would have allowed a certain amount of recovery if a temporary loss of communications (such as the display computer resetting) occurred.  The displays could have been restored to the last assumed display status (that is, to the state that they would have had if the last commands sent had been received and act on).  Or, to the state of the last saved checkpoint.

Some future addition to my exploratory project could retain some information about what was displayed to be able to recover somewhat from a disconnect.  Without this, the display can only redisplay the initial display after it begins execution once again.  (Of course, if the problem really is in the connection, the display will continue unchanged, except that it could display a Loss of Connection popup.)

Windows does some of this but not much.  That is, the desktop displays particular icons and toolbars upon each start and the like, but does not relaunch user applications that were running.  It now seems to relaunch Windows Explorer but only to display the startup page.  Particular applications such as Ultra Edit, if closed, will open the same files that were opened when the application was closed.  So certain applications have persistence to varying degrees.


Monday, August 8, 2011

Windows Forms Controls Using Multiple Threads

Windows Forms Controls Using Multiple Threads


In reworking Display Application of my exploratory project with its generalization in mind, I found myself in the Windows multithreading quagmire once again.

This is because in "emulating" the protocol of the ARINC-661 (A661) aerospace standard my Visual C# based Display Application is connected to multiple Ada User Applications.  For A661, each separate user application communicates with a separate "layer" of the display surface as interacted with via the Display Application.  Each user app, display app connection is via a separate communications channel. 

The Display Application transmits widget event messages to the user application associated with the display layer.  In response, the user application transmits to the display a command to manipulate the display – such as whether to hide one widget and show another, add text to a list box, display a new form and the like.  That is, the Display Application doesn't contain its own logic to modify the state of the display but passes that task along to the User Application associated with the layer.

Therefore, my Visual C# application is not a normal application of Visual Basic, C#, etc.  The forms and the widgets to be displayed within them are not supplied using the Visual language's features.  Instead a definition file is supplied to define what the forms and their widgets are, where they are to be displayed, etc as well as the "super" forms that are the layers that can contain multiple forms.  The user application of the layer commands when to display a particular form and which widgets of the form are to be displayed.

Because of this, the communications for a layer must wait upon the receive of the next command message.  Therefore, each layer has to have a receive thread that can block while waiting for a message from its user application.

This, in turn, causes the entry into the Windows multithreading swamp since Windows can't handle (directly) commands to modify a form from a thread other than the one that created it.  See, for instance, "How to: Make Thread-Safe Calls to Windows Forms Controls" of Visual Studio 2010 of the Microsoft web site.  [msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx]  See the Community Content comments as well.

In my own case, I have created different display threads for each display layer and a Windows timer for the layer.  Then, when a command is received for a particular layer, I add it to a queue for that layer.  When the Windows timer for a layer trips, the elapsed timer handler then checks whether the associated queue has any entries.  If so, it calls a common display function to interpret the queued command.  That function will be executing in the particular display thread so when it uses Windows display functions for a widget (form or control) it will be executing in the thread that Windows expects.

One gotcha is that the Windows timer handler (as opposed to a system timer) is only entered when both the timer has elapsed and the thread is running.  That is, the application has to have the correct display thread running before the timer can be created that supplies the name of the unique handler.  Therefore, the thread has to be created and running to display the form (forms of the layer in the case of the exploratory project) while also creating the particular Windows timer within the thread.  This establishes the thread to be used for particular forms and controls (widgets) and the timer to elapse that is to be used to read the command queue.

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;