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
164 if (res.object.isEdge() || res.object.doNotAttemptDelta()) {
165
166
167
168 keepInWindow();
169 } else {
170
171 if (bytesPerUnit <= (bytesProcessed += next.getWeight())) {
172 int d = (int) (bytesProcessed / bytesPerUnit);
173 monitor.update(d);
174 bytesProcessed -= d * bytesPerUnit;
175 }
176 searchInWindow();
177 }
178 }
179 } finally {
180 if (deflater != null)
181 deflater.end();
182 }
183 }
184
185 private static long estimateSize(ObjectToPack ent) {
186 return DeltaIndex.estimateIndexSize(ent.getWeight());
187 }
188
189 private static long estimateIndexSize(DeltaWindowEntry ent) {
190 if (ent.buffer == null)
191 return estimateSize(ent.object);
192
193 int len = ent.buffer.length;
194 return DeltaIndex.estimateIndexSize(len) - len;
195 }
196
197 private void clear(DeltaWindowEntry ent) {
198 if (ent.index != null)
199 loaded -= ent.index.getIndexSize();
200 else if (ent.buffer != null)
201 loaded -= ent.buffer.length;
202 ent.set(null);
203 }
204
205 private void searchInWindow() throws IOException {
206
207
208 for (DeltaWindowEntry src = res.prev; src != res; src = src.prev) {
209 if (src.empty())
210 break;
211 if (delta(src) )
212 continue;
213 bestBase = null;
214 deltaBuf = null;
215 return;
216 }
217
218
219
220 if (bestBase == null) {
221 keepInWindow();
222 return;
223 }
224
225
226
227 ObjectToPack srcObj = bestBase.object;
228 ObjectToPack resObj = res.object;
229 if (srcObj.isEdge()) {
230
231
232
233
234
235 resObj.setDeltaBase(srcObj.copy());
236 } else {
237
238
239 resObj.setDeltaBase(srcObj);
240 }
241
242 int depth = srcObj.getDeltaDepth() + 1;
243 resObj.setDeltaDepth(depth);
244 resObj.clearReuseAsIs();
245 cacheDelta(srcObj, resObj);
246
247 if (depth < maxDepth) {
248
249
250
251 res.makeNext(bestBase);
252 res = bestBase.next;
253 }
254
255 bestBase = null;
256 deltaBuf = null;
257 }
258
259 private boolean delta(final DeltaWindowEntry src)
260 throws IOException {
261
262 if (src.type() != res.type()) {
263 keepInWindow();
264 return NEXT_RES;
265 }
266
267
268 if (res.size() < src.size() >>> 4)
269 return NEXT_SRC;
270
271 int msz = deltaSizeLimit(src);
272 if (msz <= 8)
273 return NEXT_SRC;
274
275
276 if (res.size() - src.size() > msz)
277 return NEXT_SRC;
278
279 DeltaIndex srcIndex;
280 try {
281 srcIndex = index(src);
282 } catch (LargeObjectException tooBig) {
283
284 return NEXT_SRC;
285 } catch (IOException notAvailable) {
286 if (src.object.isEdge())
287 return NEXT_SRC;
288 throw notAvailable;
289 }
290
291 byte[] resBuf;
292 try {
293 resBuf = buffer(res);
294 } catch (LargeObjectException tooBig) {
295
296 return NEXT_RES;
297 }
298
299 try {
300 OutputStream delta = msz <= (8 << 10)
301 ? new ArrayStream(msz)
302 : new TemporaryBuffer.Heap(msz);
303 if (srcIndex.encode(delta, resBuf, msz))
304 selectDeltaBase(src, delta);
305 } catch (IOException deltaTooBig) {
306
307 }
308 return NEXT_SRC;
309 }
310
311 private void selectDeltaBase(DeltaWindowEntry src, OutputStream delta) {
312 bestBase = src;
313
314 if (delta instanceof ArrayStream) {
315 ArrayStream a = (ArrayStream) delta;
316 deltaBuf = a.buf;
317 deltaLen = a.cnt;
318 } else {
319 TemporaryBuffer.Heap b = (TemporaryBuffer.Heap) delta;
320 deltaBuf = b;
321 deltaLen = (int) b.length();
322 }
323 }
324
325 private int deltaSizeLimit(DeltaWindowEntry src) {
326 if (bestBase == null) {
327
328
329 int n = res.size() >>> 1;
330
331
332
333
334 return n * (maxDepth - src.depth()) / maxDepth;
335 }
336
337
338
339 int d = bestBase.depth();
340 int n = deltaLen;
341
342
343
344
345
346
347 return n * (maxDepth - src.depth()) / (maxDepth - d);
348 }
349
350 private void cacheDelta(ObjectToPack srcObj, ObjectToPack resObj) {
351 if (deltaCache.canCache(deltaLen, srcObj, resObj)) {
352 try {
353 byte[] zbuf = new byte[deflateBound(deltaLen)];
354 ZipStream zs = new ZipStream(deflater(), zbuf);
355 if (deltaBuf instanceof byte[])
356 zs.write((byte[]) deltaBuf, 0, deltaLen);
357 else
358 ((TemporaryBuffer.Heap) deltaBuf).writeTo(zs, null);
359 deltaBuf = null;
360 int len = zs.finish();
361
362 resObj.setCachedDelta(deltaCache.cache(zbuf, len, deltaLen));
363 resObj.setCachedSize(deltaLen);
364 } catch (IOException err) {
365 deltaCache.credit(deltaLen);
366 } catch (OutOfMemoryError err) {
367 deltaCache.credit(deltaLen);
368 }
369 }
370 }
371
372 private static int deflateBound(int insz) {
373 return insz + ((insz + 7) >> 3) + ((insz + 63) >> 6) + 11;
374 }
375
376 private void keepInWindow() {
377 res = res.next;
378 }
379
380 private DeltaIndex index(DeltaWindowEntry ent)
381 throws MissingObjectException, IncorrectObjectTypeException,
382 IOException, LargeObjectException {
383 DeltaIndex idx = ent.index;
384 if (idx == null) {
385 checkLoadable(ent, estimateIndexSize(ent));
386
387 try {
388 idx = new DeltaIndex(buffer(ent));
389 } catch (OutOfMemoryError noMemory) {
390 LargeObjectException.OutOfMemory e;
391 e = new LargeObjectException.OutOfMemory(noMemory);
392 e.setObjectId(ent.object);
393 throw e;
394 }
395 if (maxMemory != 0)
396 loaded += idx.getIndexSize() - idx.getSourceSize();
397 ent.index = idx;
398 }
399 return idx;
400 }
401
402 private byte[] buffer(DeltaWindowEntry ent) throws MissingObjectException,
403 IncorrectObjectTypeException, IOException, LargeObjectException {
404 byte[] buf = ent.buffer;
405 if (buf == null) {
406 checkLoadable(ent, ent.size());
407
408 buf = PackWriter.buffer(config, reader, ent.object);
409 if (maxMemory != 0)
410 loaded += buf.length;
411 ent.buffer = buf;
412 }
413 return buf;
414 }
415
416 private void checkLoadable(DeltaWindowEntry ent, long need) {
417 if (maxMemory == 0)
418 return;
419
420 DeltaWindowEntry n = res.next;
421 for (; maxMemory < loaded + need; n = n.next) {
422 clear(n);
423 if (n == ent)
424 throw new LargeObjectException.ExceedsLimit(
425 maxMemory, loaded + need);
426 }
427 }
428
429 private Deflater deflater() {
430 if (deflater == null)
431 deflater = new Deflater(config.getCompressionLevel());
432 else
433 deflater.reset();
434 return deflater;
435 }
436
437 static final class ZipStream extends OutputStream {
438 private final Deflater deflater;
439
440 private final byte[] zbuf;
441
442 private int outPtr;
443
444 ZipStream(Deflater deflater, byte[] zbuf) {
445 this.deflater = deflater;
446 this.zbuf = zbuf;
447 }
448
449 int finish() throws IOException {
450 deflater.finish();
451 for (;;) {
452 if (outPtr == zbuf.length)
453 throw new EOFException();
454
455 int n = deflater.deflate(zbuf, outPtr, zbuf.length - outPtr);
456 if (n == 0) {
457 if (deflater.finished())
458 return outPtr;
459 throw new IOException();
460 }
461 outPtr += n;
462 }
463 }
464
465 @Override
466 public void write(byte[] b, int off, int len) throws IOException {
467 deflater.setInput(b, off, len);
468 for (;;) {
469 if (outPtr == zbuf.length)
470 throw new EOFException();
471
472 int n = deflater.deflate(zbuf, outPtr, zbuf.length - outPtr);
473 if (n == 0) {
474 if (deflater.needsInput())
475 break;
476 throw new IOException();
477 }
478 outPtr += n;
479 }
480 }
481
482 @Override
483 public void write(int b) throws IOException {
484 throw new UnsupportedOperationException();
485 }
486 }
487
488 static final class ArrayStream extends OutputStream {
489 final byte[] buf;
490 int cnt;
491
492 ArrayStream(int max) {
493 buf = new byte[max];
494 }
495
496 @Override
497 public void write(int b) throws IOException {
498 if (cnt == buf.length)
499 throw new IOException();
500 buf[cnt++] = (byte) b;
501 }
502
503 @Override
504 public void write(byte[] b, int off, int len) throws IOException {
505 if (len > buf.length - cnt)
506 throw new IOException();
507 System.arraycopy(b, off, buf, cnt, len);
508 cnt += len;
509 }
510 }
511 }