CS 537 Spring 2007
Programming Assignment 2
Frequently Asked Questions

Last modified Mon Feb 12 15:14:58 CST 2007
Q1:
If a trader needs a grain for which it is not the supplier, can it only swap its specialty grain for it, or can it swap any grain it has too much of. For example, suppose the wheat trader needs 5 bushels of barley but has only 10 bushels of rice, and no wheat, corn, or barley. Is it allowed to swap rice for the barley? Also if the wheat trader has 0 barley, 0 corn, 5 wheat, and 10 rice, should it swap wheat for the barley because wheat is its specialty, or rice for the barley because it has more rice than wheat?

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 trader should only offer its specialty in swaps. For example, if the wheat trader needs barley, it may attempt to swap wheat for barley, but not rice. 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 brewers.

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

I used the second approach. I gave each Trader a surplus field that maintains the difference between the total amount of the “specialty” grain received (from the supplier 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 wheat trader needs barley, it only swaps wheat, 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 Supplier and Brewer 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 Supplier cycles, the amount a grain to produce on each cycle, and the trader contacted by a brewer, are controlled by a pseudo random number generator. Each time you run your program, the 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, traders, and brewers and checks that the books balance.

When the main program has created and started the supplier and all the brewer threads, it waits for the supplier to terminate, which happens when it has completed the specified number of rounds of production. The main program then sleeps for three seconds (giving the system a chance to settle down), interrupts all the brewer threads, and finally gathers and prints information about the state of the system. The auditing code calls Supplier.getProduction to determine the total amount of each grain produced, Brewer.getConsumption to find out how much was consumed, and Trader.getAmountOnHand to see how much was left over. If all is well, the amount produced of each grain 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. Before printing out the consumption statistics, the main program interrupts each brewer 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. The main loop in Brewer.run()() catches InterruptedException, prints a message, and returns. Your code should not try to catch InterruptedException, if it does, your your program may just hang, since the main method does a join on each of the brewer 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 brewer calls get([3W, 8B, 6R, 0C]) - say of the wheat trader who has [5W, 2B, 7R, 0C]
  2. we decrement wheat by 3 because we have enough.
  3. we then see that we don't have enough barley, nor do we have enough extra wheat to swap for it, so we call a waitForSupplier function that essentially waits until the supplier has given us enough to swap.

    Two problems can occur here -

    • The program exits so we now have a deficit of 3 wheat.
    • If we wait to decrement until all grains 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 Trader maintains a field called surplus, which records the difference between the amount it has on hand of its specialty grain and the amount it has earmarked for brewers. If this quantity is positive, the trader may give some or all of it to other brewers or traders. Otherwise, it will refuse. Note that the “surplus” may be negative, indicating that the trader is over-committed.

Here's your scenario with this technique added:

1. a brewer calls get [3W, 8B, 6R, 0C] - say of the wheat trader, who has [5W, 2B, 7R, 0C]
Let's assume this is the first brewer to call, so the wheat surplus is 5 before the call.
2. we decrement wheat by 3 because we have enough.
We decrement surplus by three, giving 2.
3. we then see that we don't have enough barley, nor do we have enough extra wheat to swap for it, so we call a waitForSupplier function that essentially waits until the supplier has given us enough to swap.
Since surplus is 2, we could swap two wheat for barley, or we may just decide to wait because we need 6 bushels, not 2. Let's assume we wait.
Two problems can occur here -
    • The program exits so we now have a deficit of 3 wheat.
If the program terminates at this point, you will still report that you have 5 wheat, so there's no imbalance.
    • If we wait to decrement until all grains are ready for sale then someone else can grab them.
Nobody can grab the wheat grain because the surplus is zero. Other brewers could grab other grains 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 grain. To complete the scenario above,
4. The supplier gives us 5 bushels of wheat. We now have [10W, 2B, 7U] and our surplus is 7.

5. We call the barley trader offering to swap 6 bushels of wheat for barley. 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 bushels of barley and update our state to [4W, 8B, 7R, 0C]

7. We now have enough to satisfy the brewer's request. We subtract it from our current amount on hand, yielding [1W, 0B, 1R, 0C].

8. The brewer's call to get() returns, and it adds [3W, 8B, 6R, 0C] 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 supplier stops producing for a while before terminating the program so all brewer 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 trader 0 (the wheat trader) wants to swap 2 bushels of wheat for 2 bushels of barley from trader 1 (the barley trader). Depending on how your write your program, the sequence of operations might look like this:

Suppose tw = P2.specialist(Grain.WHEAT) and tb = P2.specialist(Grain.BARLEY).

In tw.get():


    ...  // wait until I have 2 bushels of wheat to swap
    onHand.change(Grain.WHEAT, -2) // Load two bushels of wheat onto the truck
    tb.swap(Grain.WHEAT, 2);       // ... and ship it to the barley trader
    onHand.change(Grain.BARLEY, 2);// Unload two bushels of barley from the truck
and in tb.swap(Grain.WHEAT, 2):

    ...  // wait until I have 2 bushels of barley
    onHand.change(Grain.WHEAT, 2)   // Unload two bushels of wheat from the truck
    onHand.change(Grain.BARLEY, -2) // ... and replace it with two bushels of barley
    return;
[Note: In the actual code, the calls to onHand.change() would be inside some syncronized method rather than directly inside the unsynchronized method get() as this simplified code would imply.]

When trader tb does onHand.change(Grain.BARLEY, -2), the system will be briefly out of balance because two bushels of barley have “disappeared”, but immediately after the procedure returns, tw will do onHand.change(Grain.BARLEY, 2), restoring the balance. However, when trader tw does onHand.change(Grain.WHEAT, -2), the missing wheat will not be restored until trader tb finishes the operation labeled “// wait until I have 2 bushels of barley”. This delay could be arbitrarily long, and the the supplier may decide to terminate the program during this period.

The problem in this case could be fixed either by moving onHand.change(Grain.WHEAT, -2) after the swap() call in tw or by moving onHand.change(Grain.WHEAT, 2) before the wait in trader tb.


Q7:
Our solution to the problem involved a surplus variable (as you suggested); however, as the number of brewers 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 traders have reasonable stock piles of their specialties. In other words, when the supplier isn't producing, our solution isn't necessarily “live.” Our program isn't deadlocked; the traders are waiting for an event caused by the Supplier, but the Supplier 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 brewer, at the top of its main loop in Trader.get(), to check whether the Trader has sufficient stores to completely satisfy its request, regardless of the value of “surplus”. For example, consider a wheat trader with 3 of each grain that gets two requests from brewers: [3W, 4B, 0R, 0C] and [3W, 3B, 0R, 0C] (in that order). The first request would drop the surplus to zero and block the brewer. The second brewer, finding the surplus at 0 would change it to -3 and also block. The modified algorithm would allow the second brewer to see that there was enough of all four grains to satisfy its request and allow it to sneak ahead of the first brewer.

This modification is not enough to prevent the kind of “deadlock” you mention, in all cases. Consider the following scenario: Each trader has one unit of its specialty and nothing else. Each receives a request [1W, 1B, 1R, 1C] from a brewer. 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 trader would have a surplus of zero and thus refuse to swap for the other missing grains. Suppose I allowed a trader to swap even though its stock of its specialty has already been requested by brewers. The wheat trader might try to swap wheat for barley just as the barley trader is trying to swap barley for barley and the barley trader is trying to swap barley for wheat. 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.