1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.internal.storage.pack;
45
46 import java.io.EOFException;
47 import java.io.IOException;
48 import java.io.OutputStream;
49 import java.util.zip.Deflater;
50
51 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
52 import org.eclipse.jgit.errors.LargeObjectException;
53 import org.eclipse.jgit.errors.MissingObjectException;
54 import org.eclipse.jgit.lib.ObjectReader;
55 import org.eclipse.jgit.lib.ProgressMonitor;
56 import org.eclipse.jgit.storage.pack.PackConfig;
57 import org.eclipse.jgit.util.TemporaryBuffer;
58
59 final class DeltaWindow {
60 private static final boolean NEXT_RES = false;
61 private static final boolean NEXT_SRC = true;
62
63 private final PackConfig config;
64 private final DeltaCache deltaCache;
65 private final ObjectReader reader;
66 private final ProgressMonitor monitor;
67 private final long bytesPerUnit;
68 private long bytesProcessed;
69
70
71 private final long maxMemory;
72
73
74 private final int maxDepth;
75
76 private final ObjectToPack[] toSearch;
77 private int cur;
78 private int end;
79
80
81 private long loaded;
82
83
84
85
86 private DeltaWindowEntry res;
87
88
89 private DeltaWindowEntry bestBase;
90 private int deltaLen;
91 private Object deltaBuf;
92
93
94 private Deflater deflater;
95
96 DeltaWindow(PackConfig pc, DeltaCache dc, ObjectReader or,
97 ProgressMonitor pm, long bpu,
98 ObjectToPack[] in, int beginIndex, int endIndex) {
99 config = pc;
100 deltaCache = dc;
101 reader = or;
102 monitor = pm;
103 bytesPerUnit = bpu;
104 toSearch = in;
105 cur = beginIndex;
106 end = endIndex;
107
108 maxMemory = Math.max(0, config.getDeltaSearchMemoryLimit());
109 maxDepth = config.getMaxDeltaDepth();
110 res = DeltaWindowEntry.createWindow(config.getDeltaSearchWindowSize());
111 }
112
113 synchronized DeltaTask.Slice remaining() {
114 int e = end;
115 int halfRemaining = (e - cur) >>> 1;
116 if (0 == halfRemaining)
117 return null;
118
119 int split = e - halfRemaining;
120 int h = toSearch[split].getPathHash();
121
122
123 for (int n = split + 1; n < e; n++) {
124 if (h != toSearch[n].getPathHash())
125 return new DeltaTask.Slice(n, e);
126 }
127
128 if (h != toSearch[cur].getPathHash()) {
129
130
131 for (int p = split - 1; cur < p; p--) {
132 if (h != toSearch[p].getPathHash())
133 return new DeltaTask.Slice(p + 1, e);
134 }
135 }
136 return null;
137 }
138
139 synchronized boolean tryStealWork(DeltaTask.Slice s) {
140 if (s.beginIndex <= cur || end <= s.beginIndex)
141 return false;
142 end = s.beginIndex;
143 return true;
144 }
145
146 void search() throws IOException {
147 try {
148 for (;;) {
149 ObjectToPack next;
150 synchronized (this) {
151 if (end <= cur)
152 break;
153 next = toSearch[cur++];
154 }
155 if (maxMemory != 0) {
156 clear(res);
157 final long need = estimateSize(next);
158 DeltaWindowEntry n = res.next;
159 for (; maxMemory < loaded + need && n != res; n = n.next)
160 clear(n);
161 }
162 res.set(next);
163 clearWindowOnTypeSwitch();
164
165 if (res.object.isEdge() || res.object.doNotAttemptDelta()) {
166
167
168
169 keepInWindow();
170 } else {
171
172 if (bytesPerUnit <= (bytesProcessed += next.getWeight())) {
173 int d = (int) (bytesProcessed / bytesPerUnit);
174 monitor.update(d);
175 bytesProcessed -= d * bytesPerUnit;
176 }
177 searchInWindow();
178 }
179 }
180 } finally {
181 if (deflater != null)
182 deflater.end();
183 }
184 }
185
186 private static long estimateSize(ObjectToPack ent) {
187 return DeltaIndex.estimateIndexSize(ent.getWeight());
188 }
189
190 private static long estimateIndexSize(DeltaWindowEntry ent) {
191 if (ent.buffer == null)
192 return estimateSize(ent.object);
193
194 int len = ent.buffer.length;
195 return DeltaIndex.estimateIndexSize(len) - len;
196 }
197
198 private void clearWindowOnTypeSwitch() {
199 DeltaWindowEntry p = res.prev;
200 if (!p.empty() && res.type() != p.type()) {
201 for (; p != res; p = p.prev) {
202 clear(p);
203 }
204 }
205 }
206
207 private void clear(DeltaWindowEntry ent) {
208 if (ent.index != null)
209 loaded -= ent.index.getIndexSize();
210 else if (ent.buffer != null)
211 loaded -= ent.buffer.length;
212 ent.set(null);
213 }
214
215 private void searchInWindow() throws IOException {
216
217
218 for (DeltaWindowEntry src = res.prev; src != res; src = src.prev) {
219 if (src.empty())
220 break;
221 if (delta(src) )
222 continue;
223 bestBase = null;
224 deltaBuf = null;
225 return;
226 }
227
228
229
230 if (bestBase == null) {
231 keepInWindow();
232 return;
233 }
234
235
236
237 ObjectToPack srcObj = bestBase.object;
238 ObjectToPack resObj = res.object;
239 if (srcObj.isEdge()) {
240
241
242
243
244
245 resObj.setDeltaBase(srcObj.copy());
246 } else {
247
248
249 resObj.setDeltaBase(srcObj);
250 }
251
252 int depth = srcObj.getDeltaDepth() + 1;
253 resObj.setDeltaDepth(depth);
254 resObj.clearReuseAsIs();
255 cacheDelta(srcObj, resObj);
256
257 if (depth < maxDepth) {
258
259
260
261 res.makeNext(bestBase);
262 res = bestBase.next;
263 }
264
265 bestBase = null;
266 deltaBuf = null;
267 }
268
269 private boolean delta(DeltaWindowEntry src)
270 throws IOException {
271
272 if (res.size() < src.size() >>> 4)
273 return NEXT_SRC;
274
275 int msz = deltaSizeLimit(src);
276 if (msz <= 8)
277 return NEXT_SRC;
278
279
280 if (res.size() - src.size() > msz)
281 return NEXT_SRC;
282
283 DeltaIndex srcIndex;
284 try {
285 srcIndex = index(src);
286 } catch (LargeObjectException tooBig) {
287
288 return NEXT_SRC;
289 } catch (IOException notAvailable) {
290 if (src.object.isEdge())
291 return NEXT_SRC;
292 throw notAvailable;
293 }
294
295 byte[] resBuf;
296 try {
297 resBuf = buffer(res);
298 } catch (LargeObjectException tooBig) {
299
300 return NEXT_RES;
301 }
302
303 try {
304 OutputStream delta = msz <= (8 << 10)
305 ? new ArrayStream(msz)
306 : new TemporaryBuffer.Heap(msz);
307 if (srcIndex.encode(delta, resBuf, msz))
308 selectDeltaBase(src, delta);
309 } catch (IOException deltaTooBig) {
310
311 }
312 return NEXT_SRC;
313 }
314
315 private void selectDeltaBase(DeltaWindowEntry src, OutputStream delta) {
316 bestBase = src;
317
318 if (delta instanceof ArrayStream) {
319 ArrayStream a = (ArrayStream) delta;
320 deltaBuf = a.buf;
321 deltaLen = a.cnt;
322 } else {
323 TemporaryBuffer.Heap b = (TemporaryBuffer.Heap) delta;
324 deltaBuf = b;
325 deltaLen = (int) b.length();
326 }
327 }
328
329 private int deltaSizeLimit(DeltaWindowEntry src) {
330 if (bestBase == null) {
331
332
333 int n = res.size() >>> 1;
334
335
336
337
338 return n * (maxDepth - src.depth()) / maxDepth;
339 }
340
341
342
343 int d = bestBase.depth();
344 int n = deltaLen;
345
346
347
348
349
350
351 return n * (maxDepth - src.depth()) / (maxDepth - d);
352 }
353
354 private void cacheDelta(ObjectToPack./../../../../org/eclipse/jgit/internal/storage/pack/ObjectToPack.html#ObjectToPack">ObjectToPack srcObj, ObjectToPack resObj) {
355 if (deltaCache.canCache(deltaLen, srcObj, resObj)) {
356 try {
357 byte[] zbuf = new byte[deflateBound(deltaLen)];
358 ZipStream zs = new ZipStream(deflater(), zbuf);
359 if (deltaBuf instanceof byte[])
360 zs.write((byte[]) deltaBuf, 0, deltaLen);
361 else
362 ((TemporaryBuffer.Heap) deltaBuf).writeTo(zs, null);
363 deltaBuf = null;
364 int len = zs.finish();
365
366 resObj.setCachedDelta(deltaCache.cache(zbuf, len, deltaLen));
367 resObj.setCachedSize(deltaLen);
368 } catch (IOException | OutOfMemoryError err) {
369 deltaCache.credit(deltaLen);
370 }
371 }
372 }
373
374 private static int deflateBound(int insz) {
375 return insz + ((insz + 7) >> 3) + ((insz + 63) >> 6) + 11;
376 }
377
378 private void keepInWindow() {
379 res = res.next;
380 }
381
382 private DeltaIndex index(DeltaWindowEntry ent)
383 throws MissingObjectException, IncorrectObjectTypeException,
384 IOException, LargeObjectException {
385 DeltaIndex idx = ent.index;
386 if (idx == null) {
387 checkLoadable(ent, estimateIndexSize(ent));
388
389 try {
390 idx = new DeltaIndex(buffer(ent));
391 } catch (OutOfMemoryError noMemory) {
392 LargeObjectException.OutOfMemory e;
393 e = new LargeObjectException.OutOfMemory(noMemory);
394 e.setObjectId(ent.object);
395 throw e;
396 }
397 if (maxMemory != 0)
398 loaded += idx.getIndexSize() - idx.getSourceSize();
399 ent.index = idx;
400 }
401 return idx;
402 }
403
404 private byte[] buffer(DeltaWindowEntry ent) throws MissingObjectException,
405 IncorrectObjectTypeException, IOException, LargeObjectException {
406 byte[] buf = ent.buffer;
407 if (buf == null) {
408 checkLoadable(ent, ent.size());
409
410 buf = PackWriter.buffer(config, reader, ent.object);
411 if (maxMemory != 0)
412 loaded += buf.length;
413 ent.buffer = buf;
414 }
415 return buf;
416 }
417
418 private void checkLoadable(DeltaWindowEntry ent, long need) {
419 if (maxMemory == 0)
420 return;
421
422 DeltaWindowEntry n = res.next;
423 for (; maxMemory < loaded + need; n = n.next) {
424 clear(n);
425 if (n == ent)
426 throw new LargeObjectException.ExceedsLimit(
427 maxMemory, loaded + need);
428 }
429 }
430
431 private Deflater deflater() {
432 if (deflater == null)
433 deflater = new Deflater(config.getCompressionLevel());
434 else
435 deflater.reset();
436 return deflater;
437 }
438
439 static final class ZipStream extends OutputStream {
440 private final Deflater deflater;
441
442 private final byte[] zbuf;
443
444 private int outPtr;
445
446 ZipStream(Deflater deflater, byte[] zbuf) {
447 this.deflater = deflater;
448 this.zbuf = zbuf;
449 }
450
451 int finish() throws IOException {
452 deflater.finish();
453 for (;;) {
454 if (outPtr == zbuf.length)
455 throw new EOFException();
456
457 int n = deflater.deflate(zbuf, outPtr, zbuf.length - outPtr);
458 if (n == 0) {
459 if (deflater.finished())
460 return outPtr;
461 throw new IOException();
462 }
463 outPtr += n;
464 }
465 }
466
467 @Override
468 public void write(byte[] b, int off, int len) throws IOException {
469 deflater.setInput(b, off, len);
470 for (;;) {
471 if (outPtr == zbuf.length)
472 throw new EOFException();
473
474 int n = deflater.deflate(zbuf, outPtr, zbuf.length - outPtr);
475 if (n == 0) {
476 if (deflater.needsInput())
477 break;
478 throw new IOException();
479 }
480 outPtr += n;
481 }
482 }
483
484 @Override
485 public void write(int b) throws IOException {
486 throw new UnsupportedOperationException();
487 }
488 }
489
490 static final class ArrayStream extends OutputStream {
491 final byte[] buf;
492 int cnt;
493
494 ArrayStream(int max) {
495 buf = new byte[max];
496 }
497
498 @Override
499 public void write(int b) throws IOException {
500 if (cnt == buf.length)
501 throw new IOException();
502 buf[cnt++] = (byte) b;
503 }
504
505 @Override
506 public void write(byte[] b, int off, int len) throws IOException {
507 if (len > buf.length - cnt)
508 throw new IOException();
509 System.arraycopy(b, off, buf, cnt, len);
510 cnt += len;
511 }
512 }
513 }