blob: 3ddbb0cd1f3aaec335c6cd7f2fe9418e99ae5de4 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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.jetbrains.idea.svn;
import com.intellij.idea.Bombed;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.application.PluginPathManager;
import com.intellij.util.concurrency.Semaphore;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.junit.Before;
import org.junit.Ignore;
import org.tmatesoft.sqljet.core.SqlJetException;
import org.tmatesoft.sqljet.core.table.ISqlJetBusyHandler;
import org.tmatesoft.sqljet.core.table.ISqlJetTransaction;
import org.tmatesoft.sqljet.core.table.SqlJetDb;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
import org.tmatesoft.svn.core.wc.ISVNRepositoryPool;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNWCClient;
import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Created with IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 10/23/12
* Time: 2:27 PM
*/
// TODO: Locking functionality which is tested by this test is not required anymore. Likely test needs to be removed.
@Ignore
public class SvnLockingTest extends TestCase {
private File myWorkingCopyRoot;
private SvnTestWriteOperationLocks myLocks;
@Before
public void setUp() throws Exception {
super.setUp();
//PlatformTestCase.initPlatformLangPrefix();
File pluginRoot = new File(PluginPathManager.getPluginHomePath("svn4idea"));
if (!pluginRoot.isDirectory()) {
// try standalone mode
Class aClass = Svn17TestCase.class;
String rootPath = PathManager.getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class");
pluginRoot = new File(rootPath).getParentFile().getParentFile().getParentFile();
}
myWorkingCopyRoot = new File(pluginRoot, "testData/move2unv");
myLocks = new SvnTestWriteOperationLocks(new WorkingCopy(myWorkingCopyRoot, SVNURL.parseURIEncoded("http://a.b.c"), true));
}
@Override
public void tearDown() throws Exception {
super.tearDown();
}
public void testPrepare() throws Exception {
final HangInWrite operation1 = new HangInWrite("one", false);
operation1.hang();
operation1.go();
}
public void testPrepareRead() throws Exception {
final HangInRead read = new HangInRead("READ", false);
read.run();
read.go();
}
public void testWritesSequential() throws Exception {
final HangInWrite operation1 = new HangInWrite("one_");
final HangInWrite operation2 = new HangInWrite("two_");
final Thread thread1 = new Thread(operation1);
final Thread thread2 = new Thread(operation2);
try {
thread1.start();
waitForRunning(operation1);
Assert.assertTrue(operation1.isRunning());
thread2.start();
waitForRunning(operation2);
Assert.assertFalse(operation2.isRunning());
operation1.go();
waitForRunning(operation2);
Assert.assertTrue(operation2.isRunning());
operation2.go();
Thread.sleep(10);
} finally {
operation1.stop();
operation2.stop();
thread1.interrupt();
thread2.interrupt();
}
}
public void testOnlyWrites() throws Exception {
final OnlyWrite operation1 = new OnlyWrite("one");
final OnlyWrite operation2 = new OnlyWrite("two");
final Thread thread1 = new Thread(operation1);
final Thread thread2 = new Thread(operation2);
try {
thread1.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
//
}
Assert.assertTrue(operation1.isInsideWrite());
thread2.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
//
}
Assert.assertFalse(operation2.isInsideWrite());
operation1.go();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
//
}
Assert.assertTrue(operation2.isInsideWrite());
operation2.go();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
//
}
} finally {
myLocks.dispose();
operation1.stop();
operation2.stop();
Thread.sleep(100);
thread1.interrupt();
thread2.interrupt();
}
}
/*public void testDelays() throws Exception {
final HandlerCopy handler = new HandlerCopy(10000);
for (int i = 0; i < 1000; i++) {
final Pair<Boolean, Integer> pair = handler.call(i);
System.out.println("# " + i + " DELAY: " + pair.getSecond() + " CONTINUE: " + pair.getFirst());
}
}
private static class HandlerCopy {
private static final int[] delays = { 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 };
private static final int[] totals = { 0, 1, 3, 8, 18, 33, 53, 78, 103, 128, 178, 228 };
private final int timeout;
public HandlerCopy(int timeout) {
this.timeout = timeout;
}
public Pair<Boolean, Integer> call(int number) {
int delay;
int prior;
if (number < delays.length) {
delay = delays[number];
prior = totals[number];
} else {
delay = delays[delays.length - 1];
prior = totals[delays.length - 1] + delay*(number - (delays.length - 1));
}
if (prior + delay > timeout) {
delay = timeout - prior;
if (delay <= 0) {
return new Pair<Boolean, Integer>(false, delay);
}
}
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
//
}
return new Pair<Boolean, Integer>(true, delay);
}
}*/
@Bombed(year=2020, month = 1,day = 1,description = "not clear. by specification, read should not get access if write lock is taken; sometimes it is not the case.")
public void testReadInBetweenWrites() throws Exception {
final HangInWrite operation1 = new HangInWrite("one1");
final HangInWrite operation2 = new HangInWrite("two1");
final HangInRead read = new HangInRead("READ");
final Thread thread1 = new Thread(operation1);
final Thread threadRead = new Thread(read);
final Thread thread2 = new Thread(operation2);
try {
thread1.start();
waitForRunning(operation1);
Assert.assertTrue(operation1.isRunning());
threadRead.start();
waitForRunning(read);
Assert.assertFalse(read.isRunning()); // not clear why read is allowed to run when write is active, but I've not thought it over so much
operation1.go();
waitForRunning(read, 20);
Assert.assertTrue(read.isRunning());
thread2.start();
waitForRunning(operation2);
Assert.assertFalse(operation2.isRunning()); // again, not clear why write is allowed to run when read is active, but I've not thought it over so much
read.go();
waitForRunning(operation2);
Assert.assertTrue(operation2.isRunning());
// have some time after read to complete
Thread.sleep(100);
} finally {
myLocks.dispose();
operation1.stop();
operation2.stop();
Thread.sleep(100);
thread1.interrupt();
thread2.interrupt();
threadRead.interrupt();
}
}
private void waitForRunning(HangRun operation1) {
waitForRunning(operation1, 1);
}
private void waitForRunning(HangRun operation1, int multiply) {
int cnt = 10 * multiply;
while (cnt > 0) {
try {
Thread.sleep(10 * multiply);
} catch (InterruptedException e) {
//
}
if (operation1.isRunning()) break;
-- cnt;
}
}
public interface HangRun {
void go();
boolean isRunning();
void stop();
}
private class HangInRead implements Runnable, HangRun {
private final AtomicBoolean myIsRunning;
private final String myName;
private final boolean myWaitFor;
private final Semaphore mySemaphore;
private HangInRead(String name) {
this(name, true);
}
private HangInRead(String name, final boolean waitFor) {
myName = name;
myWaitFor = waitFor;
myIsRunning = new AtomicBoolean(false);
mySemaphore = new Semaphore();
}
@Override
public void run() {
mySemaphore.down();
try {
System.out.println("starting read " + myName);
myLocks.wrapRead(myWorkingCopyRoot, new Runnable() {
@Override
public void run() {
System.out.println("inside read " + myName);
final SVNWCClient client = new SVNWCClient((ISVNRepositoryPool)null, new DefaultSVNOptions());
try {
client.doInfo(myWorkingCopyRoot, SVNRevision.BASE);
}
catch (SVNException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
myIsRunning.set(true);
System.out.println("got status " + myName);
if (myWaitFor) {
mySemaphore.waitFor();
}
System.out.println("have read " + myName);
}
});
}
catch (SVNException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public void go() {
System.out.println("read going " + myName);
mySemaphore.up();
}
@Override
public boolean isRunning() {
System.out.println("read running " + myName + " " + myIsRunning.get());
return myIsRunning.get();
}
@Override
public void stop() {
}
}
private class OnlyWrite extends HangInWrite {
private OnlyWrite(String name) {
super(name);
}
@Override
public void run() {
mySemaphore.down();
operation();
}
}
private class HangInWrite implements Runnable, HangRun {
protected final Semaphore mySemaphore;
private final AtomicBoolean myIsRunning;
private final AtomicBoolean myInsideWrite;
private final String myName;
private boolean shouldWait;
private volatile boolean myStopped;
private HangInWrite(final String name) {
this(name, true);
}
private HangInWrite(final String name, final boolean shouldWait) {
myName = name;
mySemaphore = new Semaphore();
this.shouldWait = shouldWait;
myIsRunning = new AtomicBoolean(false);
myInsideWrite = new AtomicBoolean(false);
}
@Override
public void run() {
try {
hang();
}
catch (SVNException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
protected void operation() {
System.out.println("TRY OPEN FOR WRITE===");
SqlJetDb open = null;
try {
open = SqlJetDb.open(SvnUtil.getWcDb(myWorkingCopyRoot), true);
open.setBusyHandler(new ISqlJetBusyHandler() {
@Override
public boolean call(int i) {
if (myStopped) return false;
System.out.println("busy " + myName);
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
//
}
return true;
}
});
try {
System.out.println("TRY OPEN FOR WRITE " + myName);
open.runWriteTransaction(new ISqlJetTransaction() {
@Override
public Object run(SqlJetDb db) throws SqlJetException {
System.out.println("OPENed FOR WRITE " + myName);
myInsideWrite.set(true);
if (shouldWait) {
mySemaphore.waitFor();
}
return null;
}
});
} finally {
myInsideWrite.set(false);
open.rollback();
}
}
catch (SqlJetException e) {
e.printStackTrace();
throw new RuntimeException(e);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
finally {
if (open != null) {
try {
open.close();
}
catch (SqlJetException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
System.out.println("CLOSed FOR WRITE " + myName);
}
}
}
public boolean isInsideWrite() {
return myInsideWrite.get();
}
public void hang() throws SVNException {
System.out.println("starting " + myName);
mySemaphore.down();
myLocks.lockWrite(myWorkingCopyRoot);
myIsRunning.set(true);
System.out.println("started " + myName);
try {
operation();
} finally {
myLocks.unlockWrite(myWorkingCopyRoot);
myIsRunning.set(false);
}
}
public boolean isRunning() {
System.out.println("running " + myName + " " + myIsRunning.get());
return myIsRunning.get();
}
@Override
public void stop() {
myStopped = true;
}
public void go() {
System.out.println("going " + myName);
mySemaphore.up();
}
}
}