package org.jgroups.tests;

import org.jgroups.Global;
import org.jgroups.JChannel;
import org.jgroups.blocks.executor.ExecutionRunner;
import org.jgroups.blocks.executor.ExecutionService;
import org.jgroups.protocols.CENTRAL_EXECUTOR;
import org.jgroups.util.FutureListener;
import org.jgroups.util.NotifyingFuture;
import org.jgroups.util.Util;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;


/** Tests https://issues.jboss.org/browse/JGRP-1696 */
@Test(groups={Global.FUNCTIONAL,Global.EAP_EXCLUDED}, singleThreaded=true)
public class ExecutingServiceTest2 {

    Set<Thread>   threads=new HashSet<>();
    Set<JChannel> channels=new HashSet<>();

    @AfterMethod
    public void tearDown() {
        threads.forEach(Thread::interrupt);
        channels.forEach(JChannel::close);
    }

    @Test
    public void testDisconnect() throws Exception {
        JChannel channel1=new JChannel(Util.getTestStack(new CENTRAL_EXECUTOR()));
        JChannel channel2=new JChannel(Util.getTestStack(new CENTRAL_EXECUTOR()));
        channels.add(channel1);
        channels.add(channel2);

        channel1.connect("test-cluster");
        channel2.connect("test-cluster");
        Util.waitUntilAllChannelsHaveSameView(20000, 1000, channel1, channel2);

        final ExecutionService executionService=new ExecutionService(channel1);
        ExecutionRunner executionRunner1=new ExecutionRunner(channel1);
        ExecutionRunner executionRunner2=new ExecutionRunner(channel2);

        Thread runner1=new Thread(executionRunner1);
        threads.add(runner1);
        runner1.start();
        Thread runner2=new Thread(executionRunner2);
        threads.add(runner2);
        runner2.start();

        final AtomicInteger submittedTasks=new AtomicInteger();
        final AtomicInteger finishedTasks=new AtomicInteger();
        final FutureListener<Void> listener=future -> {
            finishedTasks.incrementAndGet();
            synchronized(ExecutingServiceTest2.this) {
                ExecutingServiceTest2.this.notify();
            }
        };

        Thread submitter=new Thread(new Runnable() {
            @Override
            public void run() {
                // Two long running tasks that should be sent to each runner
                submit(true);
                submit(true);
                while(!Thread.interrupted()) {
                    submit(false);

                    // Throttle
                    try {
                        Thread.sleep(50);
                    }
                    catch(InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
            }

            private void submit(boolean wait) {
                Callable<Void> task=new Wait(wait);
                NotifyingFuture<Void> future=executionService.submit(task);
                submittedTasks.incrementAndGet();
                future.setListener(listener);
            }

        });
        threads.add(submitter);
        submitter.start();

        // Run for 2 seconds
        Thread.sleep(500);

        // Close channel
        channel2.close();

        // Stop submitting
        submitter.interrupt();
        submitter.join();

        // Wait for running tasks to finish
        synchronized(this) {
            int lastFinished=finishedTasks.get();
            while(submittedTasks.get() > finishedTasks.get()) {
                wait(10000);
                if(lastFinished == finishedTasks.get()) {
                    assert false : "Tasks still outstanding, none finished in the last 10s";
                }
                lastFinished=finishedTasks.get();
            }
        }

        Assert.assertEquals(submittedTasks.get(),finishedTasks.get(),"Tasks not finished");
    }


    private static final class Wait implements Callable<Void>, Serializable {
        boolean wait=false;

        Wait(boolean wait) {
            super();
            this.wait=wait;
        }


        @Override
        public Void call() {
            if(wait) {
                try {
                    Thread.sleep(5000);
                }
                catch(InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    }
}

