changeset 631:8a5cdcb27b8f

AIOOBE in HgManifest.RevisionMapper. Provide more details about exception context. Create lock file atomically. Test concurrent pull-rebase and read
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 30 May 2013 15:24:17 +0200
parents 72c979555cb8
children 54e16ab771ec
files src/org/tmatesoft/hg/repo/HgManifest.java src/org/tmatesoft/hg/repo/HgRepositoryLock.java test/org/tmatesoft/hg/test/RepoUtils.java test/org/tmatesoft/hg/test/TestRepositoryLock.java
diffstat 4 files changed, 140 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/repo/HgManifest.java	Thu May 23 19:44:28 2013 +0200
+++ b/src/org/tmatesoft/hg/repo/HgManifest.java	Thu May 30 15:24:17 2013 +0200
@@ -662,7 +662,12 @@
 		}
 
 		// XXX can be replaced with Revlog.RevisionInspector, but I don't want Nodeid instances
-		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) {
+		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) throws HgInvalidRevisionException {
+			if (linkRevision >= changelogRevisionCount) {
+				String storeLock = HgManifest.this.getRepo().getStoreLock().readLockInfo();
+				String message = String.format("Manifest revision %d references changeset %d, which is beyond known scope [0..%d). Lock: %s", revisionNumber, linkRevision, changelogRevisionCount, storeLock);
+				throw new HgInvalidRevisionException(message, null, linkRevision);
+			}
 			if (changelog2manifest != null) {
 				// next assertion is not an error, rather assumption check, which is too development-related to be explicit exception - 
 				// I just wonder if there are manifests that have two entries pointing to single changeset. It seems unrealistic, though -
--- a/src/org/tmatesoft/hg/repo/HgRepositoryLock.java	Thu May 23 19:44:28 2013 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRepositoryLock.java	Thu May 30 15:24:17 2013 +0200
@@ -120,7 +120,7 @@
 		do {
 			synchronized(this) {
 				try {
-					if (!lockFile.exists()) {
+					if (lockFile.createNewFile()) {
 						write(lockFile, bytes);
 						use++;
 						return;
--- a/test/org/tmatesoft/hg/test/RepoUtils.java	Thu May 23 19:44:28 2013 +0200
+++ b/test/org/tmatesoft/hg/test/RepoUtils.java	Thu May 30 15:24:17 2013 +0200
@@ -66,6 +66,10 @@
 	}
 
 	static File cloneRepoToTempLocation(HgRepository repo, String name, boolean noupdate) throws IOException, InterruptedException {
+		return cloneRepoToTempLocation(repo.getWorkingDir(), name, noupdate, false);
+	}
+
+	static File cloneRepoToTempLocation(File repoLoc, String name, boolean noupdate, boolean usePull) throws IOException, InterruptedException {
 		File testRepoLoc = createEmptyDir(name);
 		ExecHelper eh = new ExecHelper(new OutputParser.Stub(), testRepoLoc.getParentFile());
 		ArrayList<String> cmd = new ArrayList<String>();
@@ -74,7 +78,10 @@
 		if (noupdate) {
 			cmd.add("--noupdate");
 		}
-		cmd.add(repo.getWorkingDir().toString());
+		if (usePull) {
+			cmd.add("--pull");
+		}
+		cmd.add(repoLoc.toString());
 		cmd.add(testRepoLoc.getName());
 		eh.run(cmd.toArray(new String[cmd.size()]));
 		assertEquals("[sanity]", 0, eh.getExitValue());
--- a/test/org/tmatesoft/hg/test/TestRepositoryLock.java	Thu May 23 19:44:28 2013 +0200
+++ b/test/org/tmatesoft/hg/test/TestRepositoryLock.java	Thu May 30 15:24:17 2013 +0200
@@ -17,9 +17,15 @@
 package org.tmatesoft.hg.test;
 
 import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
 
 import org.junit.Assert;
 import org.junit.Test;
+import org.tmatesoft.hg.core.HgStatusCommand;
+import org.tmatesoft.hg.internal.BasicSessionContext;
+import org.tmatesoft.hg.internal.DataAccessProvider;
 import org.tmatesoft.hg.repo.HgLookup;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgRepositoryLock;
@@ -50,4 +56,123 @@
 			wdLock.release();
 		}
 	}
+
+	public static void main(String[] args) throws Exception {
+		Map<String, Object> po = new HashMap<String, Object>();
+		po.put(DataAccessProvider.CFG_PROPERTY_MAPIO_LIMIT, 0);
+		final HgLookup hgLookup = new HgLookup(new BasicSessionContext(po , null));
+		final File rebaseFromRepoLoc = RepoUtils.cloneRepoToTempLocation(new File("/temp/hg/junit-test-repos/test-annotate"), "repo-lock-remote", false, true);
+		final File rebaseToRepoLoc = RepoUtils.cloneRepoToTempLocation(rebaseFromRepoLoc, "repo-lock-local", false, true);
+		final File remoteChanges = new File(rebaseFromRepoLoc, "file1");
+		//
+		// create commit in the "local" repository that will be rebased on top of changes
+		// pulled from "remote repository"
+		File localChanges = new File(rebaseToRepoLoc, "file-new");
+		if (localChanges.exists()) {
+			RepoUtils.modifyFileAppend(localChanges, "whatever");
+		} else {
+			RepoUtils.createFile(localChanges, "whatever");
+		}
+		commit(rebaseToRepoLoc, "local change");
+		//
+		final int rebaseRevisionCount = 70;
+		final CountDownLatch latch = new CountDownLatch(2);
+		Runnable r1 = new Runnable() {
+			public void run() {
+				for (int i = 0; i < rebaseRevisionCount; i++) {
+					commitPullRebaseNative(rebaseFromRepoLoc, rebaseToRepoLoc, remoteChanges);
+					sleep(500, 1000);
+				}
+				latch.countDown();
+			}
+		};
+		Runnable r2 = new Runnable() {
+			public void run() {
+				for (int i = 0; i < 100; i++) {
+					readWithHg4J(hgLookup, rebaseToRepoLoc);
+					sleep(800, 400);
+				}
+				latch.countDown();
+			}
+		};
+		new Thread(r1, "pull-rebase-thread").start();
+		new Thread(r2, "hg4j-read-thread").start();
+		latch.await();
+		System.out.println("DONE.");
+		// now `hg log` in rebaseToRepoLoc shall show 
+		// all rebaseRevisionCount revisions from rebaseFromRepoLoc + 1 more, "local change", on top of them
+	}
+
+	private static int count = 0;
+
+	private static void commitPullRebaseNative(final File rebaseFromRepoLoc, final File rebaseToRepoLoc, final File rebaseFromChanges) {
+		try {
+			OutputParser.Stub p = new OutputParser.Stub();
+			final ExecHelper eh = new ExecHelper(p, rebaseToRepoLoc);
+			RepoUtils.modifyFileAppend(rebaseFromChanges, "Change #" + count++);
+			commit(rebaseFromRepoLoc, "remote change");
+			p.reset();
+			eh.run("hg", "--traceback", "pull", rebaseFromRepoLoc.toString());
+			if (eh.getExitValue() != 0) {
+				System.out.println(p.result());
+			}
+			Assert.assertEquals(0, eh.getExitValue());
+			p.reset();
+			eh.run("hg", "--traceback", "--config", "extensions.hgext.rebase=", "rebase");
+			if (eh.getExitValue() != 0) {
+				System.out.println(p.result());
+			}
+			System.out.print("X");
+			Assert.assertEquals(0, eh.getExitValue());
+		} catch (RuntimeException ex) {
+			throw ex;
+		} catch (Exception ex) {
+			ex.printStackTrace();
+			throw new RuntimeException(null, ex); 
+		}
+	}
+	
+	private static void readWithHg4J(final HgLookup hgLookup, final File repoLoc) {
+		try {
+			System.out.print("(");
+			final long start = System.nanoTime();
+			HgRepository hgRepo = hgLookup.detect(repoLoc);
+			final HgRepositoryLock wcLock = hgRepo.getWorkingDirLock();
+			final HgRepositoryLock storeLock = hgRepo.getStoreLock();
+			wcLock.acquire();
+			System.out.print(".");
+			storeLock.acquire();
+			System.out.print(".");
+			try {
+				new HgStatusCommand(hgRepo).execute(new TestStatus.StatusCollector());
+				System.out.printf("%d ms)\n", (System.nanoTime() - start) / 1000000);
+			} finally {
+				storeLock.release();
+				wcLock.release();
+			}
+		} catch (RuntimeException ex) {
+			throw ex;
+		} catch (Exception ex) {
+			ex.printStackTrace();
+			throw new RuntimeException(null, ex); 
+		}
+	}
+	
+	private static void commit(File repoLoc, String message) throws Exception {
+		OutputParser.Stub p = new OutputParser.Stub();
+		final ExecHelper eh = new ExecHelper(p, repoLoc);
+		eh.run("hg", "commit", "--addremove", "-m", "\"" + message + "\"");
+		if (eh.getExitValue() != 0) {
+			System.out.println(p.result());
+		}
+		Assert.assertEquals(0, eh.getExitValue());
+	}
+	
+	private static void sleep(int msBase, int msDelta) {
+		try {
+			Thread.sleep(msBase + Math.round(Math.random() * msDelta));
+		} catch (InterruptedException ex) {
+			// IGNORE
+		}
+	}
 }