A wide range of claims is being made about TestDrivenDevelopment, TestFirstDesign. Not least among which is that the tests are driving your design. Ok, I said to myself, it's always good to learn something new. So I set out to look here and there for examples, and my disappointment couldn't have been greater seeing all these wide claims being made by absolutely the most trivial examples (including the TDD book). How on Earth can anybody learn something real from such examples ? So here I posted this challenge initially on usenet. The idea is to take the well known DiningPhilosophers problem and drive home a solution through TestDrivenDevelopment, TestFirstDesign. The problem is well known and almost all books on concurrency, parallel and/or distributed programming discuss it and some present a full solution. So I added the rule that using any of the known solutions is fair game (we don't want to reinvent the wheel, right ?) as long as you can drive home the idea that one can reach the design by progressing through the tests, i.e. the design decision should be the results of failing tests that you turn them green. In addition to the standard requirement I added the additional requirement that the scheduling of philosophers to their portions of food should be fair. Given the fact that the underlying multithreading on OSes like Linux Windows and languages like Java, C# doesn't offer too much fairness guarantees, I said that the fairness we can offer is that no philosher should be unnecessarily disadvantaged in comparison to the others as far as our code is concerned ( every philosopher should run in his own thread and OS scheduling might put it at a slight disadvantage if we analyze data for relatively short time spans). This was my initial requirements. What was unstated, but assumed was that DP is not about simulating some philosophers, but about granting access to shared resources to do some real work. As such, I consider that a solution that reduces throughput unnecessarily (the number of meals served) is a not a great design. I thought the problem is a good test bed for TDD because it is relatively easy to come up with a solution, it is small but not trivial, and it is concurrent, and we all know that concurrent programs are anywhere from hard to impossible to test. -- CostinCozianu ---- RonJeffries posted an article on a proposed solution: http://www.xprogramming.com/xpmag/acsDiningPhilosophers.htm. Given the fact that his philosophers do a Thread.sleep(...) instead of real work, I took that he looked more into the simulation problem, rather than the real work scenario. Nevermind the throughput, I consider his article a failure on 2 counts: * the worst failure is quote: ''Well, I think we're done. The Philosophers seem to run in their own threads, and seem to be getting fed. The system isn't locking up as far as I can tell, and no philosopher ever timed out.''. This is precisely the wrong attitude one should take towards his code. It is against the ProofObligation principle, to say the least. Actually, he missed the rather trivial fact that his solution will never deadlock because it's synchronizing on only one object. * Another failure, is that he later claimed that the fairness is guaranteed by the fact that philosopher sleep random amount of times. That's a design decision he got it from reading some articles on the web, he mentioned. That's within the rule of the game. But from the point of view of exemplifying how TDD drives home the design it is a failure. Here's the quote: ''I keep getting the same numbers. That's a little odd, sounds too deterministic. But everyone is waiting the same amount of time. Let's randomize that just for fun.''. The most crucial design decision is not the result of some failing tests, but taken rather randomly. I later mentioned to him, that I consider his design a bad design because it might decrease throughput unnecessarily. He's not agreeing to this conclusion, but he didn't bother to do any measurement either. ''Maybe you (or Ron) should specify exactly what the throughput requirements are, so that appropriate tests can be written to drive the design. Other "fairness" requirements should be more rigorously specified as well. It is unfair to criticize TDD if you don't provide specific criteria to test against. It was also a mistake for Ron to accept the challenge without a more specific problem description--as you say, a lot of his assumptions and decisions seemingly come from nowhere.'' Well, it was my mistake and I admitted that. I didn't specify initially that throughput is of concern. I believe it should be on everybody's mind that throughput is always one of the concerns and DP wasn't invented just to play some arbitrary simulation. It is, I think, a drawback of XP that they don't focus as much on requirements (elicitation, validation, etc), instead they just want to pass the buck to the customer. I'm not willing to specify a hard figure or anything precise at all about the throughput. As far as I understood from a recent dicussion with RobertCecilMartin, that would be a dead end for their XP method. They want me as a customer to sign off on specific executable acceptance tests, and they want to only take responsibility for passing those tests irrespective of what real bugs might happen in the software. That's absurd in the case of concurrent software. As a counter example, I asked them to play my customer (I'll work for free) and to write me acceptance test for the mutual exclusion problem. I'll make sure that I will deliver a flawed piece of software that will pass their tests consistently. That is another point of the challenge to simulate a situation in real life. Let's say I implement the philosophers, I know my job very well in that regard, but I know almost nothing of concurrent systems. I want to outsource, and I want to be satisfied with the results. It is the contractor's job to keep me satisfied and it is good business to deliver quality software to customers. If I get a software that I quicly test and find out that it's 100 times or slower than my amateurish and dumb implementation, then you can bet what our relationship will be in the near future. ------ So given these misunderstandings I decided to write the challenge down as Java code. I picked up Java because arguably everybody and their grandmothers is familiar with it. So here it is: //Philosophers.java //----- package testdp; /** * @author ccozianu */ public interface Philosopher extends Runnable { public void eat(Object forkLeft, Object forkRight); public long getWaitTime(); public long getNumberOfMeals(); public long getNumberOfTries(); public int getNumber(); public void stop(); // Work around java's lack of virtual constructors public interface Factory { public Philosopher createPhilosopher(Scheduler s, int number); public Object[] createResources(int n); } /** * Default behavior implementation for convenience */ public abstract class AbstractBase implements Philosopher{ Scheduler s; boolean stopped=false; public void stop() {stopped=true; } int number; public int getNumber() {return number;} long eatCount=0; public long getNumberOfMeals() {return eatCount;} long tries=0; public long getNumberOfTries() {return tries;} long totalWait,waitStart, waitStop; public long getWaitTime() { return totalWait;} public AbstractBase(Scheduler s_, int number_){ this.s= s_; this.number= number_; } public void run(){ try { while (!stopped) { think(); waitStart= System.currentTimeMillis(); tries++; s.requestAccess(this);}} catch(InterruptedException ex) {/*we've been signaled to bail out*/} } protected abstract void think(); protected abstract void doEat(Object resourceLeft, Object resourceRight); public void eat(Object resourceLeft, Object resourceRight) { waitStop= System.currentTimeMillis(); totalWait += waitStop - waitStart; eatCount++; doEat(resourceLeft,resourceRight); } } } // Scheduler.java //----------- package testdp; import junit.framework.Assert; /** * @author ccozianu * */ public interface Scheduler { /** * In order to work around Java's lack of virtual constructors * we have to declare a separate interface that is concerned with setting * up a Scheduler */ public interface Factory { Scheduler createScheduler(Object[] resources); } /** Philosopher will call this method when they need to eat * the method will either secure access to resources on the left and right * and call back Philopher.eat(); or will return leaving the philosopher hungry * hoping for a better luck next time */ public void requestAccess(Philosopher p) throws InterruptedException; /** * creates the default dummy scheduler . (The most straightforward implementation) */ public static class DummySchedulerFactory implements Factory { public Scheduler createScheduler(Object[] resources) { return new AbstractBase(resources) { // we synchronize all philosophers globally, dumb isn't it ? // Still it's very good for testing and comparing public synchronized void requestAccess(Philosopher p) { int left= p.getNumber(); int right= (left + 1) % this.resources.length; p.eat(this.resources[left], this.resources[right]); } }; } }; /** * A convenience abstract base */ public abstract class AbstractBase implements Scheduler { Object resources[]; int n; public AbstractBase(Object[] resources_) { this.resources= resources_; n= resources_.length; } } } So basically in order to write a solution, one has to provide a SchedulerFactory and a scheduler, all doable in a few lines of code. No assumption should be made about how philospher will actually perform, however, here's the actual code that will measure a few things about the solution: package testdp; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Random; /** * */ public class DPExperiment { public static void runTheShow( int nPhilosophers, int msecToRun, Scheduler.Factory schedulerFactory, Philosopher.Factory philosopherFactory){ try { Object[] resources= philosopherFactory.createResources(nPhilosophers); Scheduler s= schedulerFactory.createScheduler(resources); Philosopher philosophers[]= new Philosopher[nPhilosophers]; Thread threads[] = new Thread[nPhilosophers]; long startTime=System.currentTimeMillis(),endTime; for (int i=0; i0) nrThreads= Integer.parseInt(args[0]); int timeToRun=3500; if (args.length>1) timeToRun= Integer.parseInt(args[1]); String solution= Costin.SchedulerFactory.class.getName(); if (args.length>2) solution= args[2]; String test= DummyPhilosopherFactory.class.getName(); if (args.length>3) test= args[3]; runTheShow(nrThreads,timeToRun,solution,test); } catch(Exception ex){ System.err.println(ex); ex.printStackTrace(System.err); System.exit(-1); } } /** Philosophers doing dummy computations both for thinking and eating */ public static class DummyPhilosopherFactory implements Philosopher.Factory { public Philosopher createPhilosopher(Scheduler s, int number) { return new Philosopher.AbstractBase(s,number){ Random r= new Random(); long n= r.nextLong(); protected void think() { for (int i=0;i<10;i++) n ^= r.nextLong(); } protected void doEat(Object resourceLeft, Object resourceRight){ for (int i=0;i<10;i++) n ^= r.nextLong(); } }; } public Object[] createResources(int n) { return new Object[n]; } } public static class IOPhilosopherFactory implements Philosopher.Factory { public Philosopher createPhilosopher(Scheduler s, int number) { return new Philosopher.AbstractBase(s,number) { Random r= new Random(); long n= r.nextLong(); protected void think() { for (int i=0;i<10;i++) n ^= r.nextLong(); } int turn=0; protected void doEat(Object r1, Object r2) { try { OutputStream s1= (OutputStream) r1, s2= (OutputStream) r2; s1.write(("Philosopher "+this.number +" turn "+(turn++)).getBytes("ASCII")); } catch(Exception ex) { throw new RuntimeException(ex.getMessage()); } } }; } /** * the design is a little sloppy here to make for simplicity, * it doesn't provide for cleanly closing the file. A real design should make * provision for closing all files at the end of the experiment, typically I * do this by a ExitManager that calls a Cleanup interface for all its * subscriber, */ public Object[] createResources(int n) { try { OutputStream result[]= new OutputStream[n]; for (int i=0;i 0; countdown--) { if (forks[i] && forks[(i + 1) % n]) { forks[i]= false; forks[(i + 1) % n]= false; return true; } else { try { Thread.sleep(20); } catch (Exception ex) {} } } } return false; } public synchronized void doneEating(Philosopher p) { int i= p.getNumber(); forks[i]= true; forks[(i + 1) % n]= true; } public void requestAccess(final Philosopher p) throws InterruptedException { int i= p.getNumber(); if (canEat(p)) { try { p.eat(resources[i], resources[(i + 1) % n]); } finally { doneEating(p); } Thread.sleep(rand.nextInt(20)); } } } public static class SchedulerFactory implements Scheduler.Factory { public Scheduler createScheduler(Object[] resources_) { return new RonsScheduler(resources_); } } } ----- With apologies to JimWeirich that completed the challenge first I put here my Java translation of his solution (the original is in Ruby at http://w3.one.net/~jweirich/talks/tdd_philosophers/ ). package testdp; public class Jim { public static class SchedulerFactory implements Scheduler.Factory { public Scheduler createScheduler(Object[] resources) { return new JimScheduler(resources); } } public static class JimScheduler extends Scheduler.AbstractBase { public Object syncs[]= new Object[n]; {for (int i=0;i