Please note: this document is under development

Using the Producer Consumer Simulator

This document was last modified on February 15, 2005 and reflects changes made to version 0.42 of the Producer Consumer Simulator.


Table of Contents

Overview
System and User Requirements
Starting the Simulator
Basic Operation
Modes of Operation
Stopping Conditions
Local Logging
Logged Data
Other Buttons
Graphs


Overview

This simulator allows the user to experiment with a bounded buffer synchronization problem in the context of a single producer and a single consumer. The critical sections of the code are not protected and errors due to lack of synchronization can occur. The simulator allows you to explore issue related to the probability of failure and the resulting consequences.

In the bounded buffer problem, two (or more) processes are sharing a bounded buffer with at least one process inserting items into the buffer and at least one process removing items from the buffer.

The buffer is a queue of size n which is implemented as a circular queue in an array. The variable in is the index of the next free slot while out is the index of the last full slot. The variable counter contains the number of filled slots.

The producer executes the following code in a loop:

      nextp = produce();
      while (counter == n)
           yield();
      buffer[in] = nextp;
      in = (in + 1) % n;
      counter++;

The consumer executes the following code in a loop:

      while (counter == 0)
           yield();
      nextc = buffer[out];
      out = (out + 1) % n;
      counter--;
      consume(nextc);

The yield() statement removes the process from the CPU and puts it in the ready queue. It is assumed that some type of round robin scheduling is done in which all other processes in the ready queue will execute before this process can get back to the CPU. In a more realistic simulation, this would be replaced by a wait for notification of some kind. Since we are analyzing a different section of the code, the simpler (and less efficient) yield() is sufficient for our purposes.

If there are only one producer and one consumer, the only critical sections are the statements counter++ and counter--. In a typical RISC-like implementation each of these would be implemented with three assembly language instructions consisting of a load, an arithmetic operation on a register, and a store. If either one of these is interrupted and the other process gets the CPU and modifies the counter, the result may be a counter value which is not correct. The result could be an item which is never processed, an item processed twice, or a reference to an uninitialized variable is made.

The purpose of this simulator is to explore the likelihood of interrupting a critical section and what happens when such an interrupt occurs.


System and User Requirements

System requirements:

User requirements:

If you are only interested in this simulator, you can download a zip file pc.zip that contains all of the files that you need.

Running in a non-Windows environment:
If you installed the simulator by copying the files from a CD, the scripts may not have the correct permissions to run. The convert and runpc files should be executable.
The ASCII files distributed with this distribution are in the Windows format in which lines end in a carriage return followed by a line feed. It may be more convenient to have the carriage returns removed. If you are running under UNIX, Linux, or Mac OSX, it may be more convenient to have the carriage returns removed. You can remove all of the carriage returns from these files by executing the convert script.


Starting the Simulator

If you are running from an image of the simulator CD, start in the ring subdirectory of the run directory. If you unzipped the PC.zip file in a single directory, start in that directory. In either case, you can start the simulator from a command window by executing typing runpc. In a Windows environment you can also just click on the file runpc.bat.

If you do a custom installation, you can put the jar files anywhere you want. Modify the runpc.bat (Windows) or runpc (Unix, Linux, Mac OSX) file so that the JARDIR1 or JARDIR2 variables points to the location of the jar files.

If the simulator does not start, make sure you have the Java runtime executables in your path. In a command window, execute:
java -version
and make sure this displays a version later than 1.0.


Basic Operation

Start the simulator. If all goes well the simulator will start up and look something like Figure 1.

Figure 1: The main simulator window.

The top of the screen shows the shared buffer and the shared variables: in, out, n and counter.

Below this are tables containing the producer and consumer code with the number of times the process lost the CPU while executing each instruction, the number of times the instruction was executed, and the number of cycles required by the instruction. Also shown are the values of the register R1, the number of cycles executed and the number of items processed.

This is followed by a collection of buttons for running the simulator, and information about the total number of cycles, the quantum time remaining before the process is removed from the CPU, and the internal consistency state of the simulator.

Lastly, there is a window in which informatory messages are displayed.


Modes of Operation

The simulator can be run in 4 modes controlled by the last column of buttons. At any time one of the two processes, Producer or Consumer is active. The active process has its title highlighted in cyan and the word Active appears after the title. You can switch active processes by pushing the Switch Processes button. In all of the modes of operation except for single step, the active process will change when its quantum expires.


Stopping Conditions

There are three type os stopping conditions: Terminate on Failure, Terminate after Max Cycles, and Terminate on Failure or after Max Cycles. Which type is used is controlled by the Run Limit button which is the first of the buttons in the middle column. There are two values of Max Cycles controlled by the second button in the middle column. One value determines the maximum number of cycles to run in the Terminate after Max Cycles and Terminate on Failure or after Max Cycles modes. A second, usually larger, value controls the running in the Terminate on Failure mode. If no failure occurs after this number of cycles, the user is given the option of continuing the run or aborting.

There are two types of protocol failure that can be used for stopping conditions. These are controlled by the last of the buttons in the middle column, and are called Internal Inconsistency and External Inconsistency. Internal Inconsistency refers to a problem which occurs when a critical section is interrupted and the shared variables are no longer consistent. For example, the counter may indicate one fewer or one greater number of items in the buffer than are actually there.

External inconsistency refers to having the program process an incorrect item, by consuming an item twice, by skipping an item, or by consuming an item referenced by an uninitialized buffer entry. External inconsistencies are always preceded by at least one internal inconsistency, but it might take a very large number of cycles before an internal inconsistency causes an external inconsistency. It is even possible for an internal inconsistency to correct itself before an external inconsistency occurs.


Local Logging

The simulator gathers statistics and will generate tables and graphs which can be put into a log file. Log files are in HTML format and can be displayed by or printed from a standard browser. The fourth columns of buttons controls the logging functions of the simulator.

Initially, the buttons in column 4 are used to control the logging functions before the log file is open or after it is closed. The first button opens the log. When the log file is successfully opened most of the buttons change their function to control the log functions appropriate when logging is in progress.

While the log is closed, the second button toggles between the modes Replace Old Log and Append to Log. In the former case, opening the log file overwrites any file with the same name that already exists. In the latter case, the new log is appended to the old one. The third button can be used to change the name and directory used for storing the log file. Pushing this button pops up a dialog box in which the user can change the directory and log file names. The names of the files for storing the graph image files created by the simulator can also be changed.

The Show Log button can be used to pop up a browser window containing the log file generated. Currently this feature is not supported when the simulator is run locally.

After the log is successfully opened, the Open Log button changes to Close Log, the second button changes to Log Comment, and the third button changes to Stop Log. If there is data to log, the 4th button is Log Current Data.

Close Log terminates logging and closes the log file. Stop Log temporarily stops adding information to the log file but keeps the log file open. When pushed, this button changes to Start Log which resumes the logging.

The Log Comment button pops up a window which allows the user to enter comments into the log file.


Logged Data

When the log file is opened, the producer and consumer code is shown along with the number of cycles required for each instruction. All log entries are time stamped.

An entry is made in the log file whenever the log file is opened or closed or logging is stopped or restarted. A log entry is made whenever a run or multiple runs are started or completed. Statistical information is logged whenever a run completes. Graphs can be displayed using the Draw Graph button in the second column. When a graph is drawn you have the option of inserting it in the log file by pushing the Log button on the graph.

When a single run is done a table is produced similar to the one in Figure 2. For each line of producer or consumer code, the number of times that line was interrupted and executed is given. Interrupted refers to having the process lose the CPU while the program counter points to one of the instructions corresponding to that statement.

At the bottom of the table there are eight pieces of data including the number of cycles executed by the Producer and Consumer, the number of items processed by each, the number of items currently in the shared buffer, the size of the shared buffer, the quantum and the total number of cycles executed.

The last line of the table indicates whether the data is internally consistent, that is if the number of items buffered is the difference between the number produced and the number consumed.


Thu Nov 04 08:52:14 CST 1999 Starting one run until internal failure
Thu Nov 04 08:52:14 CST 1999 Completed

Producer
interrupted executed code cycles
7 13 nextp = produce(); [50,100]
3 13 while(counter == n) 8
0 0             yield(); 7
1 13 buffer[in] = nextp; 7
1 12 in = (in + 1)%n; 7
0 12 R1 = counter 1
0 12 R1 = R1 + 1 1
0 12 counter = R1 1

Cycles: 1209 Processed: 12
Buffered: 0Buffer Size: 10
Consumer
interrupted executed code cycles
0 14 while(counter == 0) 8
1 3            yield(); 7
0 11 nextc = buffer[out]; 7
1 11 out = (out + 1)%n; 7
0 11 R1 = counter 1
0 11 R1 = R1 - 1 1
1 11 counter = R1 1
5 10 consume(nextc); [50,100]

Cycles: 992 Processed: 11
Quantum: 100Total Cycles: 2201
Items Produced - Items Consumed = 1 != 0 = Number Buffered

Figure 2: A single run, stopping on internal failure.



The result of logging multiple runs is shown in Figure 3.

There are three tables shown. The first one is similar to the one in Figure 2, but the last line gives the average number of cycles executed.

The second table gives statistics about the number of cycles executed. The table shown was from a run which terminated on internal failure. In this case there are 6 lines of data. For each, the total number of cycles is given followed by the number of runs contributing to that total. This is followed by the average, minimum, maximum and standard deviation of the cycles for the runs considered.

The lines in the table correspond to the total number number of cycles, the number of cycles executed by the Producer, the number of cycles executed by the Consumer, the number of cycles until an internal failure, the number of cycles until an internal failure by the Producer, and the number of cycles until an internal failure by the Consumer. Each run will end in one of these types of failures.

The third table summarized the types of failures from the second table. In Figure 3 since the runs terminated when an internal failure occurred, all 10 runs ended in internal failure. Some were from a producer failure and some from a consumer failure. There were no external failures.



Mon Nov 01 12:43:54 CST 1999 Starting 10 runs until internal failure
Mon Nov 01 12:43:56 CST 1999 Runs Completed
Producer
interrupted executed code cycles
260 348 nextp = produce(); [50,100]
30 347 while(counter == n) 8
0 0             yield(); 7
26 347 buffer[in] = nextp; 7
23 346 in = (in + 1)%n; 7
2 346 R1 = counter 1
0 346 R1 = R1 + 1 1
4 346 counter = R1 1

Cycles: 34824 Processed: 346
Runs: 10Buffer Size: 10
Consumer
interrupted executed code cycles
26 357 while(counter == 0) 8
5 24            yield(); 7
17 333 nextc = buffer[out]; 7
20 333 out = (out + 1)%n; 7
3 333 R1 = counter 1
4 333 R1 = R1 - 1 1
3 333 counter = R1 1
236 323 consume(nextc); [50,100]

Cycles: 32970 Processed: 333
Quantum: 100Total Cycles: 67794
Average Number of Cycles: 6779.40

CyclesTotalNumberAverageMinimumMaximumSD
Total67,794106,779.401,39712,4103,937.87
Producer34,824103,482.408066,2591,956.61
Consumer32,970103,297.005916,1511,981.47
Internal Failure67,794106,779.401,39712,4103,937.87
Internal Producer Failure31,080310,360.007,74312,4101,947.02
Internal Consumer Failure36,71475,244.861,39710,5693,560.69

FailureNumberPercentage
Internal10100.00
Internal in Producer330.00
Internal in Consumer770.00
External00.00
External Skip Item00.00
External Repeat Item00.00

Figure 3: Multiple runs, stopping on external failure.



Figure 4 shows the last two tables from a run until an external failure occurred. The first of these has 3 additional lines compared to the corresponding table from Figure 2. These correspond to external failures. An External Repeat Failure indicates that an object was processed twice while an External Skip Failure indicates an item not processed at all by the Consumer.



CyclesTotalNumberAverageMinimumMaximumSD
Total952,4561095,245.603,204217,73173,456.01
Producer477,6271047,762.701,615109,26436,778.64
Consumer474,8291047,482.901,589108,46736,678.13
Internal Failure26,848102,684.809305,4701,172.59
Internal Producer Failure5,43231,810.679302,415636.96
Internal Consumer Failure21,41673,059.431,9235,4701,150.02
External Failure952,4561095,245.603,204217,73173,456.01
External Skip Failure372,6643124,221.3376,404217,73166,127.01
External Repeat Failure579,792782,827.433,204189,91172,939.56

FailureNumberPercentage
Internal10100.00
Internal in Producer330.00
Internal in Consumer770.00
External10100.00
External Skip Item330.00
External Repeat Item770.00

Figure 4: Multiple runs, stopping on external failure.



Other Buttons

Other buttons in the second column include the Quantum button which displays the maximum number of cycles one of the processes can execute before yielding to the other one. Pushing the button allows the user to change the value of the quantum.

The next button in the second column shows and allows the user to modify the number of runs performed by the Multiple Runs button.

In the first column, the Quit button will exit the simulator if this is allowed. In some Java implementations, applets are not allowed to terminate themselves and this button does not work.

The Reset button resets the simulator to its initial state. The Fast button, when pushed changes to a Slow button which causes a user-settable delay between cycles so that the user can monitor the progress. Pushing the ! on the right side of this button allows you to change the delay which is measured in milliseconds. Pushing the Slow button changes it back to a Fast button.

The Help button pops up a window giving context-sensitive help. This feature is not yet fully functional.


Graphs

Currently, the simulator log supports only one type of graph, a histogram of the number of cycles until failure. Pushing the Draw Graph button at the bottom of the second column of buttons draws this histogram. The results of all runs are shown with a different color used for each run. You can determine which subset of all runs will be graphed by pushing the Edit Data button at the bottom of the first column of buttons. This pops up a table of all runs. By clicking on the Log button of a run it will change the button to No Log and prevent it from being graphed the next time Draw Graph is pushed.

If the last runs used external inconsistency for termination, you can include information pertaining to the number of cycles until internal inconsistency with the Include Internal Data button which is just above the Draw Graph button.