Mercurial > hg4j
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 |
