/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.test.fs.filesystem.tests; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Vector; import org.jnode.fs.FSEntry; import org.jnode.fs.FSFile; import org.jnode.test.fs.filesystem.AbstractFSTest; import org.jnode.test.fs.filesystem.config.FSTestConfig; import org.jnode.test.support.TestUtils; import org.junit.Ignore; import org.junit.Test; /** * @author Fabien DUMINY */ public class ConcurrentAccessFSTest extends AbstractFSTest { protected static final int MAX_SLEEP = 100; protected static final int MIN_SLEEP = 10; protected static final int NB_READERS = 10; protected static final int NB_WRITERS = 10; public ConcurrentAccessFSTest(FSTestConfig config) { super(config); } @Test public void testRead() throws Throwable { setUp(); FSFile file = prepareFile(config); Monitor monitor = new Monitor("testRead"); createReaders(monitor, file); monitor.waitAll(); } @Test @Ignore("Fix concurrency issues") public void testWrite() throws Throwable { if (!config.isReadOnly()) { setUp(); FSFile file = prepareFile(config); Monitor monitor = new Monitor("testWrite"); createWriters(monitor, file); monitor.waitAll(); assertTrue("integrity test failed", isGoodResultFile(file)); } } @Test @Ignore("Fix concurrency issues") public void testReadWrite() throws Throwable { setUp(); FSFile file = prepareFile(config); Monitor monitor = new Monitor("testReadWrite"); createReaders(monitor, file); if (!config.isReadOnly()) { createWriters(monitor, file); } monitor.waitAll(); assertTrue("integrity test failed", isGoodResultFile(file)); } protected void createReaders(Monitor monitor, FSFile file) { for (int i = 0; i < NB_READERS; i++) { monitor.addWorker(new Reader(monitor, file, i * 2, NB_READERS * 2, MIN_SLEEP, MAX_SLEEP)); } } protected void createWriters(Monitor monitor, FSFile file) { for (int i = 0; i < NB_WRITERS; i++) monitor.addWorker(new Writer(monitor, file, i * 2, NB_WRITERS * 2, MIN_SLEEP, MAX_SLEEP)); } protected boolean isGoodResultFile(FSFile file) throws IOException { byte[] expData = TestUtils.getTestData(FILE_SIZE_IN_WORDS); ByteBuffer data = ByteBuffer.allocate(expData.length); file.read(0, data); return TestUtils.equals(expData, data.array()); } protected FSFile prepareFile(FSTestConfig config) throws Exception { remountFS(config, false); final String fileName = "RWTest"; FSEntry rootEntry = getFs().getRootEntry(); FSEntry entry = rootEntry.getDirectory().addFile(fileName); FSFile file = entry.getFile(); file.setLength(FILE_SIZE_IN_WORDS * 2); file.flush(); assertSize("Bad file size", FILE_SIZE_IN_WORDS * 2, file.getLength()); remountFS(config, getFs().isReadOnly()); rootEntry = getFs().getRootEntry(); entry = rootEntry.getDirectory().getEntry(fileName); file = entry.getFile(); assertSize("Bad file size", FILE_SIZE_IN_WORDS * 2, file.getLength()); return file; } class FailureRecord { final Throwable exception; final String workerClass; FailureRecord(Throwable exception, String workerClass) { this.exception = exception; this.workerClass = workerClass; } } class Monitor { private Vector<Worker> workers = new Vector<Worker>(); private Vector<Worker> finishedWorkers = new Vector<Worker>(); private Vector<FailureRecord> failures = new Vector<FailureRecord>(); private final String testName; Monitor(String testName) { this.testName = testName; } public void addWorker(Worker worker) { workers.add(worker); } public void notifyEnd(Worker worker) { finishedWorkers.add(worker); } public void notifyError(Worker worker, Throwable throwable) { failures.add(new FailureRecord(throwable, worker.getClass().getName())); } public void waitAll() throws Throwable { for (Worker worker : workers) { new Thread(worker).start(); } while (finishedWorkers.size() != workers.size()) { try { Thread.sleep(10); } catch (InterruptedException e) { // ignore } } if (failures.size() == 1) { FailureRecord failure = failures.get(0); throw new Error("Worker " + failure.workerClass + " failed", unwrap(failure.exception)); } if (failures.size() > 0) { int i = 1; for (FailureRecord failure : failures) { Throwable throwable = unwrap(failure.exception); System.err.println("Failure #" + (i++) + " of test '" + testName + " in worker " + failure.workerClass + ": " + throwable.getMessage()); throwable.printStackTrace(System.err); } throw new Error("Multiple workers had errors/exceptions (see earlier messages)"); } } public Throwable unwrap(Throwable throwable) { if (throwable.getClass().equals(RuntimeException.class) && throwable.getCause() != null) { throwable = throwable.getCause(); } return throwable; } } class Reader extends Worker { public Reader(Monitor monitor, FSFile file, int offsetStart, int offsetStep, int minSleep, int maxSleep) { super(monitor, file, offsetStart, offsetStep, minSleep, maxSleep); } public void doRun(long offset) throws IOException { ByteBuffer dest = ByteBuffer.allocate(2); file.read(offset, dest); } } abstract class Worker implements Runnable { protected FSFile file; protected int maxSleep; protected int minSleep; protected Monitor monitor; protected int offsetStart; protected int offsetStep; /** * @param monitor * @param file the file on which to work * @param offsetStart file's offset from which to start * @param offsetStep value to add to file's offset at each iteration * @param minSleep minimum delay to sleep between 2 iterations * @param maxSleep maximum delay to sleep between 2 iterations */ public Worker(Monitor monitor, FSFile file, int offsetStart, int offsetStep, int minSleep, int maxSleep) { this.file = file; this.offsetStart = offsetStart; this.offsetStep = offsetStep; this.minSleep = minSleep; this.maxSleep = maxSleep; this.monitor = monitor; } protected abstract void doRun(long offset) throws IOException; public final void run() { long length = file.getLength(); try { for (int i = offsetStart; i < (length - 1); i += offsetStep) { try { doRun(i); } catch (IOException ex) { throw new RuntimeException("Error in worker thread", ex); } int sleep = (int) (minSleep + Math.random() * (maxSleep - minSleep)); try { Thread.sleep(sleep); } catch (InterruptedException e) { //ignore } } } catch (Throwable t) { monitor.notifyError(this, t); } finally { // worker has finished properly monitor.notifyEnd(this); } } } class Writer extends Worker { /** * {@inheritDoc} */ public Writer(Monitor monitor, FSFile file, int offsetStart, int offsetStep, int minSleep, int maxSleep) { super(monitor, file, offsetStart, offsetStep, minSleep, maxSleep); } public void doRun(long offset) throws IOException { long value = offset / 2; byte msbValue = (byte) (value & 0xFF00); byte lsbValue = (byte) (value & 0x00FF); ByteBuffer src = ByteBuffer.wrap(new byte[]{msbValue, lsbValue}); file.write(offset, src); } } }