
An owner of a Public House suspects that the way in which the bar is operated means that customers have to spend too much time waiting to be served, and so tend to go elsewhere instead. In order to evaluate the situation a simulation of the Bar has been commissioned. Initially this simulation will simply run with text output describing the events as they occur in the Bar, and collect a minimal amount of statistical data.
Even if valuable to the owner, the simulation is not the main purpose of this assignment - indeed, if so was the case there are much better techniques for simulating than writing a concurrent program.
The requirement of this assignment is to write a program in which synchronization and communication takes place between several concurrent processes. It is intended to force you to solve (and not simply avoid) a range of interesting synchronisation problems.
The pub consists of a number of tables, a serving area containing a beer tap and a cupboard (holding the clean glasses, cups, milk, coffee and chocolate), a clock, a Landlord, a Barmaid, an Assistant, and a number of customers. The Pub is operated by the Landlord and Barmaid, whose job is to make and serve drinks to customers who order at the Bar in a first-come-first-served manner. Each customer may only order a single drink at one time. The Landlord and Barmaid can each serve only a single customer at a time, although others may be waiting to be served.
There is one order queue to both the Landlord and the Barmaid.
On receipt of an order, the Landlord or the Barmaid will refuse to serve a drink for any customer ordering after closing time (although closing time is not called by the Landlord in the traditional sense).
If it is not closing time, the Landlord and Barmaid serve customers independently. They serve beer, cappuccino, or hot chocolate, according to what the customers order. After having received an order they go to the cupboard and take out a glass or a cup, according to the type of drink ordered. If none are available, the Landlord or Barmaid will wait at the cupboard for a glass or cup to be placed back in the cupboard by the Assistant.
Beer: Obtain a glass (takes a set amount of time). Obtain the beer tap. Fill the glass (also takes a set amount of time).
Cappuccino: Obtain a cup (takes a set amount of time). Obtain each ingredients coffee and milk (each take a set amount of time). Mix the drink (take a set amount of time).
Hot chocolate: As for Cappuccino, but requiring milk and chocolate powder.
After making the drinks the ingredients are returned to the cupboard.
The Barmaid leaves at closing time. The Landlord will not leave the pub until all others (customers, Assistant, Barmaid) have left the pub.
At ten-minutes before closing time the Landlord calls "last orders" to warn the customers that closing time is soon.
The Landlord will be alerted by the Clock of this.
The Landlord may finish serving any customers order he started before attending to the “last orders” call.
The serving area consists of the beer tap and a cupboard. The cupboard (or bar disk) contains all the ingredients, and the cups and glasses.
There are a limited number of glasses and cups in the system.
Each of the ingredients (coffee, chocolate powder and milk) and the beer tap are separate resources. Each of these resources may only be used by one person at a time.
To serve a customer the Landlord or the Barmaid takes one glass or cup, collects all the ingredients they need, one at the time, makes and pours the drink, puts the ingredients back and finally serves the customer.
The customers enter the Pub at regular time intervals spread throughout the lifetime of the simulation.
On entering the Pub, each customer will order a drink, which will be served by either the Landlord or the barmaid and will then move to a favorite table. (It is assumed that all customers have a known favorite table to make the simulation simpler).
Each customer orders only one type of drink. The type of drink ordered is determined at random according to the set ratio. (See demands below).
Standing, they will drink the contents of their drink in a set time and then try to put the empty glass/cup down on the table.
If the table is full when a customer wants to put the glass down, the customer will have to wait for the table to be cleared by the Assistant before putting the glass down.
Having placed the glass on the table, the customer may then go back to the bar to order another drink until the customer has either drunk the set number of drinks (configured at compile time) or the Landlord refuses to give a drink because it is after closing time. In either case, the customer will leave the pub straight away.
Some of the beer-drinking customers exhibit the following additional behavior: when they hear the landlord call "last orders", if they are not currently drinking the last of their set number of drinks, they will finish their current beer instantly and go to the bar to order one and only one more.
There are a number of tables in the bar that the customer use to put down their glasses and cups after finishing their drink.
The tables have a limited number of units of space for cups and glasses. A cup takes up two units of space on the table, a glass takes up one unit.
There is no bound on the number of customers around any table. A customer never puts down his drink on a table unless it is finished.
It must be possible to fill a table. If there is only one free unit of space left on the table and a customer wants to put down a cup he may not block a second customer being able to put down his glass.
Tables mind their own bussiness: tables should be independent of each other. For instance, a delay in one table should not affect the other ones.
The Assistant has the job of clearing the glasses and cups away from each table in turn, washing them and placing them back in the cupboard once they are all washed.
It is assumed that the Assistant is able to transport as many glasses as necessary on each clearing-up round. Any glasses on a table are assumed to be empty - the Assistant does not check the glasses to see if they are empty before collecting them.
There is a set period of time allowed for the collection of each glass/cup , washing of each glass/cup, and replacement of each glass/cup in the cupboard.
After each collect-wash-replace cycle the Assistant is allowed a rest period (for a length configurable at compile time) before commencing another cycle.
The assistant continues this operation after closing time is reached, making his final round once all customers have left.
The clock in the bar serves two purposes.
It will alert the Landlord of both “last orders” and “closing time”. The Landlord will alert his customers when it is time for last orders.
The clock serves and updates the current time that each process uses to print their actions. This part is optional: processes can also use time stamps for statistics.
At the end of the simulation, i.e. when all other processes have terminated cleanly, the landlord should do some sanity checks of the Bar and print out some statistics on the run. The result of the sanity checks must be printed. You must
Check that the number of cups and glasses in the cupboard is the same as the initial number of cups and glasses.
Check that all tables are indeed empty.
Print out statistics on
Maximum/Average/Minimum waiting time for a customer .
Number of customers served.
As anyone in the UK will tell you, last orders is a serious problem. It's a problem with the pub lab too - namely getting the program to terminate cleanly once the landlord has called for last orders. Here are some hints of how to solve the problem - and how not to solve it.
Use a global variable that customers check before they order.
There are two major drawbacks with this solution:
Polling. This is a close relative of the evil busy-wait loop. It involves repeatedly attempting some action after some period of sleep. Polling might be a good way of solving some problems, but clearly this isn't one of them.
Customers are not interrupted. The main idea is that the customers that care about last call reacts instantly and finish their drinks. Gulp. This can't happen if they have to check a global variable.
This one works like the previous example, but you let the customers drink with small sips, checking once every second if last call have passed.
Once again, this is polling, and besides, this method doesn't help you understand anything more about concurrent programming - which is the main goal of the assignment. Customers are interrupted, but it's not a nice environment to have a beer if you have to check if the place is closing between every sip.
Before you start drinking, calculate the time left until last call, and don't sleep longer than that
This way is not very realistic. In concurrent programming, you hardly ever know in advance when things will happen. Using this solution, you wouldn't learn anything useful.
A potential problem is if you assume a strict client-server relationship between landlord and customer. One way around this limitation is to ensure that every customer that comes into the pub says "Hello" to the landlord and hands over a pointer to himself (or to a personal channel) to the landlord (who stores it in some structure). When they leave, they say goodbye, so he can remove that pointer again. When last call occurs, the landlord has a means to communicate with every customer. He also knows how many are in the pub. The details of what you do depend on the structure of your solution, and the programming language you are using.
Note that the process of drinking (for those thirsty customers) should be viewed as a timeout rather than a "sleep": a thirsty customer is waiting for a last orders call, and he times out when his drink is finished.
It might be tempting to use the Thread.interrupt() method to wake sleeping processes. This is a bad idea. Firstly, we have seen what a mess people can get into with this! Secondly, a behaviour which is present in every execution of the program is not exceptional, and is usually considered bad programming style to use an exception in such cases. In summary, don't use Thread.interrupt().
You must implement your simulation in JR. It is conceptually a better language than java for this task. Previous years' statistic (when using java has been allowed) has shown us that students make fewer errors in JR.
The documentation is as important as the laboration itself. You will be failed if you ignore this demand. There are some specific points you should address in your documentation of Lab 2.
Describe any modifications, assumptions and basic design decisions that have been made about the behavior of the various components of the simulation.
Identify all potential sources of deadlock in the problem specification and describe briefly how they are avoided in the implementation. Your documentation should include at least the following:
You should give an explanation of your implementation. If your code meets certain demands of readability (see below) an explaination of the tricky parts of the software suffices.
Your documentation must be in normal plain ASCII .txt-format.
The code structure has to be clear (even visually - use of indentation and newlines where necessary) and should not be hidden in extensive documentation of trivialities ("this is an assignment of A to B"). Especially the call structure has to be either obvious or documented.
The code should use helpful variable, data structure and function names (allows fewer comments).
There must be a class with only constants. One example of such a class can be found here.
The following constants must be available:
The Pub
Number of tables.
Amount of real world time that the pub stays open.
Cupboard
Initial number of glasses/cups.
Time to obtain/replace a glass/cup.
Time to obtain the different ingredients.
Table
Capacity of table.
Time to put down/pick up a glass or a cup.
Landlord/Barmaid
Time to make chocolate/cappuccino or pour beer.
Assistant
Time to wash a glass/cup.
Resting time between washing rounds.
Customer
Number of customers entering the bar. It should be possible to set this to zero.
The ratio of different types of drinkers, cappuccino, chocolate and, beer (both those who take one more when they hear last orders, and those who don't). It should be possible to set a drinking type to zero.
Drinking time at the table.
Number of drinks.
In order to see what is happening dynamically you must have output from the Customers, the Assistant, the Landlord, the Barmaid and the Clock reporting all their major events.
Add information about which process/thread is doing the output. This way you can see if a process/thread acts for another, which is strictly forbidden, but is a common error for Java solutions (objects are not processes!). An example of such incorrect behaviour is
Thread-Landlord : 21.31: Landlord: Pelle is served! main : 21.50: Landlord: Last orders! Thread-Customer-8 : 21.50: Kalle is going to order beer from Landlord.
Where you can see that not only the landlord thread but also the main thread is acting for the landlord.
Note that realistic time stamps are not required, it is fine to use
the java.lang.System.nanoTime() function to generate them.
You can set the name of a process like this:
Thread.currentThread().setName("Landlord");. Which would allow
doing output without specifying the name of who is doing the output by using
a method such as this:
public static void output(String message) {
System.out.println(
System.nanoTime() + ": " +
Thread.currentThread().getName() + ": " +
message);
}
Kill a thread or process. You may not use any of the following Java primitives:
Thread.stop
Thread.resume
Thread.suspend
Thread.interrupt
setDaemon
You may not use the JR.exit function.
If any of those primitives are found in your code, you will fail the assignment no matter the functionality of it.
Use any kind of polling. Read this link on what we consider to be polling
Solve the last orders problem in a manner forbidden in the description above.
Allow the clock to suffer from cumulative drift. (This only applies if you have chosen to implement your own ticker, which you do not have to.)
Divide up the code in classes. For example one for the Landlord, one for the Barmaid, etc..
Run your program without customers entering the pub. This should work if your solution is correct. The solutions should not be dependent on the events created by the customers.
Make very sure of who's actually doing the work. It easy to make some mistakes like leaving the Clock to call the “last call”-call and then actually finish the drinks for the customers! Make this easier for yourself by printing the name of the process performing an action.
Make sure you do not have
any busy-waiting loops. Look through your solution carefully
before handing it in. For example, you are likely to have busy waiting if you use
[]else-> inside of a
while (some_condition) {inni ... } statement in JR.
The use of semaphores (other than for controlling simple resources and basic mutex for statistics.) is strongly discouraged.
Use short delay times - there is no need for a simulation run to take more than 20-30 seconds.
You do not need to implement a ticker.