001/*
002 * $Id: DistThreadPool.java 5788 2018-01-04 21:07:10Z kredel $
003 */
004
005package edu.jas.util;
006
007
008import java.io.FileNotFoundException;
009import java.io.IOException;
010import java.util.LinkedList;
011
012import org.apache.log4j.Logger;
013
014
015/**
016 * Distributed thread pool. Using stack / list work-pile and Executable Channels
017 * and Servers.
018 * @author Heinz Kredel
019 */
020
021public class DistThreadPool /*extends ThreadPool*/{
022
023
024    /**
025     * machine file to use.
026     */
027    private final String mfile;
028
029
030    /**
031     * default machine file for test.
032     */
033    private final static String DEFAULT_MFILE = ExecutableChannels.DEFAULT_MFILE;
034
035
036    /**
037     * Number of threads to use.
038     */
039    protected final int threads;
040
041
042    /**
043     * Default number of threads to use.
044     */
045    static final int DEFAULT_SIZE = 3;
046
047
048    /**
049     * Channels to remote executable servers.
050     */
051    final ExecutableChannels ec;
052
053
054    /**
055     * Array of workers.
056     */
057    protected DistPoolThread[] workers;
058
059
060    /**
061     * Number of idle workers.
062     */
063    protected int idleworkers = 0;
064
065
066    /**
067     * Work queue / stack.
068     */
069    // should be expressed using strategy pattern
070    // List or Collection is not appropriate
071    // LIFO strategy for recursion
072    protected LinkedList<Runnable> jobstack; // FIFO strategy for GB
073
074
075    protected StrategyEnumeration strategy = StrategyEnumeration.LIFO;
076
077
078    private static final Logger logger = Logger.getLogger(DistThreadPool.class);
079
080
081    private static final boolean debug = true; //logger.isDebugEnabled();
082
083
084    /**
085     * Constructs a new DistThreadPool with strategy StrategyEnumeration.FIFO
086     * and size DEFAULT_SIZE.
087     */
088    public DistThreadPool() {
089        this(StrategyEnumeration.FIFO, DEFAULT_SIZE, null);
090    }
091
092
093    /**
094     * Constructs a new DistThreadPool with size DEFAULT_SIZE.
095     * @param strategy for job processing.
096     */
097    public DistThreadPool(StrategyEnumeration strategy) {
098        this(strategy, DEFAULT_SIZE, null);
099    }
100
101
102    /**
103     * Constructs a new DistThreadPool with strategy StrategyEnumeration.FIFO.
104     * @param size of the pool.
105     */
106    public DistThreadPool(int size) {
107        this(StrategyEnumeration.FIFO, size, null);
108    }
109
110
111    /**
112     * Constructs a new DistThreadPool with strategy StrategyEnumeration.FIFO.
113     * @param size of the pool.
114     * @param mfile machine file.
115     */
116    public DistThreadPool(int size, String mfile) {
117        this(StrategyEnumeration.FIFO, size, mfile);
118    }
119
120
121    /**
122     * Constructs a new DistThreadPool.
123     * @param strategy for job processing.
124     * @param size of the pool.
125     * @param mfile machine file.
126     */
127    public DistThreadPool(StrategyEnumeration strategy, int size, String mfile) {
128        this.strategy = strategy;
129        if (size < 0) {
130            this.threads = 0;
131        } else {
132            this.threads = size;
133        }
134        if (mfile == null || mfile.length() == 0) {
135            this.mfile = DEFAULT_MFILE;
136        } else {
137            this.mfile = mfile;
138        }
139        jobstack = new LinkedList<Runnable>(); // ok for all strategies ?
140        try {
141            ec = new ExecutableChannels(this.mfile);
142        } catch (FileNotFoundException e) {
143            e.printStackTrace();
144            throw new IllegalArgumentException("DistThreadPool " + e);
145        }
146        if (debug) {
147            logger.info("ec = " + ec);
148        }
149        try {
150            ec.open(threads);
151        } catch (IOException e) {
152            e.printStackTrace();
153            throw new IllegalArgumentException("DistThreadPool " + e);
154        }
155        if (debug) {
156            logger.info("ec = " + ec);
157        }
158        workers = new DistPoolThread[0];
159    }
160
161
162    /**
163     * String representation.
164     */
165    @Override
166    public String toString() {
167        StringBuffer s = new StringBuffer("DistThreadPool(");
168        s.append("threads="+threads);
169        s.append(", strategy="+strategy);
170        s.append(", exchan="+ec);
171        s.append(", workers="+workers.length);
172        s.append(")");
173        return s.toString();
174    }
175
176
177    /**
178     * thread initialization and start.
179     */
180    public void init() {
181        if (workers == null || workers.length == 0) {
182            workers = new DistPoolThread[threads];
183            for (int i = 0; i < workers.length; i++) {
184                workers[i] = new DistPoolThread(this, ec, i);
185                workers[i].start();
186            }
187            logger.info("init: " + this.toString());
188        }
189    }
190
191
192    /**
193     * number of worker threads.
194     */
195    public int getNumber() {
196        if (workers == null || workers.length < threads) {
197            init(); // start threads
198        }
199        return workers.length; // not null
200    }
201
202
203    /**
204     * get used strategy.
205     */
206    public StrategyEnumeration getStrategy() {
207        return strategy;
208    }
209
210
211    /**
212     * the used executable channel.
213     */
214    public ExecutableChannels getEC() {
215        return ec; // not null
216    }
217
218
219    /**
220     * Terminates the threads.
221     * @param shutDown true, if shut-down of the remote executable servers is
222     *            requested, false, if remote executable servers stay alive.
223     */
224    public void terminate(boolean shutDown) {
225        if (shutDown) {
226            logger.info("shutdown = " + this);
227            ShutdownRequest sdr = new ShutdownRequest();
228            for (int i = 0; i < workers.length; i++) {
229                addJob(sdr);
230            }
231            try {
232                Thread.sleep(20);
233            } catch (InterruptedException e) {
234                Thread.currentThread().interrupt();
235            }
236            logger.info("remaining jobs = " + jobstack.size());
237            try {
238                for (int i = 0; i < workers.length; i++) {
239                    while (workers[i].isAlive()) {
240                        workers[i].interrupt();
241                        workers[i].join(100);
242                    }
243                }
244            } catch (InterruptedException e) {
245                Thread.currentThread().interrupt();
246            }
247            ec.close();
248        } else {
249            terminate();
250        }
251        logger.info("terminated = " + this);
252    }
253
254
255    /**
256     * Terminates the threads.
257     */
258    public void terminate() {
259        logger.info("terminate = " + this);
260        while (hasJobs()) {
261            try {
262                Thread.sleep(100);
263            } catch (InterruptedException e) {
264                Thread.currentThread().interrupt();
265            }
266        }
267        for (int i = 0; i < workers.length; i++) {
268            try {
269                while (workers[i].isAlive()) {
270                    workers[i].interrupt();
271                    workers[i].join(100);
272                }
273            } catch (InterruptedException e) {
274                Thread.currentThread().interrupt();
275            }
276        }
277        ec.close();
278        logger.info("terminated = " + this);
279    }
280
281
282    /**
283     * adds a job to the workpile.
284     * @param job
285     */
286    public synchronized void addJob(Runnable job) {
287        if (workers == null || workers.length < threads) {
288            init(); // start threads
289        }
290        jobstack.addLast(job);
291        logger.debug("adding job");
292        if (idleworkers > 0) {
293            logger.debug("notifying a jobless worker");
294            notifyAll(); // findbugs
295        }
296    }
297
298
299    /**
300     * get a job for processing.
301     */
302    protected synchronized Runnable getJob() throws InterruptedException {
303        while (jobstack.isEmpty()) {
304            idleworkers++;
305            logger.debug("waiting");
306            wait();
307            idleworkers--;
308        }
309        // is expressed using strategy enumeration
310        if (strategy == StrategyEnumeration.LIFO) {
311            return jobstack.removeLast(); // LIFO
312        }
313        return jobstack.removeFirst(); // FIFO
314    }
315
316
317    /**
318     * check if there are jobs for processing.
319     */
320    public boolean hasJobs() {
321        if (jobstack.size() > 0) {
322            return true;
323        }
324        for (int i = 0; i < workers.length; i++) {
325            if (workers[i].working) {
326                return true;
327            }
328        }
329        return false;
330    }
331
332
333    /**
334     * check if there are more than n jobs for processing.
335     * @param n Integer
336     * @return true, if there are possibly more than n jobs.
337     */
338    public boolean hasJobs(int n) {
339        int j = jobstack.size();
340        if (j > 0 && (j + workers.length > n)) {
341            return true;
342            // if j > 0 no worker should be idle
343            // ( ( j > 0 && ( j+workers.length > n ) ) || ( j > n )
344        }
345        int x = 0;
346        for (int i = 0; i < workers.length; i++) {
347            if (workers[i].working) {
348                x++;
349            }
350        }
351        if ((j + x) > n) {
352            return true;
353        }
354        return false;
355    }
356
357}
358
359
360/**
361 * Implements a shutdown task.
362 */
363class ShutdownRequest implements Runnable {
364
365
366    /**
367     * Run the thread.
368     */
369    public void run() {
370        System.out.println("running ShutdownRequest");
371    }
372
373
374    /**
375     * toString.
376     * @see java.lang.Object#toString()
377     */
378    @Override
379    public String toString() {
380        return "ShutdownRequest";
381    }
382
383}
384
385
386/**
387 * Implements one local part of the distributed thread.
388 */
389class DistPoolThread extends Thread {
390
391
392    final DistThreadPool pool;
393
394
395    final ExecutableChannels ec;
396
397
398    final int myId;
399
400
401    private static final Logger logger = Logger.getLogger(DistPoolThread.class);
402
403
404    private static final boolean debug = logger.isDebugEnabled();
405
406
407    boolean working = false;
408
409
410    /**
411     * @param pool DistThreadPool.
412     */
413    public DistPoolThread(DistThreadPool pool, ExecutableChannels ec, int i) {
414        this.pool = pool;
415        this.ec = ec;
416        myId = i;
417    }
418
419
420    /**
421     * Run the thread.
422     */
423    @Override
424    public void run() {
425        logger.info("ready, myId = " + myId);
426        Runnable job;
427        int done = 0;
428        long time = 0;
429        long t;
430        boolean running = true;
431        while (running) {
432            try {
433                logger.debug("looking for a job");
434                job = pool.getJob();
435                working = true;
436                if (debug) {
437                    logger.info("working " + myId + " on " + job);
438                }
439                t = System.currentTimeMillis();
440                // send and wait, like rmi
441                try {
442                    if (job instanceof ShutdownRequest) {
443                        ec.send(myId, ExecutableServer.STOP);
444                    } else {
445                        ec.send(myId, job);
446                    }
447                    if (debug) {
448                        logger.info("send " + myId + " at " + ec + " send job " + job);
449                    }
450                } catch (IOException e) {
451                    e.printStackTrace();
452                    logger.info("error send " + myId + " at " + ec + " e = " + e);
453                    working = false;
454                }
455                // remote: job.run(); 
456                Object o = null;
457                try {
458                    if (working) {
459                        logger.info("waiting " + myId + " on " + job);
460                        o = ec.receive(myId);
461                        if (debug) {
462                            logger.info("receive " + myId + " at " + ec + " send job " + job + " received " + o);
463                        }
464                    }
465                } catch (IOException e) {
466                    logger.info("receive exception " + myId + " send job " + job + ", " + e);
467                    //e.printStackTrace();
468                    running = false;
469                } catch (ClassNotFoundException e) {
470                    logger.info("receive exception " + myId + " send job " + job + ", " + e);
471                    //e.printStackTrace();
472                    running = false;
473                } finally {
474                    if (debug) {
475                        logger.info("receive finally " + myId + " at " + ec + " send job " + job + " received "
476                                    + o + " running " + running);
477                    }
478                }
479                working = false;
480                time += System.currentTimeMillis() - t;
481                done++;
482                if (debug) {
483                    logger.info("done " + myId + " with " + o);
484                }
485            } catch (InterruptedException e) {
486                running = false;
487                Thread.currentThread().interrupt();
488            }
489        }
490        logger.info("terminated " + myId + " , done " + done + " jobs in " + time + " milliseconds");
491    }
492
493}