/* * 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 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 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(false, delay); } } try { Thread.sleep(delay); } catch (InterruptedException e) { // } return new Pair(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(); } } }