|
1 | 1 | /*
|
2 |
| - * Copyright 2002-2019 the original author or authors. |
| 2 | + * Copyright 2002-2020 the original author or authors. |
3 | 3 | *
|
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | 5 | * you may not use this file except in compliance with the License.
|
@@ -94,20 +94,44 @@ public Flux<String> decode(Publisher<DataBuffer> input, ResolvableType elementTy
|
94 | 94 |
|
95 | 95 | byte[][] delimiterBytes = getDelimiterBytes(mimeType);
|
96 | 96 |
|
97 |
| - // TODO: Drop Consumer and use bufferUntil with Supplier<LimistedDataBufferList> (reactor-core#1925) |
98 |
| - // TODO: Drop doOnDiscard(LimitedDataBufferList.class, ...) (reactor-core#1924) |
99 |
| - LimitedDataBufferConsumer limiter = new LimitedDataBufferConsumer(getMaxInMemorySize()); |
100 |
| - |
101 | 97 | Flux<DataBuffer> inputFlux = Flux.defer(() -> {
|
102 | 98 | DataBufferUtils.Matcher matcher = DataBufferUtils.matcher(delimiterBytes);
|
103 |
| - return Flux.from(input) |
104 |
| - .concatMapIterable(buffer -> endFrameAfterDelimiter(buffer, matcher)) |
105 |
| - .doOnNext(limiter) |
106 |
| - .bufferUntil(buffer -> buffer instanceof EndFrameBuffer) |
107 |
| - .map(buffers -> joinAndStrip(buffers, this.stripDelimiter)) |
108 |
| - .doOnDiscard(LimitedDataBufferList.class, LimitedDataBufferList::releaseAndClear) |
109 |
| - .doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release); |
| 99 | + if (getMaxInMemorySize() != -1) { |
| 100 | + |
| 101 | + // Passing limiter into endFrameAfterDelimiter helps to ensure that in case of one DataBuffer |
| 102 | + // containing multiple lines, the limit is checked and raised immediately without accumulating |
| 103 | + // subsequent lines. This is necessary because concatMapIterable doesn't respect doOnDiscard. |
| 104 | + // When reactor-core#1925 is resolved, we could replace bufferUntil with: |
| 105 | + |
| 106 | + // .windowUntil(buffer -> buffer instanceof EndFrameBuffer) |
| 107 | + // .concatMap(fluxes -> fluxes.collect(() -> new LimitedDataBufferList(getMaxInMemorySize()), LimitedDataBufferList::add)) |
| 108 | + |
| 109 | + LimitedDataBufferList limiter = new LimitedDataBufferList(getMaxInMemorySize()); |
| 110 | + |
| 111 | + return Flux.from(input) |
| 112 | + .concatMapIterable(buffer -> endFrameAfterDelimiter(buffer, matcher, limiter)) |
| 113 | + .bufferUntil(buffer -> buffer instanceof EndFrameBuffer) |
| 114 | + .map(buffers -> joinAndStrip(buffers, this.stripDelimiter)) |
| 115 | + .doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release); |
| 116 | + } |
| 117 | + else { |
110 | 118 |
|
| 119 | + // When the decoder is unlimited (-1), concatMapIterable will cache buffers that may not |
| 120 | + // be released if cancel is signalled before they are turned into String lines |
| 121 | + // (see test maxInMemoryLimitReleasesUnprocessedLinesWhenUnlimited). |
| 122 | + // When reactor-core#1925 is resolved, the workaround can be removed and the entire |
| 123 | + // else clause possibly dropped. |
| 124 | + |
| 125 | + ConcatMapIterableDiscardWorkaroundCache cache = new ConcatMapIterableDiscardWorkaroundCache(); |
| 126 | + |
| 127 | + return Flux.from(input) |
| 128 | + .concatMapIterable(buffer -> cache.addAll(endFrameAfterDelimiter(buffer, matcher, null))) |
| 129 | + .doOnNext(cache) |
| 130 | + .doOnCancel(cache) |
| 131 | + .bufferUntil(buffer -> buffer instanceof EndFrameBuffer) |
| 132 | + .map(buffers -> joinAndStrip(buffers, this.stripDelimiter)) |
| 133 | + .doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release); |
| 134 | + } |
111 | 135 | });
|
112 | 136 |
|
113 | 137 | return super.decode(inputFlux, elementType, mimeType, hints);
|
@@ -152,29 +176,49 @@ private static Charset getCharset(@Nullable MimeType mimeType) {
|
152 | 176 | *
|
153 | 177 | * @param dataBuffer the buffer to find delimiters in
|
154 | 178 | * @param matcher used to find the first delimiters
|
| 179 | + * @param limiter to enforce maxInMemorySize with |
155 | 180 | * @return a flux of buffers, containing {@link EndFrameBuffer} after each delimiter that was
|
156 | 181 | * found in {@code dataBuffer}. Returns Flux, because returning List (w/ flatMapIterable)
|
157 | 182 | * results in memory leaks due to pre-fetching.
|
158 | 183 | */
|
159 |
| - private static List<DataBuffer> endFrameAfterDelimiter(DataBuffer dataBuffer, DataBufferUtils.Matcher matcher) { |
| 184 | + private static List<DataBuffer> endFrameAfterDelimiter( |
| 185 | + DataBuffer dataBuffer, DataBufferUtils.Matcher matcher, @Nullable LimitedDataBufferList limiter) { |
| 186 | + |
160 | 187 | List<DataBuffer> result = new ArrayList<>();
|
161 |
| - do { |
162 |
| - int endIdx = matcher.match(dataBuffer); |
163 |
| - if (endIdx != -1) { |
164 |
| - int readPosition = dataBuffer.readPosition(); |
165 |
| - int length = endIdx - readPosition + 1; |
166 |
| - result.add(dataBuffer.retainedSlice(readPosition, length)); |
167 |
| - result.add(new EndFrameBuffer(matcher.delimiter())); |
168 |
| - dataBuffer.readPosition(endIdx + 1); |
| 188 | + try { |
| 189 | + do { |
| 190 | + int endIdx = matcher.match(dataBuffer); |
| 191 | + if (endIdx != -1) { |
| 192 | + int readPosition = dataBuffer.readPosition(); |
| 193 | + int length = (endIdx - readPosition + 1); |
| 194 | + DataBuffer slice = dataBuffer.retainedSlice(readPosition, length); |
| 195 | + result.add(slice); |
| 196 | + result.add(new EndFrameBuffer(matcher.delimiter())); |
| 197 | + dataBuffer.readPosition(endIdx + 1); |
| 198 | + if (limiter != null) { |
| 199 | + limiter.add(slice); // enforce the limit |
| 200 | + limiter.clear(); |
| 201 | + } |
| 202 | + } |
| 203 | + else { |
| 204 | + result.add(DataBufferUtils.retain(dataBuffer)); |
| 205 | + if (limiter != null) { |
| 206 | + limiter.add(dataBuffer); |
| 207 | + } |
| 208 | + break; |
| 209 | + } |
169 | 210 | }
|
170 |
| - else { |
171 |
| - result.add(DataBufferUtils.retain(dataBuffer)); |
172 |
| - break; |
| 211 | + while (dataBuffer.readableByteCount() > 0); |
| 212 | + } |
| 213 | + catch (DataBufferLimitException ex) { |
| 214 | + if (limiter != null) { |
| 215 | + limiter.releaseAndClear(); |
173 | 216 | }
|
| 217 | + throw ex; |
| 218 | + } |
| 219 | + finally { |
| 220 | + DataBufferUtils.release(dataBuffer); |
174 | 221 | }
|
175 |
| - while (dataBuffer.readableByteCount() > 0); |
176 |
| - |
177 |
| - DataBufferUtils.release(dataBuffer); |
178 | 222 | return result;
|
179 | 223 | }
|
180 | 224 |
|
@@ -288,34 +332,32 @@ public byte[] delimiter() {
|
288 | 332 | }
|
289 | 333 |
|
290 | 334 |
|
291 |
| - /** |
292 |
| - * Temporary measure for reactor-core#1925. |
293 |
| - * Consumer that adds to a {@link LimitedDataBufferList} to enforce limits. |
294 |
| - */ |
295 |
| - private static class LimitedDataBufferConsumer implements Consumer<DataBuffer> { |
| 335 | + private class ConcatMapIterableDiscardWorkaroundCache implements Consumer<DataBuffer>, Runnable { |
296 | 336 |
|
297 |
| - private final LimitedDataBufferList bufferList; |
| 337 | + private final List<DataBuffer> buffers = new ArrayList<>(); |
298 | 338 |
|
299 | 339 |
|
300 |
| - public LimitedDataBufferConsumer(int maxInMemorySize) { |
301 |
| - this.bufferList = new LimitedDataBufferList(maxInMemorySize); |
| 340 | + public List<DataBuffer> addAll(List<DataBuffer> buffersToAdd) { |
| 341 | + this.buffers.addAll(buffersToAdd); |
| 342 | + return buffersToAdd; |
302 | 343 | }
|
303 | 344 |
|
| 345 | + @Override |
| 346 | + public void accept(DataBuffer dataBuffer) { |
| 347 | + this.buffers.remove(dataBuffer); |
| 348 | + } |
304 | 349 |
|
305 | 350 | @Override
|
306 |
| - public void accept(DataBuffer buffer) { |
307 |
| - if (buffer instanceof EndFrameBuffer) { |
308 |
| - this.bufferList.clear(); |
309 |
| - } |
310 |
| - else { |
| 351 | + public void run() { |
| 352 | + this.buffers.forEach(buffer -> { |
311 | 353 | try {
|
312 |
| - this.bufferList.add(buffer); |
313 |
| - } |
314 |
| - catch (DataBufferLimitException ex) { |
315 | 354 | DataBufferUtils.release(buffer);
|
316 |
| - throw ex; |
317 | 355 | }
|
318 |
| - } |
| 356 | + catch (Throwable ex) { |
| 357 | + // Keep going.. |
| 358 | + } |
| 359 | + }); |
319 | 360 | }
|
320 | 361 | }
|
| 362 | + |
321 | 363 | }
|
0 commit comments