|
18 | 18 | */
|
19 | 19 | package org.neo4j.driver.internal.handlers;
|
20 | 20 |
|
21 |
| -import java.util.Collections; |
22 |
| -import java.util.LinkedList; |
23 |
| -import java.util.List; |
| 21 | +import java.util.ArrayDeque; |
24 | 22 | import java.util.Map;
|
25 | 23 | import java.util.Queue;
|
26 | 24 | import java.util.concurrent.CompletableFuture;
|
|
29 | 27 | import org.neo4j.driver.internal.InternalRecord;
|
30 | 28 | import org.neo4j.driver.internal.spi.Connection;
|
31 | 29 | import org.neo4j.driver.internal.spi.ResponseHandler;
|
32 |
| -import org.neo4j.driver.internal.summary.InternalNotification; |
33 |
| -import org.neo4j.driver.internal.summary.InternalPlan; |
34 |
| -import org.neo4j.driver.internal.summary.InternalProfiledPlan; |
35 |
| -import org.neo4j.driver.internal.summary.InternalResultSummary; |
36 |
| -import org.neo4j.driver.internal.summary.InternalServerInfo; |
37 |
| -import org.neo4j.driver.internal.summary.InternalSummaryCounters; |
| 30 | +import org.neo4j.driver.internal.util.MetadataUtil; |
38 | 31 | import org.neo4j.driver.v1.Record;
|
39 | 32 | import org.neo4j.driver.v1.Statement;
|
40 | 33 | import org.neo4j.driver.v1.Value;
|
41 |
| -import org.neo4j.driver.v1.summary.Notification; |
42 |
| -import org.neo4j.driver.v1.summary.Plan; |
43 |
| -import org.neo4j.driver.v1.summary.ProfiledPlan; |
44 | 34 | import org.neo4j.driver.v1.summary.ResultSummary;
|
45 |
| -import org.neo4j.driver.v1.summary.StatementType; |
46 | 35 |
|
47 | 36 | import static java.util.Collections.emptyMap;
|
48 | 37 | import static java.util.Objects.requireNonNull;
|
49 | 38 | import static java.util.concurrent.CompletableFuture.completedFuture;
|
50 | 39 | import static org.neo4j.driver.internal.util.Futures.failedFuture;
|
51 | 40 |
|
52 |
| -// todo: unit tests |
53 | 41 | public abstract class PullAllResponseHandler implements ResponseHandler
|
54 | 42 | {
|
55 |
| - private static final boolean TOUCH_AUTO_READ = false; |
| 43 | + static final int RECORD_BUFFER_LOW_WATERMARK = Integer.getInteger( "recordBufferLowWatermark", 300 ); |
| 44 | + static final int RECORD_BUFFER_HIGH_WATERMARK = Integer.getInteger( "recordBufferHighWatermark", 1000 ); |
56 | 45 |
|
57 | 46 | private final Statement statement;
|
58 | 47 | private final RunResponseHandler runResponseHandler;
|
59 | 48 | protected final Connection connection;
|
60 | 49 |
|
61 |
| - private final Queue<Record> records = new LinkedList<>(); |
| 50 | + private final Queue<Record> records = new ArrayDeque<>(); |
62 | 51 |
|
63 | 52 | private boolean finished;
|
64 | 53 | private Throwable failure;
|
@@ -210,25 +199,31 @@ else if ( finished )
|
210 | 199 | private void queueRecord( Record record )
|
211 | 200 | {
|
212 | 201 | records.add( record );
|
213 |
| - if ( TOUCH_AUTO_READ ) |
| 202 | + |
| 203 | + boolean shouldBufferAllRecords = summaryFuture != null || failureFuture != null; |
| 204 | + // when summary or failure is requested we have to buffer all remaining records and then return summary/failure |
| 205 | + // do not disable auto-read in this case, otherwise records will not be consumed and trailing |
| 206 | + // SUCCESS or FAILURE message will not arrive as well, so callers will get stuck waiting for summary/failure |
| 207 | + if ( !shouldBufferAllRecords && records.size() > RECORD_BUFFER_HIGH_WATERMARK ) |
214 | 208 | {
|
215 |
| - if ( records.size() > 10_000 ) |
216 |
| - { |
217 |
| - connection.disableAutoRead(); |
218 |
| - } |
| 209 | + // more than high watermark records are already queued, tell connection to stop auto-reading from network |
| 210 | + // this is needed to deal with slow consumers, we do not want to buffer all records in memory if they are |
| 211 | + // fetched from network faster than consumed |
| 212 | + connection.disableAutoRead(); |
219 | 213 | }
|
220 | 214 | }
|
221 | 215 |
|
222 | 216 | private Record dequeueRecord()
|
223 | 217 | {
|
224 | 218 | Record record = records.poll();
|
225 |
| - if ( TOUCH_AUTO_READ ) |
| 219 | + |
| 220 | + if ( records.size() < RECORD_BUFFER_LOW_WATERMARK ) |
226 | 221 | {
|
227 |
| - if ( record != null && records.size() < 100 ) |
228 |
| - { |
229 |
| - connection.enableAutoRead(); |
230 |
| - } |
| 222 | + // less than low watermark records are now available in the buffer, tell connection to pre-fetch more |
| 223 | + // and populate queue with new records from network |
| 224 | + connection.enableAutoRead(); |
231 | 225 | }
|
| 226 | + |
232 | 227 | return record;
|
233 | 228 | }
|
234 | 229 |
|
@@ -302,89 +297,7 @@ private boolean completeFailureFuture( Throwable error )
|
302 | 297 |
|
303 | 298 | private ResultSummary extractResultSummary( Map<String,Value> metadata )
|
304 | 299 | {
|
305 |
| - InternalServerInfo serverInfo = new InternalServerInfo( connection.serverAddress(), |
306 |
| - connection.serverVersion() ); |
307 |
| - return new InternalResultSummary( statement, serverInfo, extractStatementType( metadata ), |
308 |
| - extractCounters( metadata ), extractPlan( metadata ), extractProfiledPlan( metadata ), |
309 |
| - extractNotifications( metadata ), runResponseHandler.resultAvailableAfter(), |
310 |
| - extractResultConsumedAfter( metadata ) ); |
311 |
| - } |
312 |
| - |
313 |
| - private static StatementType extractStatementType( Map<String,Value> metadata ) |
314 |
| - { |
315 |
| - Value typeValue = metadata.get( "type" ); |
316 |
| - if ( typeValue != null ) |
317 |
| - { |
318 |
| - return StatementType.fromCode( typeValue.asString() ); |
319 |
| - } |
320 |
| - return null; |
321 |
| - } |
322 |
| - |
323 |
| - private static InternalSummaryCounters extractCounters( Map<String,Value> metadata ) |
324 |
| - { |
325 |
| - Value countersValue = metadata.get( "stats" ); |
326 |
| - if ( countersValue != null ) |
327 |
| - { |
328 |
| - return new InternalSummaryCounters( |
329 |
| - counterValue( countersValue, "nodes-created" ), |
330 |
| - counterValue( countersValue, "nodes-deleted" ), |
331 |
| - counterValue( countersValue, "relationships-created" ), |
332 |
| - counterValue( countersValue, "relationships-deleted" ), |
333 |
| - counterValue( countersValue, "properties-set" ), |
334 |
| - counterValue( countersValue, "labels-added" ), |
335 |
| - counterValue( countersValue, "labels-removed" ), |
336 |
| - counterValue( countersValue, "indexes-added" ), |
337 |
| - counterValue( countersValue, "indexes-removed" ), |
338 |
| - counterValue( countersValue, "constraints-added" ), |
339 |
| - counterValue( countersValue, "constraints-removed" ) |
340 |
| - ); |
341 |
| - } |
342 |
| - return null; |
343 |
| - } |
344 |
| - |
345 |
| - private static int counterValue( Value countersValue, String name ) |
346 |
| - { |
347 |
| - Value value = countersValue.get( name ); |
348 |
| - return value.isNull() ? 0 : value.asInt(); |
349 |
| - } |
350 |
| - |
351 |
| - private static Plan extractPlan( Map<String,Value> metadata ) |
352 |
| - { |
353 |
| - Value planValue = metadata.get( "plan" ); |
354 |
| - if ( planValue != null ) |
355 |
| - { |
356 |
| - return InternalPlan.EXPLAIN_PLAN_FROM_VALUE.apply( planValue ); |
357 |
| - } |
358 |
| - return null; |
359 |
| - } |
360 |
| - |
361 |
| - private static ProfiledPlan extractProfiledPlan( Map<String,Value> metadata ) |
362 |
| - { |
363 |
| - Value profiledPlanValue = metadata.get( "profile" ); |
364 |
| - if ( profiledPlanValue != null ) |
365 |
| - { |
366 |
| - return InternalProfiledPlan.PROFILED_PLAN_FROM_VALUE.apply( profiledPlanValue ); |
367 |
| - } |
368 |
| - return null; |
369 |
| - } |
370 |
| - |
371 |
| - private static List<Notification> extractNotifications( Map<String,Value> metadata ) |
372 |
| - { |
373 |
| - Value notificationsValue = metadata.get( "notifications" ); |
374 |
| - if ( notificationsValue != null ) |
375 |
| - { |
376 |
| - return notificationsValue.asList( InternalNotification.VALUE_TO_NOTIFICATION ); |
377 |
| - } |
378 |
| - return Collections.emptyList(); |
379 |
| - } |
380 |
| - |
381 |
| - private static long extractResultConsumedAfter( Map<String,Value> metadata ) |
382 |
| - { |
383 |
| - Value resultConsumedAfterValue = metadata.get( "result_consumed_after" ); |
384 |
| - if ( resultConsumedAfterValue != null ) |
385 |
| - { |
386 |
| - return resultConsumedAfterValue.asLong(); |
387 |
| - } |
388 |
| - return -1; |
| 300 | + long resultAvailableAfter = runResponseHandler.resultAvailableAfter(); |
| 301 | + return MetadataUtil.extractSummary( statement, connection, resultAvailableAfter, metadata ); |
389 | 302 | }
|
390 | 303 | }
|
0 commit comments