Lab-5538: The Real-Time Java™ Platform Programming Challenge: Taming Timing Troubles

Expected Duration: 120 minutes

Exercise 5: Real-Time Data Communication

Expected duration: 15 minutes — 3 minute introduction then 12 minutes for lab

The goal of this exercise is to complete the real-time data communication example from Exercise 3, by addressing the synchronization contention problem that was identified in Exercise 4. The requirements are:

  • Modify the DataBuffer class so that the periodic event handler can deposit data without suffering from any synchronization related delays.
Warning: Threads with real-time priorities run to the exclusion of all non-realtime processes on the system, including operating system services, shells and GUIs! If your real-time thread does not explicitly block it will consume 100% of a CPU. If you have less CPUs than real-time threads doing this then you system may appear to lock-up. So take care with experimentation: an infinite loop may require a reboot to fix!

Background Information

 

Read the material in the presentation, up to the slide titled "Exercise 5".

  • In Exercise 4 we identified the cause of the deadline misses of the periodic event handler to be due to contention on a monitor-lock used to synchronize access to the internal Vector instance used by the DataBuffer instance: adding the data requires acquiring the lock on the vector, but a consumer-thread can be holding onto that lock for a very long time as it sorts the data through the process() method.
  • It might seem that a solution here is to simply reduce the length of time that the consumer thread holds the lock: if this is sufficiently short then the event handler will be able to acquire the lock in time to deposit the data every 50ms. While you could contrive changes to the data structures and algorithm that would reduce the time during which the consumer thread holds the lock needed by the event handler, this would not in fact solve the problem. The issue is not just the length of time needed by the computation in the consumer-thread while the lock is held, but also any potential interference to the execution of that consumer-thread, such as due to execution of other real-time threads. In part, priority-inheritance aims to address this issue by raising the priority of the consumer-thread while it holds the lock needed by the real-time event handler. But priority-inheritance is still only a partial solution that helps reduce the amount of time the real-time entity will have to wait for the lock. The critical problem in this case is that the no-heap event handler is waiting for a heap-using consumer-thread, and that consumer-thread can be delayed while the GC executes — which in turn means that the no-heap event handler is also delayed waiting for the GC. One golden rule in the RTSJ is that no-heap entities should never need to acquire the monitor lock of an object that will also be locked by a heap-using entity.
  • The RTSJ introduced the wait-free queues, WaitFreeWriteQueue and WaitFreeReadQueue, to allow data exchanges between no-heap entities and heap-using entities, without any use of monitor based synchronization (when used the correct way). The wait-free queues are fixed size, first-in-first-out, buffers that can be constructed to use different memory areas for their internal data-store. Taking WaitFreeWriteQueue as an example, it is intended for use by a single no-heap writer thread, and potentially many heap-using reader threads. The write operation is non-blocking and non-locking, so it will never block due to the queue being full, nor will it block due to any lock contention with a reader thread. In contrast, the read operation is both blocking (if the buffer is empty) and synchronized.
  • When using a fixed size queue you have to consider how to deal with the possibility of the queue being full when you try to write to it. One possibility is to ensure the buffer is large enough to accommodate the maximum expected number of concurrent entries. This requires consideration of the rates of data production and consumption, including what factors (like GC) might delay the consumer. Alternatively, you might use a policy that drops some data if too much accumulates — the WaitFreeWriteQueue.force(Object o) method supports this by replacing the previously added object with the new object.
  • A WaitFreeWriteQueue can not be used to simply replace the use of Vector in the DataBuffer class, because it does not support the random-access functionality that is used to sort the data. Instead we can introduce a simple double-buffering scheme, whereby data is initially written into a WaitFreeWriteQueue, and then moved across to the Vector (by the consumer-thread) for sorting.
  • To avoid unpredictable delays due to class-loading we're utilizing the Initialization Time Compilation (ITC) feature of Sun Java RTS. This is handled by the settings defined under Project Properties -> Real-Time Configuration. The use of ITC should be transparent to you for the purposes of these exercises, but you may see messages in the console output referring to the files itc.precompile and/or itc.preinit — these files have been provided for you in each of the project directories.

Steps to Follow

 

This exercise uses the code from Ex3Soln as its starting point. For convenience this has been copied into the Ex5 project. Alternatively you can use your own solution to Exercise 3, as this exercise only requires modification of the DataBuffer class.

  1. Start the Netbeans IDE if not already started. There are projects defined for each exercise and its solution. Expand the project Ex5 by clicking on the "lollypop" icon to the far left of the project name, in the project pane. Click on the same icon next to the Source Packages item to show the packages defined in this project. Click on the same icon next to the ex5 package name to show all the source files defined in this project. To edit a source file, double-click on its name and an editing tab for that file will appear in the right-hand pane.
  2. Edit the DataBuffer class (in DataBuffer.java).
  3. The following field has been added to the DataBuffer:
    final WaitFreeWriteQueue queue;
    Add code to the constructor to initialize queue. The WaitFreeWriteQueue(int maximum) constructor creates the queue in immortal memory — which is the only memory through which no-heap entities and heap-using entities can communicate.
  4. Modify the add method to write the supplied data into the queue.
  5. Modify the take method to move the current contents of the queue into the dataSet prior to calling process. Note the following:
    • The WaitFreeReadQueue.read operation removes the head object, but will block if the queue is empty. Ensure that you check the queue is not empty before trying to read the data.
    • Because read can block it is defined as a cancellation-point and so can throw InterruptedException if the thread is interrupted while waiting for data to appear in the queue. You must catch this checked-exception, but for the purpose of this exercise you can treat this as a fatal error, as interruptions are not expected.
    • The internal synchronization, on dataset, must remain in take to ensure that only one consumer-thread at a time can execute it — which is necessary for the data to be properly sorted.
  6. Build the project by right-clicking on the project name in the project pane, and then clicking on Build. Correct any compilation errors and build again, repeating as necessary.
  7. Run the project:
    • From within Netbeans by right-clicking on the project name in the project pane, and then clicking on Run. The output will be displayed in the output pane at the bottom of the window.
    • From a terminal window by executing the command: <INSTALL_DIR>/bin/java -jar /export/home/lab5538/realtimejava/exercises/Ex5/dist/Ex5.jar
    Here's an example of part of what you might see:
    # > /opt/SUNWrtjv/bin/java -jar /export/home/lab5538/realtimejava/exercises/Ex5/dist/Ex5.jar
    Application started ...
    Handler read 0.838178
    Handler read 0.509115
    Handler read 0.769864
    
    Handler read 0.414092
    Handler read 0.577811
    
  8. Note: the application will attempt to terminate after 60 seconds, in case your event handler does not terminate when it should.

    In contrast to Exercise 3, there should now be no deadline misses.

Solution

 

The solution of this exercise is provided as a "ready to build and run" NetBeans project: <lab_root>/realtimejava/solutions/exercise5/Ex5Soln.


Summary

 

In this exercise you've seen how the wait-free queues can be used to allow communication between no-heap entities and heap-using entities, without incurring any synchronization delays, or related GC delays, on the part of the no-heap entity. This is a fundamental aspect of providing effective real-time data communication.  

Back to top
To Summary