comparison src/org/tmatesoft/hg/internal/RevlogStream.java @ 593:9619301a7bb9

Share last revision read between #iterate() invocations, to save revision rebuild efforts when few subsequent revisions are read
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 02 May 2013 16:51:02 +0200
parents b47ef0d2777b
children cc7b0c4dc993
comparison
equal deleted inserted replaced
592:b12cc3d64a35 593:9619301a7bb9
20 import static org.tmatesoft.hg.repo.HgRepository.TIP; 20 import static org.tmatesoft.hg.repo.HgRepository.TIP;
21 import static org.tmatesoft.hg.internal.Internals.REVLOGV1_RECORD_SIZE; 21 import static org.tmatesoft.hg.internal.Internals.REVLOGV1_RECORD_SIZE;
22 22
23 import java.io.File; 23 import java.io.File;
24 import java.io.IOException; 24 import java.io.IOException;
25 import java.lang.ref.Reference;
26 import java.lang.ref.ReferenceQueue;
27 import java.lang.ref.SoftReference;
25 import java.util.zip.Inflater; 28 import java.util.zip.Inflater;
26 29
27 import org.tmatesoft.hg.core.Nodeid; 30 import org.tmatesoft.hg.core.Nodeid;
28 import org.tmatesoft.hg.repo.HgInternals; 31 import org.tmatesoft.hg.repo.HgInternals;
29 import org.tmatesoft.hg.repo.HgInvalidControlFileException; 32 import org.tmatesoft.hg.repo.HgInvalidControlFileException;
57 private int[] baseRevisions; 60 private int[] baseRevisions;
58 private boolean inline = false; 61 private boolean inline = false;
59 private final File indexFile; 62 private final File indexFile;
60 private File dataFile; 63 private File dataFile;
61 private final DataAccessProvider dataAccess; 64 private final DataAccessProvider dataAccess;
65 // keeps last complete revision we've read. Note, this cached revision doesn't help
66 // for subsequent #iterate() calls with the same revision (Inspector needs more data than
67 // we currently cache here, perhaps, we shall cache everything it wants to cover same
68 // revision case as well). Now this helps when second #iterate() call is for a revision greater
69 // than one from the first call, and both revisions got same base rev. It's often the case when
70 // parents/children are analyzed.
71 private SoftReference<CachedRevision> lastRevisionRead;
72 private final ReferenceQueue<CachedRevision> lastRevisionQueue = new ReferenceQueue<CachedRevision>();
62 73
63 // if we need anything else from HgRepo, might replace DAP parameter with HgRepo and query it for DAP. 74 // if we need anything else from HgRepo, might replace DAP parameter with HgRepo and query it for DAP.
64 public RevlogStream(DataAccessProvider dap, File indexFile) { 75 public RevlogStream(DataAccessProvider dap, File indexFile) {
65 this.dataAccess = dap; 76 this.dataAccess = dap;
66 this.indexFile = indexFile; 77 this.indexFile = indexFile;
258 } finally { 269 } finally {
259 daIndex.done(); 270 daIndex.done();
260 } 271 }
261 } 272 }
262 273
263
264
265 // should be possible to use TIP, ALL, or -1, -2, -n notation of Hg 274 // should be possible to use TIP, ALL, or -1, -2, -n notation of Hg
266 // ? boolean needsNodeid 275 // ? boolean needsNodeid
267 public void iterate(int start, int end, boolean needData, Inspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException { 276 public void iterate(int start, int end, boolean needData, Inspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException {
268 initOutline(); 277 initOutline();
269 final int indexSize = revisionCount(); 278 final int indexSize = revisionCount();
279 HgInternals.checkRevlogRange(start, end, indexSize-1); 288 HgInternals.checkRevlogRange(start, end, indexSize-1);
280 // XXX may cache [start .. end] from index with a single read (pre-read) 289 // XXX may cache [start .. end] from index with a single read (pre-read)
281 290
282 ReaderN1 r = new ReaderN1(needData, inspector, dataAccess.shallMergePatches()); 291 ReaderN1 r = new ReaderN1(needData, inspector, dataAccess.shallMergePatches());
283 try { 292 try {
284 r.start(end - start + 1); 293 r.start(end - start + 1, getLastRevisionRead());
285 r.range(start, end); 294 r.range(start, end);
286 } catch (IOException ex) { 295 } catch (IOException ex) {
287 throw new HgInvalidControlFileException(String.format("Failed reading [%d..%d]", start, end), ex, indexFile); 296 throw new HgInvalidControlFileException(String.format("Failed reading [%d..%d]", start, end), ex, indexFile);
288 } catch (HgInvalidControlFileException ex) { 297 } catch (HgInvalidControlFileException ex) {
289 throw ex; 298 throw ex;
290 } finally { 299 } finally {
291 r.finish(); 300 CachedRevision cr = r.finish();
301 setLastRevisionRead(cr);
292 } 302 }
293 } 303 }
294 304
295 /** 305 /**
296 * Effective alternative to {@link #iterate(int, int, boolean, Inspector) batch read}, when only few selected 306 * Effective alternative to {@link #iterate(int, int, boolean, Inspector) batch read}, when only few selected
311 throw new HgInvalidRevisionException(String.format("Can't iterate [%d, %d] in range [0..%d]", sortedRevisions[0], sortedRevisions[sortedRevisions.length - 1], indexSize), null, sortedRevisions[sortedRevisions.length - 1]); 321 throw new HgInvalidRevisionException(String.format("Can't iterate [%d, %d] in range [0..%d]", sortedRevisions[0], sortedRevisions[sortedRevisions.length - 1], indexSize), null, sortedRevisions[sortedRevisions.length - 1]);
312 } 322 }
313 323
314 ReaderN1 r = new ReaderN1(needData, inspector, dataAccess.shallMergePatches()); 324 ReaderN1 r = new ReaderN1(needData, inspector, dataAccess.shallMergePatches());
315 try { 325 try {
316 r.start(sortedRevisions.length); 326 r.start(sortedRevisions.length, lastRevisionRead == null ? null : lastRevisionRead.get());
317 for (int i = 0; i < sortedRevisions.length; ) { 327 for (int i = 0; i < sortedRevisions.length; ) {
318 int x = i; 328 int x = i;
319 i++; 329 i++;
320 while (i < sortedRevisions.length) { 330 while (i < sortedRevisions.length) {
321 if (sortedRevisions[i] == sortedRevisions[i-1] + 1) { 331 if (sortedRevisions[i] == sortedRevisions[i-1] + 1) {
334 throw new HgInvalidControlFileException(String.format("Failed reading %d revisions in [%d; %d]",c, sortedRevisions[0], sortedRevisions[c-1]), ex, indexFile); 344 throw new HgInvalidControlFileException(String.format("Failed reading %d revisions in [%d; %d]",c, sortedRevisions[0], sortedRevisions[c-1]), ex, indexFile);
335 } catch (HgInvalidControlFileException ex) { 345 } catch (HgInvalidControlFileException ex) {
336 // TODO post-1.0 fill HgRuntimeException with appropriate file (either index or data, depending on error source) 346 // TODO post-1.0 fill HgRuntimeException with appropriate file (either index or data, depending on error source)
337 throw ex; 347 throw ex;
338 } finally { 348 } finally {
339 r.finish(); 349 CachedRevision cr = r.finish();
350 setLastRevisionRead(cr);
340 } 351 }
341 } 352 }
342 353
343 void revisionAdded(int revisionIndex, Nodeid revision, int baseRevisionIndex, long revisionOffset) throws HgInvalidControlFileException { 354 void revisionAdded(int revisionIndex, Nodeid revision, int baseRevisionIndex, long revisionOffset) throws HgInvalidControlFileException {
344 if (!outlineCached()) { 355 if (!outlineCached()) {
469 } finally { 480 } finally {
470 da.done(); 481 da.done();
471 } 482 }
472 } 483 }
473 484
485 private CachedRevision getLastRevisionRead() {
486 return lastRevisionRead == null ? null : lastRevisionRead.get();
487 }
488
489 private void setLastRevisionRead(CachedRevision cr) {
490 // done() for lastRevisionRead.userData has been called by ReaderN1 once
491 // it noticed unsuitable DataAccess.
492 // Now, done() for any CachedRevision cleared by GC:
493 for (Reference<? extends CachedRevision> r; (r = lastRevisionQueue.poll()) != null;) {
494 CachedRevision toClean = r.get();
495 if (toClean != null && toClean.userData != null) {
496 toClean.userData.done();
497 }
498 }
499 if (cr != null) {
500 lastRevisionRead = new SoftReference<CachedRevision>(cr, lastRevisionQueue);
501 } else {
502 lastRevisionRead = null;
503 }
504 }
505
506 final static class CachedRevision {
507 final int revision;
508 final DataAccess userData;
509
510 public CachedRevision(int lastRevisionRead, DataAccess lastUserData) {
511 revision = lastRevisionRead;
512 userData = lastUserData;
513 }
514 }
515
474 /** 516 /**
475 * operation with single file open/close and multiple diverse reads. 517 * operation with single file open/close and multiple diverse reads.
476 * XXX initOutline might need similar extraction to keep N1 format knowledge 518 * XXX initOutline might need similar extraction to keep N1 format knowledge
477 */ 519 */
478 final class ReaderN1 { 520 final class ReaderN1 {
498 private int actualLen; 540 private int actualLen;
499 private int baseRevision; 541 private int baseRevision;
500 private int linkRevision; 542 private int linkRevision;
501 private int parent1Revision; 543 private int parent1Revision;
502 private int parent2Revision; 544 private int parent2Revision;
503 // next are to track two major bottlenecks - patch application and actual time spent in inspector
504 // private long applyTime, inspectorTime; // TIMING
505 545
506 public ReaderN1(boolean dataRequested, Inspector insp, boolean usePatchMerge) { 546 public ReaderN1(boolean dataRequested, Inspector insp, boolean usePatchMerge) {
507 assert insp != null; 547 assert insp != null;
508 needData = dataRequested; 548 needData = dataRequested;
509 inspector = insp; 549 inspector = insp;
510 mergePatches = usePatchMerge; 550 mergePatches = usePatchMerge;
511 } 551 }
512 552
513 public void start(int totalWork) { 553 public void start(int totalWork, CachedRevision cachedRevision) {
514 daIndex = getIndexStream(); 554 daIndex = getIndexStream();
515 if (needData && !inline) { 555 if (needData && !inline) {
516 daData = getDataStream(); 556 daData = getDataStream();
517 } 557 }
518 lifecycleListener = Adaptable.Factory.getAdapter(inspector, Lifecycle.class, null); 558 lifecycleListener = Adaptable.Factory.getAdapter(inspector, Lifecycle.class, null);
519 if (lifecycleListener != null) { 559 if (lifecycleListener != null) {
520 cb = new Lifecycle.BasicCallback(); 560 cb = new Lifecycle.BasicCallback();
521 lifecycleListener.start(totalWork, cb, cb); 561 lifecycleListener.start(totalWork, cb, cb);
522 } 562 }
523 // applyTime = inspectorTime = 0; // TIMING 563 if (needData && cachedRevision != null) {
564 lastUserData = cachedRevision.userData;
565 lastRevisionRead = cachedRevision.revision;
566 assert lastUserData != null;
567 }
524 } 568 }
525 569
526 // invoked only once per instance 570 // invoked only once per instance
527 public void finish() { 571 public CachedRevision finish() {
572 CachedRevision rv = null;
528 if (lastUserData != null) { 573 if (lastUserData != null) {
529 lastUserData.done(); 574 rv = new CachedRevision(lastRevisionRead, lastUserData);
530 lastUserData = null; 575 lastUserData = null;
531 } 576 }
532 if (lifecycleListener != null) { 577 if (lifecycleListener != null) {
533 lifecycleListener.finish(cb); 578 lifecycleListener.finish(cb);
534 lifecycleListener = null; 579 lifecycleListener = null;
538 daIndex.done(); 583 daIndex.done();
539 if (daData != null) { 584 if (daData != null) {
540 daData.done(); 585 daData.done();
541 daData = null; 586 daData = null;
542 } 587 }
543 // System.out.printf("applyTime:%d ms, inspectorTime: %d ms\n", applyTime, inspectorTime); // TIMING 588 return rv;
544 } 589 }
545 590
546 private void readHeaderRecord(int i) throws IOException { 591 private void readHeaderRecord(int i) throws IOException {
547 if (inline && needData) { 592 if (inline && needData) {
548 // inspector reading data (though FilterDataAccess) may have affected index position 593 // inspector reading data (though FilterDataAccess) may have affected index position