/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.catalina.session;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import org.junit.Assert;
import org.junit.Test;

/**
 * The design of the session manager depends on the thread-safety - or not - of
 * a number of classes. In some cases the Javadoc is unclear on the
 * thread-safety of a class. These tests were written to test the thread-safety
 * of key classes.
 *
 * Named Threading so it is not automatically executed as part of the unit
 * tests.
 */
public class Threading {

    /*
     * {@link FileInputStream#read(byte[])} and related methods are all native
     * methods so it isn't immediately obvious if they are thread-safe or not.
     *
     * <pre>
     * Windows JDK 1.6.0_22_x64 - Thread safe
     * OSX     JDK 1.6.0_22_x64 - Not thread safe
     * OSX     JDK 1.7.0_51_x64 - Not thread safe
     * </pre>
     *
     * Therefore, have to assume that {@link FileInputStream#read(byte[])} is
     * not thread safe.
     */
    @Test
    public void testFileInputStream() throws Exception {
        doTestFileInputStream(1);
        doTestFileInputStream(2);
        doTestFileInputStream(4);
        doTestFileInputStream(16);
    }

    public void doTestFileInputStream(int threadCount) throws Exception {

        // Assumes "ant release" has been run
        // Will need to be updated as new releases are made
        File file = new File(
                "./output/release/v8.0.15-dev/bin/apache-tomcat-8.0.15-dev.zip");

        FileInputStream fis = new FileInputStream(file);

        Thread[] threads = new Thread[threadCount];
        FisReaderThread[] runnables = new FisReaderThread[threadCount];

        for (int i = 0; i < threadCount; i++) {
            runnables[i] = new FisReaderThread(fis);
            threads[i] = new Thread(runnables[i]);
        }

        long start = System.currentTimeMillis();

        for (int i = 0; i < threadCount; i++) {
            threads[i].start();
        }
        for (int i = 0; i < threadCount; i++) {
            try {
                threads[i].join();
                if (runnables[i].isfailed()) {
                    Assert.fail();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                Assert.fail(e.getMessage());
            }
        }
        long end = System.currentTimeMillis();

        long byteCount = 0;
        for (int i = 0; i < threadCount; i++) {
            byteCount += runnables[i].getByteCount();
        }

        StringBuilder result = new StringBuilder();
        result.append("Threads: ");
        result.append(threadCount);
        result.append(", Time(ms): ");
        result.append(end-start);
        result.append(", Bytes: ");
        result.append(byteCount);
        System.out.println(result.toString());
    }

    private static final class FisReaderThread implements Runnable {

        private FileInputStream fis;
        // Small buffer to make the process slow
        private byte[] buffer = new byte[4];
        private long byteCount = 0;
        private boolean fail = false;

        FisReaderThread(FileInputStream fis) {
            this.fis = fis;
        }

        @Override
        public void run() {
            int read = 0;
            while (read > -1) {
                byteCount += read;
                try {
                    // Uncomment the sync block to test adding the sync fixes
                    // issues on platforms where fis is not thread-safe
                    // synchronized (fis) {
                        read = fis.read(buffer);
                    //}
                } catch (IOException e) {
                    e.printStackTrace();
                    fail = true;
                    read = -1;
                }
            }
        }

        public long getByteCount() {
            return byteCount;
        }

        public boolean isfailed() {
            return fail;
        }
    }
}
