SVN: made-aside work for Subversion locking synchronization

idea: use "SQLite read access wrapper" for read operations; use inner locking for write operations

not finished since sqljet have fundamental bug, if fixed, maybe IDEA-level locking is not necessary
This commit is contained in:
irengrig
2012-10-23 19:51:10 +04:00
parent 7fd0c3b4ae
commit 3f169f5937
21 changed files with 1025 additions and 81 deletions
@@ -28,7 +28,7 @@ import java.util.*;
* Date: 10/19/12
* Time: 12:42 PM
*/
public abstract class LearningProxy<T> implements InvocationHandler {
public abstract class LearningProxy<T, E extends Throwable> {
private final static Map<String, Object> ourDefaultValues = new HashMap<String, Object>();
static {
ourDefaultValues.put("byte", new Byte((byte) 0));
@@ -42,13 +42,25 @@ public abstract class LearningProxy<T> implements InvocationHandler {
ourDefaultValues.put("void", null);
}
private final Set<MethodDescriptor> myTrackedMethods;
private final InvocationHandler myLearn;
public LearningProxy() {
myTrackedMethods = new HashSet<MethodDescriptor>();
myLearn = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
myTrackedMethods.add(new MethodDescriptor(method));
final Class<?> returnType = method.getReturnType();
if (returnType.isPrimitive()) {
return ourDefaultValues.get(returnType.getName());
}
return null;
}
};
}
protected abstract void onBefore();
protected abstract void onAfter();
protected abstract void onBefore() throws E;
protected abstract void onAfter() throws E;
public <T> T create(Class<T> clazz, final T t) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
@@ -58,6 +70,7 @@ public abstract class LearningProxy<T> implements InvocationHandler {
if (myTrackedMethods.contains(current)) {
try {
onBefore();
method.setAccessible(true);
return method.invoke(t, args);
} finally {
onAfter();
@@ -69,17 +82,7 @@ public abstract class LearningProxy<T> implements InvocationHandler {
}
public <T> T learn(Class<T> clazz) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
myTrackedMethods.add(new MethodDescriptor(method));
final Class<?> returnType = method.getReturnType();
if (returnType.isPrimitive()) {
return ourDefaultValues.get(returnType.getName());
}
return null;
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, myLearn);
}
private static class MethodDescriptor {
@@ -0,0 +1,56 @@
/*
* Copyright 2000-2012 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 com.intellij.vcsUtil;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Created with IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 10/22/12
* Time: 10:58 AM
*/
public class PseudoImplementProxy {
public static <T, Impl> T create(final Class<T> theInterface, final Impl implementation) {
checkMethodsExist(theInterface, implementation);
final Class<?> implementationClass = implementation.getClass();
return (T) Proxy.newProxyInstance(theInterface.getClassLoader(), new Class[]{theInterface},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final Method implementationClassMethod =
implementationClass.getMethod(method.getName(), method.getParameterTypes());
implementationClassMethod.setAccessible(true);
return implementationClassMethod.invoke(implementation, args);
}
});
}
private static <T, Impl> void checkMethodsExist(Class<T> theInterface, Impl implementation) {
final Class<?> implementationClass = implementation.getClass();
final Method[] methods = theInterface.getDeclaredMethods();
for (Method method : methods) {
try {
implementationClass.getMethod(method.getName(), method.getParameterTypes());
}
catch (NoSuchMethodException e) {
throw new UnsupportedOperationException(e);
}
}
}
}
Binary file not shown.
@@ -90,7 +90,7 @@ public class RootsToWorkingCopies implements VcsListener {
@Nullable
@CalledInBackground
public WorkingCopy getMatchingCopy(final SVNURL url) {
assert ! ApplicationManager.getApplication().isDispatchThread();
assert (! ApplicationManager.getApplication().isDispatchThread()) || ApplicationManager.getApplication().isUnitTestMode();
if (url == null) return null;
final VirtualFile[] roots = ProjectLevelVcsManager.getInstance(myProject).getRootsUnderVcs(SvnVcs.getInstance(myProject));
@@ -111,7 +111,7 @@ public class RootsToWorkingCopies implements VcsListener {
@CalledInBackground
@Nullable
public WorkingCopy getWcRoot(final VirtualFile root) {
assert ! ApplicationManager.getApplication().isDispatchThread();
assert (! ApplicationManager.getApplication().isDispatchThread()) || ApplicationManager.getApplication().isUnitTestMode();
synchronized (myLock) {
if (myUnversioned.contains(root)) return null;
@@ -0,0 +1,142 @@
/*
* Copyright 2000-2012 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.openapi.diagnostic.Logger;
import com.intellij.openapi.vcs.changes.FilePathsHelper;
import org.jetbrains.annotations.Nullable;
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 java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created with IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 10/19/12
* Time: 12:09 PM
*/
public abstract class SvnAbstractWriteOperationLocks {
private final long myTimeout;
private final static Map<String, Lock> myLockMap = new HashMap<String, Lock>();
private final static Object myLock = new Object();
private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnAbstractWriteOperationLocks");
private ISqlJetBusyHandler ourBusyHandler;
protected SvnAbstractWriteOperationLocks(long timeout) {
myTimeout = timeout;
ourBusyHandler = new ISqlJetBusyHandler() {
@Override
public boolean call(int i) {
try {
Thread.sleep(myTimeout);
}
catch (InterruptedException e) {
//
}
return true;
}
};
}
// null if not 1.7+ copy
@Nullable
private Lock getLockObject(File file) throws SVNException {
final boolean directory = file.isDirectory();
final WorkingCopy wcRoot = getCopy(file, directory);
if (! wcRoot.is17Copy()) return null;
Lock lock;
final String path = FilePathsHelper.convertPath(wcRoot.getFile().getPath());
synchronized (myLock) {
lock = myLockMap.get(path);
if (lock == null) {
lock = new ReentrantLock();
myLockMap.put(path, lock);
}
}
return lock;
}
protected abstract WorkingCopy getCopy(File file, boolean directory) throws SVNException;
public void lockWrite(final File file) throws SVNException {
final Lock lock = getLockObject(file);
if (lock != null) {
lock.lock();
}
}
public void unlockWrite(final File file) throws SVNException {
final Lock lock = getLockObject(file);
if (lock != null) {
lock.unlock();
}
}
// would wait until read is available
public void wrapRead(final File file, final Runnable runnable) throws SVNException {
final WorkingCopy copy = getCopy(file, file.isDirectory());
if (! copy.is17Copy()) {
runnable.run();
return;
}
final File root = copy.getFile();
SqlJetDb open = null;
final boolean run[] = new boolean[1];
run[0] = false;
try {
open = SqlJetDb.open(SvnUtil.getWcDb(root), false);
open.setBusyHandler(ourBusyHandler);
try {
final SqlJetDb finalOpen = open;
open.runReadTransaction(new ISqlJetTransaction() {
@Override
public Object run(SqlJetDb db) throws SqlJetException {
run[0] = true;
runnable.run();
return null;
}
});
} finally {
open.rollback();
}
}
catch (SqlJetException e) {
LOG.info(e);
if (! run[0]) {
runnable.run();
}
}
finally {
if (open != null) {
try {
open.close();
}
catch (SqlJetException e) {
LOG.info(e);
}
}
}
}
}
@@ -0,0 +1,112 @@
/*
* Copyright 2000-2012 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.vcsUtil.LearningProxy;
import org.jetbrains.idea.svn.portable.SVNChangelistClientI;
import org.jetbrains.idea.svn.portable.SvnUpdateClientI;
import org.tmatesoft.svn.core.SVNException;
import java.io.File;
import java.util.concurrent.atomic.AtomicReference;
/**
* Created with IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 10/19/12
* Time: 4:22 PM
*/
public class SvnProxies {
private final SvnAbstractWriteOperationLocks myLocks;
private final AtomicReference<LearningProxy<SVNChangelistClientI, SVNException>> myChangelist;
private final AtomicReference<LearningProxy<SvnUpdateClientI, SVNException>> myUpdate;
public SvnProxies(SvnAbstractWriteOperationLocks locks) {
myLocks = locks;
myChangelist = new AtomicReference<LearningProxy<SVNChangelistClientI, SVNException>>();
myUpdate = new AtomicReference<LearningProxy<SvnUpdateClientI, SVNException>>();
}
public LearningProxy<SVNChangelistClientI, SVNException> getChangelistsProfessor(final File file) {
if (myChangelist.get() == null) {
final MyLearningProxy<SVNChangelistClientI> proxy = new MyLearningProxy<SVNChangelistClientI>(myLocks, file);
final SVNChangelistClientI learn = proxy.learn(SVNChangelistClientI.class);
try {
learn.addToChangelist(null, null, null, null);
learn.doAddToChangelist(null, null, null, null);
learn.doRemoveFromChangelist(null, null, null);
learn.removeFromChangelist(null, null, null);
} catch (SVNException e) {
//can not occur since methods are not really called
throw new RuntimeException(e);
}
myChangelist.set(proxy);
}
return myChangelist.get();
}
public LearningProxy<SvnUpdateClientI, SVNException> getUpdateProfessor(final File file) {
if (myUpdate.get() == null) {
final MyLearningProxy<SvnUpdateClientI> proxy = new MyLearningProxy<SvnUpdateClientI>(myLocks, file);
final SvnUpdateClientI learn = proxy.learn(SvnUpdateClientI.class);
try {
learn.doUpdate(null, null, false);
learn.doUpdate(null, null, false, false);
learn.doUpdate((File[]) null, null, null, false, false);
learn.doUpdate((File[]) null, null, null, false, false, false);
learn.doUpdate((File) null, null, null, false, false);
learn.doSwitch(null, null, null, false);
learn.doSwitch(null, null, null, null, false);
learn.doSwitch(null, null, null, null, false, false);
learn.doSwitch(null, null, null, null, null, false, false);
learn.doSwitch(null, null, null, null, null, false, false, false);
learn.doCheckout(null, null, null, null, false);
// todo continue
//learn.doCheckout();
//learn.doCheckout();
}
catch (SVNException e) {
//can not occur since methods are not really called
throw new RuntimeException(e);
}
myUpdate.set(proxy);
}
return myUpdate.get();
}
private static class MyLearningProxy<T> extends LearningProxy<T, SVNException> {
private final SvnAbstractWriteOperationLocks myLocks;
private final File myFile;
private MyLearningProxy(SvnAbstractWriteOperationLocks locks, final File file) {
myLocks = locks;
myFile = file;
}
@Override
protected void onBefore() throws SVNException {
myLocks.lockWrite(myFile);
}
@Override
protected void onAfter() throws SVNException {
myLocks.unlockWrite(myFile);
}
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2000-2012 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 org.tmatesoft.svn.core.SVNException;
import java.io.File;
/**
* Created with IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 10/23/12
* Time: 2:31 PM
*/
public class SvnTestWriteOperationLocks extends SvnAbstractWriteOperationLocks {
private final WorkingCopy myWorkingCopy;
public SvnTestWriteOperationLocks(WorkingCopy copy) {
super(10);
myWorkingCopy = copy;
}
@Override
protected WorkingCopy getCopy(File file, boolean directory) throws SVNException {
return myWorkingCopy;
}
}
@@ -583,6 +583,10 @@ public class SvnUtil {
}
}
public static File getWcDb(final File file) {
return new File(file, ".svn/wc.db");
}
@Nullable
public static File getWcCopyRootIf17(final File file, @Nullable final File upperBound) {
File current = file;
@@ -15,80 +15,36 @@
*/
package org.jetbrains.idea.svn;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.FilePathImpl;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
import com.intellij.openapi.vcs.changes.FilePathsHelper;
import com.intellij.openapi.vcs.changes.ChangesUtil;
import com.intellij.openapi.vfs.VirtualFile;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created with IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 10/19/12
* Time: 12:09 PM
* Date: 10/23/12
* Time: 2:29 PM
*/
public class SvnWriteOperationLocks {
private final ProjectLevelVcsManager myVcsManager;
private final Map<String, Lock> myLockMap;
private final Object myLock;
public class SvnWriteOperationLocks extends SvnAbstractWriteOperationLocks {
private final RootsToWorkingCopies myRootsToWorkingCopies;
public SvnWriteOperationLocks(final Project project) {
myVcsManager = ProjectLevelVcsManager.getInstance(project);
myLockMap = new HashMap<String, Lock>();
myLock = new Object();
public SvnWriteOperationLocks(RootsToWorkingCopies rootsToWorkingCopies) {
super(1000);
myRootsToWorkingCopies = rootsToWorkingCopies;
}
public void lock(final File file) throws SVNException {
final Lock lock = getLockObject(file);
lock.lock();
}
private Lock getLockObject(File file) throws SVNException {
return getLockObject(new FilePathImpl(file, file.isDirectory()), FilePathsHelper.convertPath(file.getPath()));
}
private Lock getLockObject(VirtualFile file) throws SVNException {
return getLockObject(new FilePathImpl(file), FilePathsHelper.convertPath(file.getPath()));
}
private Lock getLockObject(FilePathImpl file, final String path) throws SVNException {
final boolean directory = file.isDirectory();
final VirtualFile root = myVcsManager.getVcsRootFor(file);
if (root == null) {
protected WorkingCopy getCopy(File file, boolean directory) throws SVNException {
final VirtualFile parentOrSelf = ChangesUtil.findValidParentAccurately(new FilePathImpl(file, directory));
final WorkingCopy wcRoot = myRootsToWorkingCopies.getWcRoot(parentOrSelf);
if (wcRoot == null) {
throw new SVNException(SVNErrorMessage.create(directory ? SVNErrorCode.WC_NOT_WORKING_COPY : SVNErrorCode.WC_NOT_FILE));
}
Lock lock;
synchronized (myLock) {
lock = myLockMap.get(path);
if (lock == null) {
lock = new ReentrantLock();
myLockMap.put(path, lock);
}
}
return lock;
}
public void unlock(final File file) throws SVNException {
final Lock lock = getLockObject(file);
lock.unlock();
}
public void lock(final VirtualFile vf) throws SVNException {
final Lock lock = getLockObject(vf);
lock.lock();
}
public void unlock(final VirtualFile vf) throws SVNException {
final Lock lock = getLockObject(vf);
lock.unlock();
// todo check about externals!
return wcRoot;
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2000-2012 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.history;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
/**
* Created with IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 10/23/12
* Time: 7:23 PM
*/
public class SvnEditCommitMessageAction extends AnAction {
// todo we need:
// todo repo url
// todo revision number, and current text
@Override
public void actionPerformed(AnActionEvent e) {
//To change body of implemented methods use File | Settings | File Templates.
}
}
@@ -0,0 +1,25 @@
/*
* Copyright 2000-2012 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.history;
/**
* Created with IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 10/23/12
* Time: 7:24 PM
*/
public class SvnRevisionComment {
}
@@ -0,0 +1,47 @@
/*
* Copyright 2000-2012 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.portable;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.wc.ISVNChangelistHandler;
import java.io.File;
import java.util.Collection;
/**
* Created with IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 10/19/12
* Time: 3:50 PM
*/
public interface SVNChangelistClientI extends SvnMarkerInterface {
void getChangeLists(File path, Collection<String> changeLists, SVNDepth depth, ISVNChangelistHandler handler) throws SVNException;
void getChangeListPaths(Collection<String> changeLists, Collection<File> targets, SVNDepth depth, ISVNChangelistHandler handler) throws SVNException;
void addToChangelist(File[] paths, SVNDepth depth, String changelist, String[] changelists) throws SVNException;
void removeFromChangelist(File[] paths, SVNDepth depth, String[] changelists) throws SVNException;
void doAddToChangelist(File[] paths, SVNDepth depth, String changelist, String[] changelists) throws SVNException;
void doRemoveFromChangelist(File[] paths, SVNDepth depth, String[] changelists) throws SVNException;
void doGetChangeListPaths(Collection<String> changeLists, Collection<File> targets, SVNDepth depth, ISVNChangelistHandler handler) throws SVNException;
void doGetChangeLists(File path, Collection<String> changeLists, SVNDepth depth, ISVNChangelistHandler handler) throws SVNException;
}
@@ -0,0 +1,10 @@
package org.jetbrains.idea.svn.portable;
/**
* Created with IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 10/19/12
* Time: 3:48 PM
*/
public interface SvnMarkerInterface {
}
@@ -30,7 +30,7 @@ import java.util.Collection;
* Date: 1/24/12
* Time: 9:46 AM
*/
public interface SvnStatusClientI {
public interface SvnStatusClientI extends SvnMarkerInterface {
long doStatus(File path, boolean recursive, boolean remote, boolean reportAll,
boolean includeIgnored, ISVNStatusHandler handler) throws SVNException;
long doStatus(File path, boolean recursive, boolean remote, boolean reportAll, boolean includeIgnored, boolean collectParentExternals, ISVNStatusHandler handler) throws SVNException;
@@ -0,0 +1,85 @@
/*
* Copyright 2000-2012 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.portable;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.wc.ISVNChangelistHandler;
import org.tmatesoft.svn.core.wc.SVNChangelistClient;
import java.io.File;
import java.util.Collection;
/**
* Created with IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 10/19/12
* Time: 3:51 PM
*/
public class SvnSvnkitChangelistClient implements SVNChangelistClientI {
private final SVNChangelistClient myClient;
public SvnSvnkitChangelistClient(SVNChangelistClient client) {
myClient = client;
}
@Override
public void getChangeLists(File path, Collection<String> changeLists, SVNDepth depth, ISVNChangelistHandler handler) throws SVNException {
myClient.getChangeLists(path, changeLists, depth, handler);
}
@Override
public void getChangeListPaths(Collection<String> changeLists,
Collection<File> targets,
SVNDepth depth,
ISVNChangelistHandler handler) throws SVNException {
myClient.getChangeListPaths(changeLists, targets, depth, handler);
}
@Override
public void addToChangelist(File[] paths, SVNDepth depth, String changelist, String[] changelists) throws SVNException {
myClient.addToChangelist(paths, depth, changelist, changelists);
}
@Override
public void removeFromChangelist(File[] paths, SVNDepth depth, String[] changelists) throws SVNException {
myClient.removeFromChangelist(paths, depth, changelists);
}
@Override
public void doAddToChangelist(File[] paths, SVNDepth depth, String changelist, String[] changelists) throws SVNException {
myClient.doAddToChangelist(paths, depth, changelist, changelists);
}
@Override
public void doRemoveFromChangelist(File[] paths, SVNDepth depth, String[] changelists) throws SVNException {
myClient.doRemoveFromChangelist(paths, depth, changelists);
}
@Override
public void doGetChangeListPaths(Collection<String> changeLists,
Collection<File> targets,
SVNDepth depth,
ISVNChangelistHandler handler) throws SVNException {
myClient.doGetChangeListPaths(changeLists, targets, depth, handler);
}
@Override
public void doGetChangeLists(File path, Collection<String> changeLists, SVNDepth depth, ISVNChangelistHandler handler)
throws SVNException {
myClient.doGetChangeLists(path, changeLists, depth, handler);
}
}
@@ -29,7 +29,7 @@ import java.io.File;
* Date: 2/1/12
* Time: 11:59 AM
*/
public interface SvnUpdateClientI {
public interface SvnUpdateClientI extends SvnMarkerInterface {
long doUpdate(File file, SVNRevision revision, boolean recursive) throws SVNException;
long doUpdate(File file, SVNRevision revision, boolean recursive, boolean force) throws SVNException;
@@ -28,7 +28,7 @@ import java.util.Collection;
* Date: 1/20/12
* Time: 6:54 PM
*/
public interface SvnWcClientI {
public interface SvnWcClientI extends SvnMarkerInterface {
void setAddParameters(ISVNAddParameters addParameters);
ISVNCommitHandler getCommitHandler();
void setCommitHandler(ISVNCommitHandler handler);
+9
View File
@@ -27,6 +27,15 @@
<orderEntry type="module" module-name="lang-api" />
<orderEntry type="module" module-name="platform-resources" scope="TEST" />
<orderEntry type="module" module-name="dom-impl" scope="TEST" />
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/sqljet.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>
+7 -1
View File
@@ -51,7 +51,13 @@
<root url="jar://$MODULE_DIR$/lib/sqljet.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
<SOURCES>
<root url="jar://$MODULE_DIR$/lib/sqljetsrc.zip!/src/test/compatibility" />
<root url="jar://$MODULE_DIR$/lib/sqljetsrc.zip!/src/test/benchmark" />
<root url="jar://$MODULE_DIR$/lib/sqljetsrc.zip!/src/test/stress" />
<root url="jar://$MODULE_DIR$/lib/sqljetsrc.zip!/src/test/java" />
<root url="jar://$MODULE_DIR$/lib/sqljetsrc.zip!/src/main/java" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library" scope="RUNTIME">
@@ -108,8 +108,8 @@ public class SvnBusyOnAddTest extends TestCase {
final File ioFile = new File(myWorkingCopyRoot, filename);
ioFile.getParentFile().mkdirs();
SVNWCClient client11 = new SVNWCClient((ISVNRepositoryPool)null, new DefaultSVNOptions());
client11.doAdd(ioFile.getParentFile(), true, false, true, true);
/*SVNWCClient client11 = new SVNWCClient((ISVNRepositoryPool)null, new DefaultSVNOptions());
client11.doAdd(ioFile.getParentFile(), true, false, true, true);*/
ioFile.createNewFile();
try {
@@ -153,7 +153,7 @@ public class SvnBusyOnAddTest extends TestCase {
//
}
SVNWCClient client = new SVNWCClient((ISVNRepositoryPool)null, new DefaultSVNOptions());
client.doAdd(ioFile, true, false, true, true);
client.doAdd(ioFile.getParentFile(), true, false, true, true);
semaphore.up();
semaphoreWokeUp.waitFor();
@@ -0,0 +1,412 @@
/*
* Copyright 2000-2012 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.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
*/
public class SvnLockingTest extends TestCase {
private File myWorkingCopyRoot;
private SvnTestWriteOperationLocks myLocks;
@Before
public void setUp() throws Exception {
//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 {
}
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();
}
}
@Bombed(year=2020, month = 1,day = 1,description = "waiting for http://issues.tmatesoft.com/issue/SVNKIT-317")
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 {
operation1.stop();
operation2.stop();
thread1.interrupt();
thread2.interrupt();
}
}
@Bombed(year=2020, month = 1,day = 1,description = "waiting for http://issues.tmatesoft.com/issue/SVNKIT-317")
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);
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());
} finally {
operation1.stop();
operation2.stop();
thread1.interrupt();
thread2.interrupt();
threadRead.interrupt();
}
}
private void waitForRunning(HangRun operation1) {
int cnt = 10;
while (cnt > 0) {
try {
Thread.sleep(10);
} 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() {
myIsRunning.set(true);
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);
}
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();
}
}
}