protected void evalOpInternal()

in gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractEvalOpProcessor.java [211:350]


    protected void evalOpInternal(final Context ctx, final Supplier<GremlinExecutor> gremlinExecutorSupplier,
                                  final BindingSupplier bindingsSupplier) {
        final Timer.Context timerContext = evalOpTimer.time();
        final RequestMessage msg = ctx.getRequestMessage();
        final GremlinExecutor gremlinExecutor = gremlinExecutorSupplier.get();
        final GraphManager graphManager = ctx.getGraphManager();
        final Settings settings = ctx.getSettings();

        final Map<String, Object> args = msg.getArgs();

        final String script = (String) args.get(Tokens.ARGS_GREMLIN);
        final String language = args.containsKey(Tokens.ARGS_LANGUAGE) ? (String) args.get(Tokens.ARGS_LANGUAGE) : null;
        final Bindings bindings = new SimpleBindings();

        // sessionless requests are always transaction managed, but in-session requests are configurable.
        final boolean managedTransactionsForRequest = manageTransactions ?
                true : (Boolean) args.getOrDefault(Tokens.ARGS_MANAGE_TRANSACTION, false);

        // timeout override - handle both deprecated and newly named configuration. earlier logic should prevent
        // both configurations from being submitted at the same time
        final long seto = args.containsKey(Tokens.ARGS_EVAL_TIMEOUT) ?
                ((Number) args.get(Tokens.ARGS_EVAL_TIMEOUT)).longValue() : settings.getEvaluationTimeout();

        final GremlinExecutor.LifeCycle lifeCycle = GremlinExecutor.LifeCycle.build()
                .evaluationTimeoutOverride(seto)
                .afterFailure((b,t) -> {
                    graphManager.onQueryError(msg, t);
                    if (managedTransactionsForRequest) attemptRollback(msg, ctx.getGraphManager(), settings.strictTransactionManagement);
                })
                .afterTimeout((b, t) -> {
                  graphManager.onQueryError(msg, t);
                })
                .beforeEval(b -> {
                    graphManager.beforeQueryStart(msg);
                    try {
                        b.putAll(bindingsSupplier.get());
                    } catch (OpProcessorException ope) {
                        // this should bubble up in the GremlinExecutor properly as the RuntimeException will be
                        // unwrapped and the root cause thrown
                        throw new RuntimeException(ope);
                    }
                })
                .withResult(o -> {
                    final Iterator itty = IteratorUtils.asIterator(o);

                    logger.debug("Preparing to iterate results from - {} - in thread [{}]", msg, Thread.currentThread().getName());
                    if (settings.enableAuditLog) {
                        AuthenticatedUser user = ctx.getChannelHandlerContext().channel().attr(StateKey.AUTHENTICATED_USER).get();
                        if (null == user) {    // This is expected when using the AllowAllAuthenticator
                            user = AuthenticatedUser.ANONYMOUS_USER;
                        }
                        String address = ctx.getChannelHandlerContext().channel().remoteAddress().toString();
                        if (address.startsWith("/") && address.length() > 1) address = address.substring(1);
                        auditLogger.info("User {} with address {} requested: {}", user.getName(), address, script);
                    }

                    try {
                        handleIterator(ctx, itty);
                        graphManager.onQuerySuccess(msg);
                    } catch (Exception ex) {
                        graphManager.onQueryError(msg, ex);
                        if (managedTransactionsForRequest) attemptRollback(msg, ctx.getGraphManager(), settings.strictTransactionManagement);

                        CloseableIterator.closeIterator(itty);

                        // wrap up the exception and rethrow. the error will be written to the client by the evalFuture
                        // as it will completeExceptionally in the GremlinExecutor
                        throw new RuntimeException(ex);
                    }
                }).create();

        try {
            final CompletableFuture<Object> evalFuture = gremlinExecutor.eval(script, language, bindings, lifeCycle);

            evalFuture.handle((v, t) -> {
                timerContext.stop();

                if (t != null) {
                    // if any exception in the chain is TemporaryException or Failure then we should respond with the
                    // right error code so that the client knows to retry
                    final Optional<Throwable> possibleSpecialException = determineIfSpecialException(t);
                    if (possibleSpecialException.isPresent()) {
                        final Throwable special = possibleSpecialException.get();
                        final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(msg).
                                statusMessage(special.getMessage()).
                                statusAttributeException(special);
                        if (special instanceof TemporaryException) {
                            specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
                        } else if (special instanceof Failure) {
                            final Failure failure = (Failure) special;
                            specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
                                    statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
                        }
                        ctx.writeAndFlush(specialResponseMsg.create());
                    } else if (t instanceof OpProcessorException) {
                        ctx.writeAndFlush(((OpProcessorException) t).getResponseMessage());
                    } else if (t instanceof TimedInterruptTimeoutException) {
                        // occurs when the TimedInterruptCustomizerProvider is in play
                        final String errorMessage = String.format("A timeout occurred within the script during evaluation of [%s] - consider increasing the limit given to TimedInterruptCustomizerProvider", msg);
                        logger.warn(errorMessage);
                        ctx.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TIMEOUT)
                                .statusMessage("Timeout during script evaluation triggered by TimedInterruptCustomizerProvider")
                                .statusAttributeException(t).create());
                    } else if (t instanceof TimeoutException) {
                        final String errorMessage = String.format("Script evaluation exceeded the configured threshold for request [%s]", msg);
                        logger.warn(errorMessage, t);
                        ctx.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TIMEOUT)
                                .statusMessage(t.getMessage())
                                .statusAttributeException(t).create());
                    } else {
                        // try to trap the specific jvm error of "Method code too large!" to re-write it as something nicer,
                        // but only re-write if it's the only error because otherwise we might lose some other important
                        // information related to the failure. at this point, there hasn't been a scenario that has
                        // presented itself where the "Method code too large!" comes with other compilation errors so
                        // it seems that this message trumps other compilation errors to some reasonable degree that ends
                        // up being favorable for this problem
                        if (t instanceof MultipleCompilationErrorsException && t.getMessage().contains("Method too large") &&
                                ((MultipleCompilationErrorsException) t).getErrorCollector().getErrorCount() == 1) {
                            final String errorMessage = String.format("The Gremlin statement that was submitted exceeds the maximum compilation size allowed by the JVM, please split it into multiple smaller statements - %s", trimMessage(msg));
                            logger.warn(errorMessage);
                            ctx.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_EVALUATION)
                                    .statusMessage(errorMessage)
                                    .statusAttributeException(t).create());
                        } else {
                            final String errorMessage = (t.getMessage() == null) ? t.toString() : t.getMessage();
                            logger.warn(String.format("Exception processing a script on request [%s].", msg), t);
                            ctx.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_EVALUATION)
                                    .statusMessage(errorMessage)
                                    .statusAttributeException(t).create());
                        }
                    }
                }

                return null;
            });
        } catch (RejectedExecutionException ree) {
            ctx.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.TOO_MANY_REQUESTS)
                    .statusMessage("Rate limiting").create());
        }
    }