CS 537
Programming Assignment 2
Frequently Asked Questions

Last modified Wed Mar 31 08:57:54 CST 2004
Errata

The answer to Q6 should read

The problem in this case could be fixed either by moving onHand[0] -= 2 after the swap call in broker 0, or by moving onHand[0] += 2 before the wait in broker 1.
Note also that the changes to onHand need to be in a synchronized method called by get rather than directly in get as the example code would imply.
Q1:
If a broker needs a metal for which it is not the supplier, can it only swap its specialty metal for it, or can it swap any metal it has too much of. For example, suppose the gold supplier needs 5 ounces of uranium but has only 10 ounces of platinum, and no gold or uranium. Is it allowed to swap platinum for the uranium? Also if the gold supplier has 0 uranium, 5 gold, and 10 platinum should it swap gold for the uranium because gold is its specialty, or platinum for the uranium because it has more platinum than gold?

A:
There is a lot of room for variation and experimentation in this project, so there is not necessarily one "right" answer to questions like this.

I would suggest that a broker should only request swaps in its specialty. For example, if the gold supplier needs uranium, it may attempt to swap gold for uranium, but not platinum. This is the way I wrote my solution, it seems to work, and it's relatively simple. If you like, you can experiment with a more aggressive strategy and see if you get better results, where "better" means that more stuff gets delivered to consumers.

There's also a question of how much to swap and when. For example, suppose the gold supplier has 6 ounces of gold but no uranium and receives a get order for 4 gold and 4 uranium. Should it try to swap gold for uranium, and if so, how much? Some possibilities are

I used the second approach. I gave each Broker a surplus field that maintains the difference between the total amount of the "specialty" metal received (from the refiner and from swaps) and the total requests received for it (from swap calls and get orders). This "surplus" may be negative (a deficit). If the gold supplier needs uranium, it only swaps gold, and never offers to swap more that the current value of surplus.

Q2:
How do we know if our program is working correctly?

A:
In the works of Dijkstra, "Testing can only show the presence of errors, never their absence." Thus the only sure way to guarantee there are no errors in your program is to prove it correct :-). However, good testing can help to weed out the more obvious bugs.

There are two command-line options that can help with debugging. You can invoke the program as


    java Project2 -v 3 100
The -v means "verbose". It will print lots of messages about significant events in the Refiner and Consumer threads. You can add your own debugging output with calls to

    Project2.debug(message)
The message will only be printed if -v was specified. Each message will be preceded by an indication of which thread was running when it was printed.

You may also supply the -r flag to make the output less random. Various choices in the program, such as the time between Refiner cycles, the amount a metal to produce on each cycle, and the broker contacted by a consumer, are controlled by a random-number generator. Each time you run your program, the random-number generator starts with a different "seed", so your results will be different each run, even if you don't change anything in your program. If you specify -r, the same seed is used each time, so there will be much less variation. You still may see a slight variation due to random timing effects, but it should be much less.

See also Q3.


Q3:
The output generated by -v is an awful lot to wade through. Isn't there a simpler test to see if things are working OK?

A:
Just before the main program terminates, it performs an "audit". It gathers some data from the supplier, brokers, and consumers and checks that the books balance.

When the refiner has completed the specified number of rounds of production, it sleeps for three seconds and then gathers and prints information about the state of the system. It calls Supplier.getProduction to determine the total amount of each metal produced, Consumer.getConsumption to find out how much was consumed, and Broker.getAmountOnHand to see how much was left over. If all is well, the amount produced should exactly equal the sum of the amount consumed and the amount left on hand. The difference between these quantities is printed near the end of the output. If it isn't zero, you should investigate.

Another feature should be helpful in tracking down deadlocks. After the program has printed out the consumption statistics, it interrupts each consumer thread by calling Thread.interrupt(). Most likely, the thread will be blocked on a wait() call. When it gets interrupted, it will throw an InterruptedException. Each wait() in your program should be inside a try statement such as


    try {
        wait();
    } catch (InterruptedException e) {
        Project2.debug(... whatever ...);
        throw new StopThread();
    }
The output of the Project2.debug call should indicate why the thread is waiting, what it's waiting for, etc. Be sure to include the throw statement at the end of the catch clause. If you don't add this statement, your program will just hang, since the main method does a join on each of the consumer threads before exiting.

Q4:
We are still having a problem with the treads exiting - almost every time there is some deficit. Here is an outline of the problem:
  1. a consumer calls get [3G, 8P, 6U] - say of the gold broker who has [5G, 2P, 7U]
  2. we decrement gold by 3 because we have enough.
  3. we then see that we don't have enough platinum, nor do we have enough extra gold to swap for it, so we call a waitForRefiner function that essentially waits until the refiner has given us enough to swap.

    Two problems can occur here -

    • The program exits so we now have a deficit of 3 gold.
    • If we wait to decrement until all metals are ready for sale then someone else can grab them.
We seem to keep going in circles - both giving very unpredictable results. Is there something obvious that we are missing - or how should we deal with decrementing? Any info would be helpful.

A:
This is an accounting problem, so it is best solved by a technique used by former Enron accountants: keep two sets of books, one for day-to-day operations and one for the auditors. In real life, this accounting technique can land you in jail, but in this project, it can help solve your problems. :-)

The Broker maintains a field called surplus, which records the difference between the amount it has on hand of its specialty metal and the amount it has earmarked for consumers. If this quantity is positive, the broker may give some or all of it to other consumers or brokers. Otherwise, it will refuse. Note that the "surplus" may be negative, indicating that the broker is over-committed.

Here's your scenario with this technique added:

1. a consumer calls get [3G, 8P, 6U] - say of the gold broker, who has [5G, 2P, 7U]
Let's assume this is the first consumer to call, so the gold surplus is 5 before the call.
2. we decrement gold by 3 because we have enough.
We decrement surplus by three, giving 2.
3. we then see that we don't have enough platinum, nor do we have enough extra gold to swap for it, so we call a waitForRefiner function that essentially waits until the refiner has given us enough to swap.
Since surplus is 2, we could swap two gold for platinum, or we may just decide to wait because we need 6 ounces, not 2. Let's assume we wait.
Two problems can occur here -
    • The program exits so we now have a deficit of 3 gold.
If the program terminates at this point, you will still report that you have 5 gold, so there's no imbalance.
    • If we wait to decrement until all metals are ready for sale then someone else can grab them.
Nobody can grab the gold metal because the surplus is zero. Other consumers could grab other metals if you have enough to completely satisfy their requests. It's not clear to me that that would be a problem (it may hurt fairness but improve overall performance). If you want to prevent it, you could keep track of the surplus in each metal. To complete the scenario above,
4.
The refiner gives us 5 ounces of gold. We now have [10G, 2P, 7U] and our surplus is 7.
5.
We call the platinum broker offering to swap 6 ounces of gold for platinum. Before the call, we subtract 6 from the surplus giving 1. We may again block, but as before, if the program terminates while we are blocked, everything will balance.
6.
We get back 6 ounces of platinum and update our state to [4G, 8P, 7U]
7.
We now have enough to satisfy the consumer's request. We subtract it from our current amount on hand, yielding [1G, 0P, 1U].
8.
The consumer's call to get() returns, and it adds [3G, 8P, 6U] to its record of total consumption.
If the program happens to terminate between steps 7 and 8 (or between 5 and 6) there will still be a deficit. However, that's very unlikely to happen. The refiner stops producing for a while before terminating the program so all consumer threads are almost certainly blocked waiting for something, not moving between steps.

Q5:
We we have to use a variable surplus as described in Q4?

A:
No. That was simply a suggestion for one way to avoid the problem mentioned in the question. There are solutions that do not use "two sets of books".

Q6:
I followed the suggestions in Q4 and I'm still getting a deficit recorded at the end. What am I doing wrong?

A:
Many problems like this are caused by failure to follow one simple rule: Before a thread calls wait() it should ensure that everything "balances." For example, consider the situation in which broker 0 (the gold broker) wants to swap 2 ounces of gold for 2 ounces of platinum from broker 1 (the platinum broker). Depending on how your write your program, the sequence of operations might look like this:

In broker[0].get():


    ...  // wait until I have 2 ounces of gold to swap
    onHand[0] -= 2;           // Load two ounces of gold onto the truck
    broker[1].swap(GOLD, 2); // ... and ship it to the other broker
    onHand[1] += 2;           // Unload two ounces of platinum from the truck
and in broker[1].swap():

    ...  // wait until I have 2 ounces of platinum
    onHand[0] += 2; // Unload two ounces of gold from the truck
    onHand[1] -= 2; // ... and replace it with two ounces of platinum
    return;
When broker 1 does onHand[1] -= 2 the system will be briefly out of balance because two ounces of platinum have "disappeared", but immediately after the procedure returns, broker[0] will do onHand[1] += 2, restoring the balance. However, when broker 0 does onHand[0] -= 2, the missing gold will not be restored until broker 1 finishes the operation labeled "// wait until I have 2 ounces of platinum". This delay could be arbitrarily long, and the the refiner may decide to terminate the program during this period.

The problem in this case could be fixed either by moving onHand[0] -= 2 after the wait in broker 0, or by moving onHand[0] += 2 before the wait in broker 1.


Q7:
Our solution to the problem involved a surplus variable (as you suggested); however, as the number of consumers becomes large, surplus ends up being a large negative number. Because swaps and order fulfillments are based on the value of surplus, when the number of iterations runs out and surplus is negative, trading stops even though the brokers have reasonable stock piles of their specialties. In other words, when the refiner isn't producing, our solution isn't necessarily "live." Our program isn't deadlocked; the brokers are waiting for an event caused by the Refiner, but the Refiner isn't causing any more events. Is this case of non-liveness a bug or a feature? ;-) I imagine it could be solved by putting the order and swap requests into some kind of priority queue, but that would require redesigning the entire program from the ground up.

A:
You raise a very interesting question. I would agree that technically, the system isn't "deadlocked" if more production could unwedge it. However, I also agree that a system that stops running even though there is excess capacity seems "wrong". One measure that comes to mind to mitigate (but not solve) the problem would be for each consumer, at the top of its main loop in Broker.get(), to check whether the Broker has sufficient stores to completely satisfy its request, regardless of the value of "surplus". For example, consider a gold broker with 3 of each metal that gets two requests from consumers: [3G, 4P, 0U] and [3G, 3P, 0U] (in that order). The first request would drop the surplus to zero and block the consumer. The second consumer, finding the surplus at 0 would change it to -3 and also block. The modified algorithm would allow the second consumer to see that there was enough of all three metals to satisfy its request and allow it to sneak ahead of the first consumer.

This modification is not enough to prevent the kind of "deadlock" you mention, in all cases. Consider the following scenario: Each broker has one unit of its specialty and nothing else. Each receives a request [1G, 1P, 1U] from a consumer. From a "God's eye" view it is clear that one of the requests could be satisfied, but it is not at all clear (at least to me) how to program the system to find such a solution. In my solution, each broker would have a surplus of zero and thus refuse to swap for the other missing metals. Suppose I allowed a broker to swap even though its stock of its specialty has already been requested by consumers. The gold broker might try to swap gold for platinum just as the platinum broker is trying to swap platinum for uranium and the uranium broker is trying to swap uranium for gold. Once again: deadlock! I have no doubt that a solution that avoid this problem is possible (it would require some sort of symmetry breaking), but it would be far beyond the scope of this project.