/*
 * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.testing.tests.clientserver;

import java.util.*;
import org.eclipse.persistence.testing.framework.*;
import org.eclipse.persistence.sessions.DatabaseLogin;

/**
 * Tests that a deadlock in sequencing does not occur.
 */
@SuppressWarnings("deprecation")
public class ClientServerSequenceDeadlockTest extends ClientServerConcurrentWriteTest {
    protected FIFO[] fifoArray;
    protected int[] clientStateArray;
    protected long[] clientLastActionTimeArray;
    protected int numObjects = 25;
    protected int doneClients;
    protected boolean deadlock;
    protected static final int THREAD_UNDEFINED = 0;
    protected static final int THREAD_RUNNING = 0;
    protected static final int THREAD_LOCKED = 1;
    protected static final int THREAD_EXITED = 2;
    protected static final long TIME_TO_PRESUME_DEADLOCK = 300000;

    /**
     *
     */
    public ClientServerSequenceDeadlockTest() {
        super();
        numObjects = 25;
        setDescription("Tests concurrent writing of objects of two types for sequencing deadlock");
        fifoArray = new FIFO[NUM_CLIENTS];
        clientStateArray = new int[NUM_CLIENTS];
        clientLastActionTimeArray = new long[NUM_CLIENTS];
    }

    /**
     *
     */
    @Override
    public void reset() {
        for (int i = 0; i < getClients().size(); i++) {
            EmployeeSeqDeadlockClient client = (EmployeeSeqDeadlockClient)getClients().elementAt(i);
            if (clientStateArray[i] == THREAD_RUNNING) {
                client.stop();
            }
        }
        this.server.logout();
        setClients(new Vector());
    }

    /**
     *
     */
    @Override
    public void setup() {
        this.login = (DatabaseLogin)getSession().getLogin().clone();
        // Workaround for DB2 bug (or feature?) causing test deadlock, see bug 3550940.
        if (this.login.getPlatform().isDB2()) {
            this.login.setTransactionIsolation(DatabaseLogin.TRANSACTION_SERIALIZABLE);
        }
        this.server = new Server(this.login);
        this.server.serverSession.getLogin().getDefaultSequence().setPreallocationSize(1);
        this.server.serverSession.setSessionLog(getSession().getSessionLog());
        this.server.login();
        this.server.copyDescriptors(getSession());
        for (int i = 0; i < NUM_CLIENTS; i++) {
            boolean createEmployees = (i % 2) == 0;
            fifoArray[i] = new FIFO(numObjects);
            getClients().addElement(new EmployeeSeqDeadlockClient(this.server, getSession(), "Client " + i, numObjects, createEmployees, fifoArray[i]));
            clientStateArray[i] = THREAD_UNDEFINED;
            clientLastActionTimeArray[i] = 0;
        }
        doneClients = 0;
        deadlock = false;
    }

    /**
     *
     */
    @Override
    public void test() {
        for (int i = 0; i < NUM_CLIENTS; i++) {
            EmployeeSeqDeadlockClient client = (EmployeeSeqDeadlockClient)clients.elementAt(i);
            client.start();
            clientStateArray[i] = THREAD_RUNNING;
        }

        while (doneClients < NUM_CLIENTS) {
            for (int i = 0; i < NUM_CLIENTS; i++) {
                if (clientStateArray[i] == THREAD_RUNNING) {
                    if ((NUM_CLIENTS - doneClients) == 1) {
                        ((EmployeeClient)getClients().elementAt(i)).pleaseStop();
                    }
                    long currentTime = System.currentTimeMillis();
                    if (!fifoArray[i].isEmpty()) {
                        clientLastActionTimeArray[i] = currentTime;
                        int objectNumber = (Integer) fifoArray[i].removeHead();

                        //                    System.out.println("Client# = " + i + " object# =  " + objectNumber);
                        if ((objectNumber == numObjects) || (objectNumber == -1)) {
                            clientStateArray[i] = THREAD_EXITED;
                            doneClients++;
                            //                        System.out.println("Client# = " + i + " EXITED");
                        }
                    } else {
                        if (clientLastActionTimeArray[i] == 0) {
                            clientLastActionTimeArray[i] = System.currentTimeMillis();
                        } else if ((currentTime - clientLastActionTimeArray[i]) >= TIME_TO_PRESUME_DEADLOCK) {
                            clientStateArray[i] = THREAD_LOCKED;
                            deadlock = true;
                            doneClients++;
                            ((Thread)getClients().elementAt(i)).stop();
                            //                        System.out.println("Client# = " + i + " DEADLOCK");
                        }
                    }
                }
            }
        }
    }

    /**
     *
     */
    @Override
    public void verify() {
        for (int i = 0; i < NUM_CLIENTS; i++) {
            EmployeeSeqDeadlockClient client = (EmployeeSeqDeadlockClient)clients.elementAt(i);
            if (client.anErrorOccurred()) {
                throw new TestErrorException("An exception " + client.getTestException() + " occurred in client " + client.toString());
            }
        }
        if (deadlock) {
            throw new TestErrorException("Looks like it's a deadlock");
        }
    }
}
