From 725f8414fe153fc8cf54132f9f93abc123d2853d Mon Sep 17 00:00:00 2001 From: lutovich Date: Sun, 13 Dec 2015 22:39:41 +0100 Subject: [PATCH] Introduced adversarial page cache Such page cache implementation is able to wrap the given page cache and introduce some misbehaviour to exercise the user code. It can either throw RuntimeExceptions/IOExceptions or return a page cursor with inconsistent reads. Misbehaviour is controlled by the given Adversary. Changed PageCacheRule to use the AdversarialPageCache. --- .../org/neo4j/io/pagecache/PageCursor.java | 3 +- .../pagecache/AdversarialPageCache.java | 83 +++++ .../pagecache/AdversarialPagedFile.java | 94 +++++ .../pagecache/AdversarialReadPageCursor.java | 253 +++++++++++++ .../pagecache/AdversarialWritePageCursor.java | 258 +++++++++++++ .../neo4j/io/pagecache/StubPageCursor.java | 2 +- .../batchimport/store/BatchingPageCache.java | 2 +- .../java/org/neo4j/test/PageCacheRule.java | 341 ++---------------- 8 files changed, 712 insertions(+), 324 deletions(-) create mode 100644 community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialPageCache.java create mode 100644 community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialPagedFile.java create mode 100644 community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialReadPageCursor.java create mode 100644 community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialWritePageCursor.java diff --git a/community/io/src/main/java/org/neo4j/io/pagecache/PageCursor.java b/community/io/src/main/java/org/neo4j/io/pagecache/PageCursor.java index 7424e2d863376..74c108dba7d5b 100644 --- a/community/io/src/main/java/org/neo4j/io/pagecache/PageCursor.java +++ b/community/io/src/main/java/org/neo4j/io/pagecache/PageCursor.java @@ -279,9 +279,8 @@ public interface PageCursor extends AutoCloseable * an equivalent io() call. In other words, the next call to next() will * move the cursor to the starting page that was specified in the io() that * produced the cursor. - * @throws IOException */ - void rewind() throws IOException; + void rewind(); /** * Moves the cursor to the next page, if any, and returns true when it is diff --git a/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialPageCache.java b/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialPageCache.java new file mode 100644 index 0000000000000..365a06454988b --- /dev/null +++ b/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialPageCache.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.adversaries.pagecache; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Objects; + +import org.neo4j.adversaries.Adversary; +import org.neo4j.io.pagecache.PageCache; +import org.neo4j.io.pagecache.PagedFile; + +/** + * A {@linkplain PageCache page cache} that wraps another page cache and an {@linkplain Adversary adversary} to provide + * a misbehaving page cache implementation for testing. + *

+ * Depending on the adversary each operation can throw either {@link RuntimeException} like {@link SecurityException} + * or {@link IOException} like {@link FileNotFoundException}. + */ +@SuppressWarnings( "unchecked" ) +public class AdversarialPageCache implements PageCache +{ + private final PageCache delegate; + private final Adversary adversary; + + public AdversarialPageCache( PageCache delegate, Adversary adversary ) + { + this.delegate = Objects.requireNonNull( delegate ); + this.adversary = Objects.requireNonNull( adversary ); + } + + @Override + public PagedFile map( File file, int pageSize ) throws IOException + { + adversary.injectFailure( FileNotFoundException.class, IOException.class, SecurityException.class ); + PagedFile pagedFile = delegate.map( file, pageSize ); + return new AdversarialPagedFile( pagedFile, adversary ); + } + + @Override + public void flushAndForce() throws IOException + { + adversary.injectFailure( FileNotFoundException.class, IOException.class, SecurityException.class ); + delegate.flushAndForce(); + } + + @Override + public void close() throws IOException + { + adversary.injectFailure( FileNotFoundException.class, IOException.class, SecurityException.class ); + delegate.close(); + } + + @Override + public int pageSize() + { + return delegate.pageSize(); + } + + @Override + public int maxCachedPages() + { + return delegate.maxCachedPages(); + } +} diff --git a/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialPagedFile.java b/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialPagedFile.java new file mode 100644 index 0000000000000..b43fb398b1201 --- /dev/null +++ b/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialPagedFile.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.adversaries.pagecache; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Objects; + +import org.neo4j.adversaries.Adversary; +import org.neo4j.io.pagecache.PageCursor; +import org.neo4j.io.pagecache.PagedFile; + +/** + * A {@linkplain PagedFile paged file} that wraps another paged file and an {@linkplain Adversary adversary} to provide + * a misbehaving paged file implementation for testing. + *

+ * Depending on the adversary each operation can throw either {@link RuntimeException} like {@link SecurityException} + * or {@link IOException} like {@link FileNotFoundException}. + */ +@SuppressWarnings( "unchecked" ) +class AdversarialPagedFile implements PagedFile +{ + private final PagedFile delegate; + private final Adversary adversary; + + AdversarialPagedFile( PagedFile delegate, Adversary adversary ) + { + this.delegate = Objects.requireNonNull( delegate ); + this.adversary = Objects.requireNonNull( adversary ); + } + + @Override + public PageCursor io( long pageId, int pf_flags ) throws IOException + { + adversary.injectFailure( IllegalStateException.class ); + PageCursor pageCursor = delegate.io( pageId, pf_flags ); + if ( (pf_flags & PF_SHARED_LOCK) == PF_SHARED_LOCK ) + { + return new AdversarialReadPageCursor( pageCursor, adversary ); + } + return new AdversarialWritePageCursor( pageCursor, adversary ); + } + + @Override + public int pageSize() + { + return delegate.pageSize(); + } + + @Override + public void flushAndForce() throws IOException + { + adversary.injectFailure( FileNotFoundException.class, IOException.class, SecurityException.class ); + delegate.flushAndForce(); + } + + @Override + public void force() throws IOException + { + adversary.injectFailure( IOException.class, SecurityException.class ); + delegate.force(); + } + + @Override + public long getLastPageId() throws IOException + { + adversary.injectFailure( IllegalStateException.class ); + return delegate.getLastPageId(); + } + + @Override + public void close() throws IOException + { + adversary.injectFailure( FileNotFoundException.class, IOException.class, SecurityException.class ); + delegate.close(); + } +} diff --git a/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialReadPageCursor.java b/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialReadPageCursor.java new file mode 100644 index 0000000000000..1dd66533e75c9 --- /dev/null +++ b/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialReadPageCursor.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.adversaries.pagecache; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Objects; + +import org.neo4j.adversaries.Adversary; +import org.neo4j.io.pagecache.PageCursor; + +/** + * A read {@linkplain PageCursor page cursor} that wraps another page cursor and an {@linkplain Adversary adversary} + * to provide a misbehaving page cursor implementation for testing. + *

+ * Depending on the adversary each read operation can throw either {@link RuntimeException} like + * {@link SecurityException} or {@link IOException} like {@link FileNotFoundException}. + *

+ * Depending on the adversary each read operation can produce an inconsistent read and require caller to retry using + * while loop with {@link PageCursor#shouldRetry()} as a condition. + *

+ * Write operations will always throw an {@link IllegalStateException} because this is a read cursor. + * See {@link org.neo4j.io.pagecache.PagedFile#PF_SHARED_LOCK} flag. + */ +@SuppressWarnings( "unchecked" ) +class AdversarialReadPageCursor implements PageCursor +{ + private final PageCursor delegate; + private final Adversary adversary; + + private boolean currentReadIsInconsistent; + + AdversarialReadPageCursor( PageCursor delegate, Adversary adversary ) + { + this.delegate = Objects.requireNonNull( delegate ); + this.adversary = Objects.requireNonNull( adversary ); + } + + @Override + public byte getByte() + { + return currentReadIsInconsistent ? 0 : delegate.getByte(); + } + + @Override + public byte getByte( int offset ) + { + return currentReadIsInconsistent ? 0 : delegate.getByte( offset ); + } + + @Override + public void putByte( byte value ) + { + throw new IllegalStateException( "Cannot write using read cursor" ); + } + + @Override + public void putByte( int offset, byte value ) + { + throw new IllegalStateException( "Cannot write using read cursor" ); + } + + @Override + public long getLong() + { + return currentReadIsInconsistent ? 0 : delegate.getLong(); + } + + @Override + public long getLong( int offset ) + { + return currentReadIsInconsistent ? 0 : delegate.getLong( offset ); + } + + @Override + public void putLong( long value ) + { + throw new IllegalStateException( "Cannot write using read cursor" ); + } + + @Override + public void putLong( int offset, long value ) + { + throw new IllegalStateException( "Cannot write using read cursor" ); + } + + @Override + public int getInt() + { + return currentReadIsInconsistent ? 0 : delegate.getInt(); + } + + @Override + public int getInt( int offset ) + { + return currentReadIsInconsistent ? 0 : delegate.getInt( offset ); + } + + @Override + public void putInt( int value ) + { + throw new IllegalStateException( "Cannot write using read cursor" ); + } + + @Override + public void putInt( int offset, int value ) + { + throw new IllegalStateException( "Cannot write using read cursor" ); + } + + @Override + public long getUnsignedInt() + { + return currentReadIsInconsistent ? 0 : delegate.getUnsignedInt(); + } + + @Override + public long getUnsignedInt( int offset ) + { + return currentReadIsInconsistent ? 0 : delegate.getUnsignedInt( offset ); + } + + @Override + public void getBytes( byte[] data ) + { + if ( !currentReadIsInconsistent ) + { + delegate.getBytes( data ); + } + } + + @Override + public void putBytes( byte[] data ) + { + throw new IllegalStateException( "Cannot write using read cursor" ); + } + + @Override + public short getShort() + { + return currentReadIsInconsistent ? 0 : delegate.getShort(); + } + + @Override + public short getShort( int offset ) + { + return currentReadIsInconsistent ? 0 : delegate.getShort( offset ); + } + + @Override + public void putShort( short value ) + { + throw new IllegalStateException( "Cannot write using read cursor" ); + } + + @Override + public void putShort( int offset, short value ) + { + throw new IllegalStateException( "Cannot write using read cursor" ); + } + + @Override + public void setOffset( int offset ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + delegate.setOffset( offset ); + } + + @Override + public int getOffset() + { + return delegate.getOffset(); + } + + @Override + public long getCurrentPageId() + { + return delegate.getCurrentPageId(); + } + + @Override + public int getCurrentPageSize() + { + return delegate.getCurrentPageSize(); + } + + @Override + public File getCurrentFile() + { + return delegate.getCurrentFile(); + } + + @Override + public void rewind() + { + delegate.rewind(); + } + + @Override + public boolean next() throws IOException + { + currentReadIsInconsistent = adversary.injectFailureOrMischief( FileNotFoundException.class, IOException.class, + SecurityException.class, IllegalStateException.class ); + return delegate.next(); + } + + @Override + public boolean next( long pageId ) throws IOException + { + currentReadIsInconsistent = adversary.injectFailureOrMischief( FileNotFoundException.class, IOException.class, + SecurityException.class, IllegalStateException.class ); + return delegate.next( pageId ); + } + + @Override + public void close() + { + delegate.close(); + } + + @Override + public boolean shouldRetry() throws IOException + { + adversary.injectFailure( FileNotFoundException.class, IOException.class, SecurityException.class, + IllegalStateException.class ); + if ( currentReadIsInconsistent ) + { + currentReadIsInconsistent = false; + delegate.shouldRetry(); + delegate.setOffset( 0 ); + return true; + } + return delegate.shouldRetry(); + } +} diff --git a/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialWritePageCursor.java b/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialWritePageCursor.java new file mode 100644 index 0000000000000..e81e8dbedfec7 --- /dev/null +++ b/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialWritePageCursor.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.adversaries.pagecache; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Objects; + +import org.neo4j.adversaries.Adversary; +import org.neo4j.io.pagecache.PageCursor; + +/** + * A write {@linkplain PageCursor page cursor} that wraps another page cursor and an {@linkplain Adversary adversary} + * to provide a misbehaving page cursor implementation for testing. + *

+ * Depending on the adversary each read and write operation can throw either {@link RuntimeException} like + * {@link SecurityException} or {@link IOException} like {@link FileNotFoundException}. + *

+ * Read operations will always return a consistent value because the underlying page is exclusively write locked. + * See {@link org.neo4j.io.pagecache.PagedFile#PF_EXCLUSIVE_LOCK} flag. + */ +@SuppressWarnings( "unchecked" ) +class AdversarialWritePageCursor implements PageCursor +{ + private final PageCursor delegate; + private final Adversary adversary; + + AdversarialWritePageCursor( PageCursor delegate, Adversary adversary ) + { + this.delegate = Objects.requireNonNull( delegate ); + this.adversary = Objects.requireNonNull( adversary ); + } + + @Override + public byte getByte() + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + return delegate.getByte(); + } + + @Override + public byte getByte( int offset ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + return delegate.getByte( offset ); + } + + @Override + public void putByte( byte value ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + delegate.putByte( value ); + } + + @Override + public void putByte( int offset, byte value ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + delegate.putByte( offset, value ); + } + + @Override + public long getLong() + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + return delegate.getLong(); + } + + @Override + public long getLong( int offset ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + return delegate.getLong( offset ); + } + + @Override + public void putLong( long value ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + delegate.putLong( value ); + } + + @Override + public void putLong( int offset, long value ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + delegate.putLong( offset, value ); + } + + @Override + public int getInt() + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + return delegate.getInt(); + } + + @Override + public int getInt( int offset ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + return delegate.getInt( offset ); + } + + @Override + public void putInt( int value ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + delegate.putInt( value ); + } + + @Override + public void putInt( int offset, int value ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + delegate.putInt( offset, value ); + } + + @Override + public long getUnsignedInt() + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + return delegate.getUnsignedInt(); + } + + @Override + public long getUnsignedInt( int offset ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + return delegate.getUnsignedInt( offset ); + } + + @Override + public void getBytes( byte[] data ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + delegate.getBytes( data ); + } + + @Override + public void putBytes( byte[] data ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + delegate.putBytes( data ); + } + + @Override + public short getShort() + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + return delegate.getShort(); + } + + @Override + public short getShort( int offset ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + return delegate.getShort( offset ); + } + + @Override + public void putShort( short value ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + delegate.putShort( value ); + } + + @Override + public void putShort( int offset, short value ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + delegate.putShort( offset, value ); + } + + @Override + public void setOffset( int offset ) + { + adversary.injectFailure( IndexOutOfBoundsException.class ); + delegate.setOffset( offset ); + } + + @Override + public int getOffset() + { + return delegate.getOffset(); + } + + @Override + public long getCurrentPageId() + { + return delegate.getCurrentPageId(); + } + + @Override + public int getCurrentPageSize() + { + return delegate.getCurrentPageSize(); + } + + @Override + public File getCurrentFile() + { + return delegate.getCurrentFile(); + } + + @Override + public void rewind() + { + delegate.rewind(); + } + + @Override + public boolean next() throws IOException + { + adversary.injectFailure( FileNotFoundException.class, IOException.class, SecurityException.class, + IllegalStateException.class ); + return delegate.next(); + } + + @Override + public boolean next( long pageId ) throws IOException + { + adversary.injectFailure( FileNotFoundException.class, IOException.class, SecurityException.class, + IllegalStateException.class ); + return delegate.next( pageId ); + } + + @Override + public void close() + { + delegate.close(); + } + + @Override + public boolean shouldRetry() throws IOException + { + adversary.injectFailure( FileNotFoundException.class, IOException.class, SecurityException.class, + IllegalStateException.class ); + return delegate.shouldRetry(); + } +} diff --git a/community/io/src/test/java/org/neo4j/io/pagecache/StubPageCursor.java b/community/io/src/test/java/org/neo4j/io/pagecache/StubPageCursor.java index 48fed1069185d..50ae30781016a 100644 --- a/community/io/src/test/java/org/neo4j/io/pagecache/StubPageCursor.java +++ b/community/io/src/test/java/org/neo4j/io/pagecache/StubPageCursor.java @@ -63,7 +63,7 @@ public File getCurrentFile() } @Override - public void rewind() throws IOException + public void rewind() { throw new UnsupportedOperationException(); } diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/store/BatchingPageCache.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/store/BatchingPageCache.java index 873c4caab8647..9222efbf4b7ae 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/store/BatchingPageCache.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/store/BatchingPageCache.java @@ -509,7 +509,7 @@ public File getCurrentFile() } @Override - public void rewind() throws IOException + public void rewind() { throw new UnsupportedOperationException( "Unsupported in this batching page cache, since it's all about strictly sequential access" ); diff --git a/community/kernel/src/test/java/org/neo4j/test/PageCacheRule.java b/community/kernel/src/test/java/org/neo4j/test/PageCacheRule.java index 6dc37930be003..f69f25b8cf910 100644 --- a/community/kernel/src/test/java/org/neo4j/test/PageCacheRule.java +++ b/community/kernel/src/test/java/org/neo4j/test/PageCacheRule.java @@ -19,18 +19,18 @@ */ package org.neo4j.test; -import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; +import org.neo4j.adversaries.Adversary; +import org.neo4j.adversaries.pagecache.AdversarialPageCache; import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.io.fs.FileSystemAbstraction; import org.neo4j.io.pagecache.PageCache; -import org.neo4j.io.pagecache.PageCursor; -import org.neo4j.io.pagecache.PagedFile; +import org.neo4j.io.pagecache.tracing.PageCacheTracer; import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.impl.pagecache.StandalonePageCacheFactory; @@ -105,8 +105,8 @@ protected void after( boolean success ) */ public PageCache withInconsistentReads( PageCache pageCache, AtomicBoolean nextReadIsInconsistent ) { - InconsistentReadDecision decision = new AtomicInconsistentReadDecision( nextReadIsInconsistent ); - return new PossiblyInconsistentPageCache( pageCache, decision ); + Adversary adversary = new AtomicBooleanInconsistentReadAdversary( nextReadIsInconsistent ); + return new AdversarialPageCache( pageCache, adversary ); } /** @@ -115,345 +115,46 @@ public PageCache withInconsistentReads( PageCache pageCache, AtomicBoolean nextR */ public PageCache withInconsistentReads( PageCache pageCache ) { - InconsistentReadDecision decision = new RandomInconsistentReadDecision(); - return new PossiblyInconsistentPageCache( pageCache, decision ); + Adversary adversary = new RandomInconsistentReadAdversary(); + return new AdversarialPageCache( pageCache, adversary ); } - private static interface InconsistentReadDecision + private static class AtomicBooleanInconsistentReadAdversary implements Adversary { - boolean isNextReadInconsistent(); - } - - private static class AtomicInconsistentReadDecision implements InconsistentReadDecision - { - private final AtomicBoolean nextReadIsInconsistent; + final AtomicBoolean nextReadIsInconsistent; - public AtomicInconsistentReadDecision( AtomicBoolean nextReadIsInconsistent ) + AtomicBooleanInconsistentReadAdversary( AtomicBoolean nextReadIsInconsistent ) { this.nextReadIsInconsistent = nextReadIsInconsistent; } @Override - public boolean isNextReadInconsistent() - { - return nextReadIsInconsistent.getAndSet( false ); - } - } - - private static class RandomInconsistentReadDecision implements InconsistentReadDecision - { - @Override - public boolean isNextReadInconsistent() - { - return ThreadLocalRandom.current().nextBoolean(); - } - } - - private static class PossiblyInconsistentPageCache implements PageCache - { - private final PageCache pageCache; - private final InconsistentReadDecision decision; - - public PossiblyInconsistentPageCache( PageCache pageCache, InconsistentReadDecision decision ) - { - this.pageCache = pageCache; - this.decision = decision; - } - - @Override - public PagedFile map( File file, int pageSize ) throws IOException - { - PagedFile pagedFile = pageCache.map( file, pageSize ); - return new PossiblyInconsistentPagedFile( pagedFile, decision ); - } - - @Override - public void flushAndForce() throws IOException - { - pageCache.flushAndForce(); - } - - @Override - public void close() throws IOException - { - pageCache.close(); - } - - @Override - public int pageSize() - { - return pageCache.pageSize(); - } - - @Override - public int maxCachedPages() - { - return pageCache.maxCachedPages(); - } - } - - private static class PossiblyInconsistentPagedFile implements PagedFile - { - private final PagedFile pagedFile; - private final InconsistentReadDecision decision; - - public PossiblyInconsistentPagedFile( - PagedFile pagedFile, InconsistentReadDecision decision ) - { - this.pagedFile = pagedFile; - this.decision = decision; - } - - @Override - public String toString() - { - return "PossiblyInconsistent:" + pagedFile; - } - - @Override - public PageCursor io( long pageId, int pf_flags ) throws IOException - { - PageCursor cursor = pagedFile.io( pageId, pf_flags ); - if ( (pf_flags & PF_SHARED_LOCK) == PF_SHARED_LOCK ) - { - return new PossiblyInconsistentPageCursor( cursor, decision ); - } - return cursor; - } - - @Override - public int pageSize() - { - return pagedFile.pageSize(); - } - - @Override - public void flushAndForce() throws IOException - { - pagedFile.flushAndForce(); - } - - @Override - public void force() throws IOException - { - pagedFile.force(); - } - - @Override - public long getLastPageId() throws IOException + @SafeVarargs + public final void injectFailure( Class... failureTypes ) { - return pagedFile.getLastPageId(); } @Override - public void close() throws IOException + @SafeVarargs + public final boolean injectFailureOrMischief( Class... failureTypes ) { - pagedFile.close(); + return nextReadIsInconsistent.getAndSet( false ); } } - private static class PossiblyInconsistentPageCursor implements PageCursor + private static class RandomInconsistentReadAdversary implements Adversary { - private final PageCursor cursor; - private final InconsistentReadDecision decision; - private boolean currentReadIsInconsistent; - - public PossiblyInconsistentPageCursor( - PageCursor cursor, InconsistentReadDecision decision ) - { - this.cursor = cursor; - this.decision = decision; - } - - @Override - public byte getByte() - { - return currentReadIsInconsistent? 0 : cursor.getByte(); - } - - @Override - public byte getByte( int offset ) - { - return currentReadIsInconsistent? 0 : cursor.getByte( offset ); - } - - @Override - public void putByte( byte value ) - { - cursor.putByte( value ); - } - - @Override - public void putByte( int offset, byte value ) - { - cursor.putByte( offset, value ); - } - @Override - public long getLong() + @SafeVarargs + public final void injectFailure( Class... failureTypes ) { - return currentReadIsInconsistent? 0 : cursor.getLong(); } @Override - public long getLong( int offset ) + @SafeVarargs + public final boolean injectFailureOrMischief( Class... failureTypes ) { - return currentReadIsInconsistent? 0 : cursor.getLong( offset ); - } - - @Override - public void putLong( long value ) - { - cursor.putLong( value ); - } - - @Override - public void putLong( int offset, long value ) - { - cursor.putLong( offset, value ); - } - - @Override - public int getInt() - { - return currentReadIsInconsistent? 0 : cursor.getInt(); - } - - @Override - public int getInt( int offset ) - { - return currentReadIsInconsistent? 0 : cursor.getInt( offset ); - } - - @Override - public void putInt( int value ) - { - cursor.putInt( value ); - } - - @Override - public void putInt( int offset, int value ) - { - cursor.putInt( offset, value ); - } - - @Override - public long getUnsignedInt() - { - return currentReadIsInconsistent? 0 : cursor.getUnsignedInt(); - } - - @Override - public long getUnsignedInt( int offset ) - { - return currentReadIsInconsistent? 0 : cursor.getUnsignedInt( offset ); - } - - @Override - public void getBytes( byte[] data ) - { - if ( !currentReadIsInconsistent ) - { - cursor.getBytes( data ); - } - } - - @Override - public void putBytes( byte[] data ) - { - cursor.putBytes( data ); - } - - @Override - public short getShort() - { - return currentReadIsInconsistent? 0 : cursor.getShort(); - } - - @Override - public short getShort( int offset ) - { - return currentReadIsInconsistent? 0 : cursor.getShort( offset ); - } - - @Override - public void putShort( short value ) - { - cursor.putShort( value ); - } - - @Override - public void putShort( int offset, short value ) - { - cursor.putShort( offset, value ); - } - - @Override - public void setOffset( int offset ) - { - cursor.setOffset( offset ); - } - - @Override - public int getOffset() - { - return cursor.getOffset(); - } - - @Override - public long getCurrentPageId() - { - return cursor.getCurrentPageId(); - } - - @Override - public int getCurrentPageSize() - { - return cursor.getCurrentPageSize(); - } - - @Override - public File getCurrentFile() - { - return cursor.getCurrentFile(); - } - - @Override - public void rewind() throws IOException - { - cursor.rewind(); - } - - @Override - public boolean next() throws IOException - { - currentReadIsInconsistent = decision.isNextReadInconsistent(); - return cursor.next(); - } - - @Override - public boolean next( long pageId ) throws IOException - { - currentReadIsInconsistent = decision.isNextReadInconsistent(); - return cursor.next( pageId ); - } - - @Override - public void close() - { - cursor.close(); - } - - @Override - public boolean shouldRetry() throws IOException - { - if ( currentReadIsInconsistent ) - { - currentReadIsInconsistent = false; - cursor.shouldRetry(); - return true; - } - return cursor.shouldRetry(); + return ThreadLocalRandom.current().nextBoolean(); } } }