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