// ========================================================================
// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at 
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses. 
// ========================================================================

package org.eclipse.jetty.util.thread;

import static org.junit.Assert.assertTrue;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import junit.framework.Assert;

import org.junit.Test;


public class QueuedThreadPoolTest
{
    final AtomicInteger _jobs=new AtomicInteger();
    
    class RunningJob implements Runnable
    {
        private final CountDownLatch _run = new CountDownLatch(1);
        private final CountDownLatch _stopping = new CountDownLatch(1);
        private final CountDownLatch _stopped = new CountDownLatch(1);
        public void run()
        {
            try 
            {
                _run.countDown();
                _stopping.await();
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
            finally
            {
                _jobs.incrementAndGet();
                _stopped.countDown();
            }
        }
        
        public void stop() throws InterruptedException
        {
            _run.await(10,TimeUnit.SECONDS);
            _stopping.countDown();
            if (!_stopped.await(10,TimeUnit.SECONDS))
                throw new IllegalStateException(); 
        }
    };   
    
    
    @Test
    public void testThreadPool() throws Exception
    {        
        QueuedThreadPool tp= new QueuedThreadPool();
        tp.setMinThreads(5);
        tp.setMaxThreads(10);
        tp.setMaxIdleTimeMs(1000);
        tp.setThreadsPriority(Thread.NORM_PRIORITY-1);

        tp.start();
        
        waitForThreads(tp,5);
        waitForIdle(tp,5);
        
        Thread.sleep(1000);
        waitForThreads(tp,5);
        waitForIdle(tp,5);
        
        RunningJob job=new RunningJob();
        tp.dispatch(job);
        waitForIdle(tp,4);
        waitForThreads(tp,5);

        job.stop();
        waitForIdle(tp,5);
        waitForThreads(tp,5);
        
        Thread.sleep(200);
        waitForIdle(tp,5);
        waitForThreads(tp,5);

        RunningJob[] jobs = new RunningJob[5];
        for (int i=0;i<jobs.length;i++)
        {
            jobs[i]=new RunningJob();
            tp.dispatch(jobs[i]);
        }
        waitForIdle(tp,0);
        waitForThreads(tp,5);
        
        job=new RunningJob();
        tp.dispatch(job);
        waitForThreads(tp,6);
        
        job.stop();
        waitForThreads(tp,5);
        
        jobs[0].stop();
        waitForIdle(tp,1);
        waitForThreads(tp,5);
        
        for (int i=1;i<jobs.length;i++)
            jobs[i].stop();

        waitForIdle(tp,5);
        waitForThreads(tp,5);
        
        jobs = new RunningJob[15];
        for (int i=0;i<jobs.length;i++)
        {
            jobs[i]=new RunningJob();
            tp.dispatch(jobs[i]);
        }
        waitForIdle(tp,0);
        waitForThreads(tp,10);
        for (int i=0;i<9;i++)
            jobs[i].stop();
        waitForThreads(tp,9);
        
        for (int i=9;i<jobs.length;i++)
            jobs[i].stop();
        waitForIdle(tp,5);
        tp.stop();
    }

    @Test
    public void testShrink() throws Exception
    {
        final AtomicInteger sleep = new AtomicInteger(100);
        Runnable job = new Runnable()
        {
            public void run()
            {
                try 
                {
                    Thread.sleep(sleep.get());
                }
                catch(Exception e)
                {
                    e.printStackTrace();
                }
            }
            
        };
        
        QueuedThreadPool tp= new QueuedThreadPool();
        tp.setMinThreads(2);
        tp.setMaxThreads(10);
        tp.setMaxIdleTimeMs(400);
        tp.setThreadsPriority(Thread.NORM_PRIORITY-1);
        
        tp.start();
        waitForIdle(tp,2);
        waitForThreads(tp,2);
        
        sleep.set(200);
        tp.dispatch(job);
        tp.dispatch(job);
        for (int i=0;i<20;i++)
            tp.dispatch(job);

        waitForThreads(tp,10);
        waitForIdle(tp,0);
        
        sleep.set(5);
        for (int i=0;i<500;i++)
        {
            tp.dispatch(job);
            Thread.sleep(10);
        }
        waitForThreads(tp,2);
        waitForIdle(tp,2);
    }

    @Test
    public void testMaxStopTime() throws Exception
    {
        QueuedThreadPool tp= new QueuedThreadPool();
        tp.setMaxStopTimeMs(500);
        tp.start();
        tp.dispatch(new Runnable(){
            public void run () {
                while (true) {
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException ie) {}
                }
            }
        });

        long beforeStop = System.currentTimeMillis();
        tp.stop();
        long afterStop = System.currentTimeMillis();
        assertTrue(tp.isStopped());
        assertTrue(afterStop - beforeStop < 1000);
    }


    private void waitForIdle(QueuedThreadPool tp, int idle)
    {
        long now=System.currentTimeMillis();
        long start=now;
        while (tp.getIdleThreads()!=idle && (now-start)<10000)
        {
            try
            {
                Thread.sleep(10);
            }
            catch(InterruptedException e)
            {}
        }
        Assert.assertEquals(idle,tp.getIdleThreads());
    }

    private void waitForThreads(QueuedThreadPool tp, int threads)
    {
        long now=System.currentTimeMillis();
        long start=now;
        while (tp.getThreads()!=threads && (now-start)<10000)
        {
            try
            {
                Thread.sleep(10);
            }
            catch(InterruptedException e)
            {} 
            now=System.currentTimeMillis();
        }
        Assert.assertEquals(threads,tp.getThreads());
    }

}
