/** * This method is thread safe post threshold initialization (specifically, * {@link MemoryMonitor} is synchronized). */ public void setResponseAt(int index, Response response) { if (memoryMonitor != null) { memoryMonitor.apply(response); } responses[index] = response; }
public int getResponseDataSizeB() { if (memoryMonitor != null) { return memoryMonitor.getDataSizeB(); } else { return 0; } } }
/** * Experimental! Use carefully. */ public void deductMemory(JsonNode json) { memoryMonitor.deduct(json); }
/** * Threshold is expressed in bytes. This is just an approximation, see @{link {@link JsonUtils#size(JsonNode)} for details. * * @param maxResultSetSizeB error when this threshold is breached * @param warnResultSetSizeB log a warning when this threshold is breached * @param forRequest request which resulted in this response, for logging purposes * @param initialDataSizeB initial size in memory (memory occupied by prior operations) */ public void setQueuedHooksSizeThresholds(int maxQueuedHooksSizeB, int warnQueuedHooksSizeB, final QueryExpression query, int initialDataSizeB) { this.monitor = new MemoryMonitor<>((node) -> JsonUtils.size(node), initialDataSizeB); this.monitor.registerMonitor(new ThresholdMonitor<>(warnQueuedHooksSizeB, (current, threshold, node) -> { LOGGER.warn("crud:ResultSizeIsLarge: query={}, queuedHooksSizeB={} threshold={}", query, current, threshold); })); this.monitor.registerMonitor(new ThresholdMonitor<>(maxQueuedHooksSizeB, (current, threshold, node) -> { throw Error.get(Response.ERR_RESULT_SIZE_TOO_LARGE, current+"B > "+threshold+"B (during hook processing)"); })); }
/** * Result set size threshold is expressed in bytes. This is just an approximation, see @{link {@link JsonUtils#size(JsonNode)} for details. * * @param maxResultSetSizeB error when this threshold is breached * @param warnResultSetSizeB log a warning when this threshold is breached * @param forRequest request which resulted in this response, for logging purposes */ private void registerMemoryMonitors(int maxResultSetSizeB, int warnResultSetSizeB, final Request forRequest) { // Order is significant – warn first for request in log output. memoryMonitor.registerMonitor(new MemoryMonitor.ThresholdMonitor<>(warnResultSetSizeB, (current, threshold, doc) -> LOGGER.warn("crud:ExecutionContextIsLarge: request={}, executionDataSizeB={} threshold={}", forRequest, current, threshold))); memoryMonitor.registerMonitor(new MemoryMonitor.ThresholdMonitor<>(maxResultSetSizeB, (current, threshold, doc) -> { throw Error.get( CrudConstants.ERR_EXECUTION_CONTEXT_TOO_LARGE, current + "B > " + threshold + "B"); })); }
/** * Add this value's size to the total, unless this object (by reference) has already been * counted. * * @param value * @return */ public synchronized T apply(final T value) { if (counted.add(value)) { dataSizeB += sizeCalculator.size(value); checkThresholdMonitors(value); } return value; }
/** * Threshold is expressed in bytes. This is just an approximation, see @{link {@link JsonUtils#size(JsonNode)} for details. * * @param maxResultSetSizeB error when this threshold is breached * @param warnResultSetSizeB log a warning when this threshold is breached * @param forRequest request which resulted in this response, for logging purposes * @param initialDataSizeB initial size in memory (memory occupied by prior operations) */ public void setQueuedHooksSizeThresholds(int maxQueuedHooksSizeB, int warnQueuedHooksSizeB, final QueryExpression query, int initialDataSizeB) { this.monitor = new MemoryMonitor<>((node) -> JsonUtils.size(node), initialDataSizeB); this.monitor.registerMonitor(new ThresholdMonitor<>(warnQueuedHooksSizeB, (current, threshold, node) -> { LOGGER.warn("crud:ResultSizeIsLarge: query={}, queuedHooksSizeB={} threshold={}", query, current, threshold); })); this.monitor.registerMonitor(new ThresholdMonitor<>(maxQueuedHooksSizeB, (current, threshold, node) -> { throw Error.get(Response.ERR_RESULT_SIZE_TOO_LARGE, current+"B > "+threshold+"B (during hook processing)"); })); }
/** * Result set size threshold is expressed in bytes. This is just an approximation, see @{link {@link JsonUtils#size(JsonNode)} for details. * * @param maxResultSetSizeB error when this threshold is breached * @param warnResultSetSizeB log a warning when this threshold is breached * @param forRequest request which resulted in this response, for logging purposes */ private void registerMemoryMonitors(int maxResultSetSizeB, int warnResultSetSizeB, final Request forRequest) { // Order is significant – warn first for request in log output. memoryMonitor.registerMonitor(new MemoryMonitor.ThresholdMonitor<>(warnResultSetSizeB, (current, threshold, doc) -> LOGGER.warn("crud:ExecutionContextIsLarge: request={}, executionDataSizeB={} threshold={}", forRequest, current, threshold))); memoryMonitor.registerMonitor(new MemoryMonitor.ThresholdMonitor<>(maxResultSetSizeB, (current, threshold, doc) -> { throw Error.get( CrudConstants.ERR_EXECUTION_CONTEXT_TOO_LARGE, current + "B > " + threshold + "B"); })); }
/** * Add this value's size to the total, unless this object (by reference) has already been * counted. * * @param value * @return */ public synchronized T apply(final T value) { if (counted.add(value)) { dataSizeB += sizeCalculator.size(value); checkThresholdMonitors(value); } return value; }
/** * Bulk result set size threshold is expressed in bytes. This is just an approximation, see @{link {@link JsonUtils#size(JsonNode)} for details. * * @param maxResultSetSizeB error when this threshold is breached * @param warnResultSetSizeB log a warning when this threshold is breached * @param forRequest request which resulted in this response, for logging purposes */ public void setResultSizeThresholds(int maxResultSetSizeB, int warnResultSetSizeB, BulkRequest forRequest) { this.memoryMonitor = new MemoryMonitor<>((response) -> response.getResponseDataSizeB()); memoryMonitor.registerMonitor(new ThresholdMonitor<Response>(warnResultSetSizeB, (current, threshold, response) -> { LOGGER.warn("crud:ResultSizeIsLarge: request={}, responseDataSizeB={} threshold={}", forRequest, current, threshold); })); memoryMonitor.registerMonitor(new ThresholdMonitor<Response>(maxResultSetSizeB, (current, threshold, response) -> { // remove data response.setEntityData(JsonNodeFactory.instance.arrayNode()); response.getErrors().add(Error.get(Response.ERR_RESULT_SIZE_TOO_LARGE, current+"B > "+threshold+"B")); })); }
private void enforceMemoryLimit(DocCtx doc) { if (memoryMonitor != null) { // if memory threshold is exceeded, this will throw an Error memoryMonitor.apply(doc); // an Error means *inconsistent update operation*: // some batches will be updated, some don't // no hooks will fire for updated batches // counts sent to client will be set to zero // TODO: I perceive this as a problem with updates and hooks impl in general // we need to run hooks per batch (see https://github.com/lightblue-platform/lightblue-mongo/issues/378) } }
public int getResponseDataSizeB() { if (memoryMonitor != null) { return memoryMonitor.getDataSizeB(); } else { return 0; } } }
/** * Experimental! Use carefully. */ public void deductMemory(JsonNode json) { memoryMonitor.deduct(json); }
/** * Bulk result set size threshold is expressed in bytes. This is just an approximation, see @{link {@link JsonUtils#size(JsonNode)} for details. * * @param maxResultSetSizeB error when this threshold is breached * @param warnResultSetSizeB log a warning when this threshold is breached * @param forRequest request which resulted in this response, for logging purposes */ public void setResultSizeThresholds(int maxResultSetSizeB, int warnResultSetSizeB, BulkRequest forRequest) { this.memoryMonitor = new MemoryMonitor<>((response) -> response.getResponseDataSizeB()); memoryMonitor.registerMonitor(new ThresholdMonitor<Response>(warnResultSetSizeB, (current, threshold, response) -> { LOGGER.warn("crud:ResultSizeIsLarge: request={}, responseDataSizeB={} threshold={}", forRequest, current, threshold); })); memoryMonitor.registerMonitor(new ThresholdMonitor<Response>(maxResultSetSizeB, (current, threshold, response) -> { // remove data response.setEntityData(JsonNodeFactory.instance.arrayNode()); response.getErrors().add(Error.get(Response.ERR_RESULT_SIZE_TOO_LARGE, current+"B > "+threshold+"B")); })); }
/** * This method is thread safe post threshold initialization (specifically, * {@link MemoryMonitor} is synchronized). */ public void setResponseAt(int index, Response response) { if (memoryMonitor != null) { memoryMonitor.apply(response); } responses[index] = response; }
public int getDataSizeB() { if (memoryMonitor != null) { return memoryMonitor.getDataSizeB(); } else { return 0; } } }
/** * Result set size threshold is expressed in bytes. This is just an approximation, see @{link {@link JsonUtils#size(JsonNode)} for details. * * @param maxResultSetSizeB error when this threshold is breached * @param warnResultSetSizeB log a warning when this threshold is breached * @param forRequest request which resulted in this response, for logging purposes */ public void setResultSizeThresholds(int maxResultSetSizeB, int warnResultSetSizeB, final Request forRequest) { this.memoryMonitor = new MemoryMonitor<>((jsonNode) -> JsonUtils.size(jsonNode)); // Order is significant – warn first for request in log output. memoryMonitor.registerMonitor(new ThresholdMonitor<JsonNode>(warnResultSetSizeB, (current, threshold, doc) -> { LOGGER.warn("crud:ResultSizeIsLarge: request={}, responseDataSizeB={} threshold={}", forRequest, current, threshold); })); memoryMonitor.registerMonitor(new ThresholdMonitor<JsonNode>(maxResultSetSizeB, (current, threshold, doc) -> { // empty data // returning incomplete result set could be useful, but also confusing and thus dangerous // the counts - matchCount, modifiedCount - are unmodified setEntityData(JsonNodeFactory.instance.arrayNode()); setStatus(OperationStatus.ERROR); throw Error.get(ERR_RESULT_SIZE_TOO_LARGE, current + "B > " + threshold + "B"); })); }
/** * Monitor the memory usage for JSON objects part of a response that are might not be garbage * collected at all or garbage collected timely enough before they are streamed to clients. Too * much memory may warn or cause this method to throw an {@link Error} based on configured * thresholds. * * <p>Objects which are known to be short lived should not be monitored, such as objects * passing through a {@link java.util.stream.Stream} or {@link java.util.Iterator} that are not * aggregated in a {@link java.util.Collection} or array, etc. * * <p>You <em>may</em> want to monitor objects that are aggregated, depending on how large the * aggregation may be and for how long the objects may be referenced. * * @throws Error {@link CrudConstants#ERR_EXECUTION_CONTEXT_TOO_LARGE} If max result size * threshold is set and triggered. * @see #registerMemoryMonitors(int, int, Request) */ public void monitorMemory(JsonNode json) { memoryMonitor.apply(json); }
public int getQueuedHooksSizeB() { if (monitor != null) { return monitor.getDataSizeB(); } else { return 0; } }
/** * Result set size threshold is expressed in bytes. This is just an approximation, see @{link {@link JsonUtils#size(JsonNode)} for details. * * @param maxResultSetSizeB error when this threshold is breached * @param warnResultSetSizeB log a warning when this threshold is breached * @param forRequest request which resulted in this response, for logging purposes */ public void setResultSizeThresholds(int maxResultSetSizeB, int warnResultSetSizeB, final Request forRequest) { this.memoryMonitor = new MemoryMonitor<>((jsonNode) -> JsonUtils.size(jsonNode)); // Order is significant – warn first for request in log output. memoryMonitor.registerMonitor(new ThresholdMonitor<JsonNode>(warnResultSetSizeB, (current, threshold, doc) -> { LOGGER.warn("crud:ResultSizeIsLarge: request={}, responseDataSizeB={} threshold={}", forRequest, current, threshold); })); memoryMonitor.registerMonitor(new ThresholdMonitor<JsonNode>(maxResultSetSizeB, (current, threshold, doc) -> { // empty data // returning incomplete result set could be useful, but also confusing and thus dangerous // the counts - matchCount, modifiedCount - are unmodified setEntityData(JsonNodeFactory.instance.arrayNode()); setStatus(OperationStatus.ERROR); throw Error.get(ERR_RESULT_SIZE_TOO_LARGE, current + "B > " + threshold + "B"); })); }