kr.co.bitnine.octopus.frame.Session.java Source code

Java tutorial

Introduction

Here is the source code for kr.co.bitnine.octopus.frame.Session.java

Source

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package kr.co.bitnine.octopus.frame;

import kr.co.bitnine.octopus.engine.QueryEngine;
import kr.co.bitnine.octopus.meta.MetaContext;
import kr.co.bitnine.octopus.meta.MetaException;
import kr.co.bitnine.octopus.postgres.access.common.TupleDesc;
import kr.co.bitnine.octopus.postgres.access.transam.TransactionStatus;
import kr.co.bitnine.octopus.postgres.catalog.PostgresAttribute;
import kr.co.bitnine.octopus.postgres.catalog.PostgresType;
import kr.co.bitnine.octopus.postgres.executor.Tuple;
import kr.co.bitnine.octopus.postgres.executor.TupleSet;
import kr.co.bitnine.octopus.postgres.libpq.Message;
import kr.co.bitnine.octopus.postgres.libpq.MessageStream;
import kr.co.bitnine.octopus.postgres.libpq.ProtocolConstants;
import kr.co.bitnine.octopus.postgres.utils.PostgresErrorData;
import kr.co.bitnine.octopus.postgres.utils.PostgresException;
import kr.co.bitnine.octopus.postgres.utils.PostgresSQLState;
import kr.co.bitnine.octopus.postgres.utils.PostgresSeverity;
import kr.co.bitnine.octopus.postgres.utils.adt.FormatCode;
import kr.co.bitnine.octopus.postgres.utils.adt.IoFunction;
import kr.co.bitnine.octopus.postgres.utils.adt.IoFunctions;
import kr.co.bitnine.octopus.postgres.utils.cache.CachedQuery;
import kr.co.bitnine.octopus.postgres.utils.cache.Portal;
import kr.co.bitnine.octopus.postgres.utils.misc.PostgresConfiguration;
import kr.co.bitnine.octopus.schema.SchemaManager;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;

import java.io.EOFException;
import java.io.IOException;
import java.nio.channels.SocketChannel;
import java.util.Map;
import java.util.Random;

public final class Session implements Runnable {
    private static final Log LOG = LogFactory.getLog(Session.class);

    private final SocketChannel clientChannel;
    private final int sessionId; // secret key
    private final CancelContext cancelContext;

    interface EventHandler {
        void onClose(Session session);

        void onCancel(int sessionId);
    }

    private final EventHandler eventHandler;

    private final MessageStream messageStream;
    private final MetaContext metaContext;
    private final QueryEngine queryEngine;

    private final PostgresConfiguration postgresConf;

    Session(SocketChannel clientChannel, EventHandler eventHandler, MetaContext metaContext,
            ConnectionManager connectionManager, SchemaManager schemaManager, Configuration conf) {
        this.clientChannel = clientChannel;
        sessionId = new Random(this.hashCode()).nextInt();
        cancelContext = new CancelContext(this);

        this.eventHandler = eventHandler;

        messageStream = new MessageStream(clientChannel);
        this.metaContext = metaContext;
        queryEngine = new QueryEngine(metaContext, connectionManager, schemaManager, conf);

        postgresConf = new PostgresConfiguration();
    }

    public int getId() {
        return sessionId;
    }

    public boolean isCanceled() {
        return cancelContext.isCanceled();
    }

    private static final ThreadLocal<Session> LOCAL_SESSION = new ThreadLocal<>();

    public static Session currentSession() {
        Session currSess = LOCAL_SESSION.get();
        if (currSess == null)
            throw new RuntimeException("current session does not exist");

        return LOCAL_SESSION.get();
    }

    @Override
    public void run() {
        LOCAL_SESSION.set(this);
        try {
            boolean proceed = doStartup();
            if (proceed) {
                doAuthentication();

                messageLoop();
            }
        } catch (Exception e) {
            LOG.fatal(ExceptionUtils.getStackTrace(e));
        }
        LOCAL_SESSION.remove();

        close();
    }

    void emitErrorReport(PostgresErrorData errorData) throws IOException {
        messageStream.putMessageAndFlush(errorData.toMessage());
    }

    void reject() {
        try {
            PostgresErrorData edata = new PostgresErrorData(PostgresSeverity.FATAL,
                    PostgresSQLState.TOO_MANY_CONNECTIONS, "too many clients already, rejected");
            emitErrorReport(edata);
        } catch (IOException e) {
            LOG.error(ExceptionUtils.getStackTrace(e));
        }

        close();
    }

    public String getClientParam(String key) {
        return postgresConf.get(key);
    }

    public String setClientParam(String key, String value) {
        return postgresConf.put(key, value);
    }

    private boolean doStartup() throws IOException, OctopusException {
        Message imsg = messageStream.getInitialMessage();
        int i = imsg.peekInt();

        if (i == ProtocolConstants.CANCEL_REQUEST_CODE) {
            handleCancelRequest(imsg);
            return false;
        }

        if (i == ProtocolConstants.SSL_REQUEST_CODE) {
            handleSSLRequest(imsg);
            return doStartup();
        }

        handleStartupMessage(imsg);
        return true;
    }

    private void handleCancelRequest(Message imsg) {
        imsg.getInt(); // cancel request code
        imsg.getInt(); // process ID, not used
        int cancelKey = imsg.getInt();

        LOG.debug("handle CancelRequest message");

        // cancelKey is the same as sessionId
        eventHandler.onCancel(cancelKey);
    }

    private void handleSSLRequest(Message imsg) throws IOException, OctopusException {
        LOG.debug("handle SSLRequest message");

        // TODO: SSL

        PostgresErrorData edata = new PostgresErrorData(PostgresSeverity.FATAL,
                PostgresSQLState.FEATURE_NOT_SUPPORTED, "unsupported frontend protocol");
        new OctopusException(edata).emitErrorReport();
    }

    private void handleStartupMessage(Message imsg) throws IOException, OctopusException {
        LOG.debug("handle StartupMessage message");

        int version = imsg.getInt();
        if (ProtocolConstants.protocolMajor(version) != ProtocolConstants
                .protocolMajor(ProtocolConstants.PROTOCOL_LATEST)
                || ProtocolConstants.protocolMinor(version) > ProtocolConstants
                        .protocolMinor(ProtocolConstants.PROTOCOL_LATEST)) {
            PostgresErrorData edata = new PostgresErrorData(PostgresSeverity.FATAL,
                    PostgresSQLState.FEATURE_NOT_SUPPORTED, "unsupported frontend protocol");
            new OctopusException(edata).emitErrorReport();
        }

        while (true) {
            String paramName = imsg.getCString();
            if (paramName.length() == 0)
                break;
            String paramValue = imsg.getCString();
            postgresConf.put(paramName, paramValue);
        }

        if (LOG.isDebugEnabled()) {
            for (Map.Entry<String, String> e : postgresConf.entrySet())
                LOG.debug(e.getKey() + "=" + e.getValue());
        }
    }

    // NOTE: Now, cleartext only
    private void doAuthentication() throws IOException, OctopusException {
        LOG.debug("send AuthenticationCleartextPassword message");
        // AuthenticationCleartextPassword
        Message msg = Message.builder('R').putInt(3).build();
        messageStream.putMessageAndFlush(msg);

        // receive PasswordMessage
        msg = messageStream.getMessage();
        if (msg.getType() != 'p') {
            PostgresErrorData edata = new PostgresErrorData(PostgresSeverity.FATAL,
                    PostgresSQLState.PROTOCOL_VIOLATION,
                    "expected password response, got message type '" + msg.getType() + "'");
            new OctopusException(edata).emitErrorReport();
        }

        // verify password
        String username = postgresConf.get(PostgresConfiguration.PARAM_USER);
        try {
            String password = msg.getCString();
            String currentPassword = metaContext.getUser(username).getPassword();
            if (!password.equals(currentPassword)) {
                PostgresErrorData edata = new PostgresErrorData(PostgresSeverity.FATAL,
                        PostgresSQLState.INVALID_PASSWORD, "password authentication failed for user " + username);
                new OctopusException(edata).emitErrorReport();
            }
        } catch (MetaException e) {
            PostgresErrorData edata = new PostgresErrorData(PostgresSeverity.FATAL,
                    PostgresSQLState.PROTOCOL_VIOLATION, "invalid user name '" + username + "'");
            new OctopusException(edata, e).emitErrorReport();
        }

        LOG.debug("send AuthenticationOk message");
        // AuthenticationOk
        msg = Message.builder('R').putInt(0).build();
        messageStream.putMessage(msg);

        LOG.debug("send ParameterStatus message");
        // ParameterStatus
        String[] reportedParams = { PostgresConfiguration.PARAM_INTEGER_DATETIMES,
                PostgresConfiguration.PARAM_DATESTYLE, PostgresConfiguration.PARAM_CLIENT_ENCODING,
                PostgresConfiguration.PARAM_SERVER_ENCODING, PostgresConfiguration.PARAM_SERVER_VERSION };
        for (String param : reportedParams) {
            msg = Message.builder('S').putCString(param).putCString(postgresConf.get(param)).build();
            messageStream.putMessage(msg);
        }

        LOG.debug("send BackendKeyData message");
        // BackendKeyData
        msg = Message.builder('K').putInt(0) // process ID, not used
                .putInt(sessionId).build();
        messageStream.putMessage(msg);

        LOG.info("authentication success for \"" + username + "\" (session=" + getId() + ')');
    }

    private void messageLoop() throws Exception {
        boolean doingExtendedQueryMessage = false;
        boolean ignoreTillSync = false;
        boolean sendReadyForQuery = true;

        while (true) {
            try {
                doingExtendedQueryMessage = false;

                if (sendReadyForQuery) {
                    LOG.debug("send ReadyForQuery message");
                    Message msg = Message.builder('Z').putChar(TransactionStatus.IDLE.getIndicator()).build();
                    messageStream.putMessageAndFlush(msg);
                    sendReadyForQuery = false;
                }

                Message msg = messageStream.getMessage();
                char type = msg.getType();

                switch (type) {
                case 'Q':
                    break;
                case 'P':
                case 'B':
                case 'E':
                case 'C':
                case 'D':
                case 'H':
                    LOG.debug("extended query sub-protocol message");
                    doingExtendedQueryMessage = true;
                    break;
                case 'S':
                case 'X':
                    ignoreTillSync = false;
                    break;
                case 'd':
                case 'c':
                case 'f':
                    break;
                default:
                    PostgresErrorData edata = new PostgresErrorData(PostgresSeverity.FATAL,
                            PostgresSQLState.PROTOCOL_VIOLATION, "invalid frontend message type '" + type + "'");
                    new OctopusException(edata).emitErrorReport();
                }

                if (ignoreTillSync) {
                    LOG.debug("ignore message(type='" + type + "') till Sync message");
                    continue;
                }

                switch (type) {
                case 'Q':
                    cancelContext.enterCancel();
                    handleQuery(msg);
                    cancelContext.exitCancel();
                    sendReadyForQuery = true;
                    break;
                case 'P':
                    cancelContext.enterCancel();
                    handleParse(msg);
                    break;
                case 'B':
                    handleBind(msg);
                    break;
                case 'E':
                    handleExecute(msg);
                    break;
                case 'C':
                    handleClose(msg);
                    break;
                case 'D':
                    handleDescribe(msg);
                    break;
                case 'H':
                    LOG.debug("handle Flush message");
                    messageStream.flush();
                    break;
                case 'S':
                    LOG.debug("handle Sync message");
                    cancelContext.exitCancel();
                    sendReadyForQuery = true;
                    break;
                case 'X':
                    LOG.info("Terminate received");
                    return;
                case 'd': // copy data
                case 'c': // copy done
                case 'f': // copy fail
                    break; // ignore these messages
                default:
                    PostgresErrorData edata = new PostgresErrorData(PostgresSeverity.FATAL,
                            PostgresSQLState.PROTOCOL_VIOLATION, "invalid frontend message type '" + type + "'");
                    new OctopusException(edata).emitErrorReport();
                }
            } catch (OctopusException oe) {
                switch (oe.getErrorData().getSeverity()) {
                case PANIC: // exit Octopus
                    LOG.fatal(ExceptionUtils.getStackTrace(oe));
                    System.exit(0);
                    break;
                case FATAL: // exit Session
                    throw oe;
                case ERROR:
                    LOG.error(ExceptionUtils.getStackTrace(oe));
                    cancelContext.exitCancel();
                    break;
                default:
                    throw new RuntimeException("could not reach here");
                }

                // ERROR
                if (doingExtendedQueryMessage) {
                    LOG.debug("ignore till Sync message");
                    ignoreTillSync = true;
                }
                if (!ignoreTillSync)
                    sendReadyForQuery = true;
            } catch (EOFException eofe) {
                PostgresErrorData edata = new PostgresErrorData(PostgresSeverity.FATAL,
                        PostgresSQLState.PROTOCOL_VIOLATION, eofe.getMessage());
                emitErrorReport(edata);
                throw eofe;
            }
        }
    }

    private void sendEmptyQueryResponse() throws IOException {
        LOG.debug("send EmptyQueryResponse message");
        messageStream.putMessage(Message.builder('I').build());
    }

    private void sendCommandComplete(String tag) throws IOException {
        LOG.debug("send CommandComplete message");

        // FIXME: tag format
        String newTag = tag;
        if (newTag == null)
            newTag = "SELECT 0";

        Message msg = Message.builder('C').putCString(newTag).build();
        messageStream.putMessage(msg);
    }

    private void sendRowDescription(TupleDesc tupDesc, FormatCode[] resultFormats) throws IOException {
        LOG.debug("send RowDescription message");

        PostgresAttribute[] attrs = tupDesc.getAttributes();

        // RowDescription
        Message.Builder msgBld = Message.builder('T').putShort((short) attrs.length);
        for (int i = 0; i < attrs.length; i++) {
            msgBld.putCString(attrs[i].getName()).putInt(0) // table OID
                    .putShort((short) 0); // attribute number

            PostgresType type = attrs[i].getType();
            msgBld.putInt(type.oid()) // data type OID
                    .putShort((short) type.typeLength()); // data type size
            if (type == PostgresType.VARCHAR) // type-specific type modifier
                msgBld.putInt(attrs[i].getTypeInfo());
            else
                msgBld.putInt(-1);

            if (resultFormats.length > 0)
                msgBld.putShort((short) resultFormats[i].code());
            else
                msgBld.putShort((short) FormatCode.TEXT.code());
        }
        messageStream.putMessage(msgBld.build());
    }

    private void sendDataRow(TupleSet ts, int numRows) throws IOException, PostgresException {
        LOG.debug("send DataRow message");

        TupleDesc td = ts.getTupleDesc();

        PostgresAttribute[] attrs = td.getAttributes();
        FormatCode[] resultFormats = td.getResultFormats();

        // DataRow
        while (true) {
            if (isCanceled()) {
                if (LOG.isDebugEnabled())
                    LOG.debug("cancel sending result rows for session(" + sessionId + ')');

                PostgresErrorData edata = new PostgresErrorData(PostgresSeverity.ERROR,
                        PostgresSQLState.QUERY_CANCELED,
                        "canceling statement for session(" + getId() + ") due to user request");
                throw new PostgresException(edata);
            }

            Tuple t = ts.next();
            if (t == null)
                break;

            Message.Builder msgBld = Message.builder('D').putShort((short) attrs.length);
            Object[] datums = t.getDatums();
            for (int i = 0; i < datums.length; i++) {
                byte[] bytes;

                IoFunction io = IoFunctions.ofType(attrs[i].getType());
                if (resultFormats[i] == FormatCode.TEXT)
                    bytes = io.out(datums[i]);
                else
                    bytes = io.send(datums[i]);

                if (bytes == null)
                    msgBld.putInt(-1); // -1 indicates a NULL column value
                else
                    msgBld.putInt(bytes.length).putBytes(bytes);
            }
            messageStream.putMessage(msgBld.build());
        }
    }

    private void handleQuery(Message msg) throws IOException, OctopusException {
        String queryString = msg.getCString();

        LOG.info("query {" + queryString + "}");
        LOG.debug("handle Query message (query={" + queryString + "})");

        // TODO: support multiple queries in a single queryString

        try {
            Portal p = queryEngine.query(queryString);
            if (p.getCachedQuery().getCommandTag() == null) {
                sendEmptyQueryResponse();
                return;
            }

            // FIXME: See {PortalState}
            LOG.error("run portal '" + p.getName() + "'");
            try {
                TupleSet ts = p.run(0);
                if (ts != null) { // ts == null if DDL
                    sendRowDescription(ts.getTupleDesc(), ts.getTupleDesc().getResultFormats());
                    sendDataRow(ts, 0);
                    ts.close();
                }
            } catch (Exception e) {
                LOG.error("failed to run portal '" + p.getName() + "'");
                p.setState(Portal.State.FAILED);
                p.close();
                throw e;
            }

            // NOTE: SimpleQuery has no Suspend/Execute mechanism
            assert p.getState() == Portal.State.DONE;

            sendCommandComplete(p.getCompletionTag());
        } catch (PostgresException e) {
            new OctopusException(e.getErrorData(), e).emitErrorReport();
        }
    }

    private void handleParse(Message msg) throws IOException, OctopusException {
        String stmtName = msg.getCString();
        String queryString = msg.getCString();
        short numParams = msg.getShort();
        PostgresType[] paramTypes = new PostgresType[numParams];
        for (short i = 0; i < numParams; i++)
            paramTypes[i] = PostgresType.ofOid(msg.getInt());

        LOG.info("query {" + queryString + "}");
        LOG.debug("handle Parse message (stmt=" + stmtName + ", query={" + queryString + "})");
        if (LOG.isDebugEnabled()) {
            for (short i = 0; i < paramTypes.length; i++)
                LOG.debug("paramTypes[" + i + "]=" + paramTypes[i].name());
        }

        try {
            queryEngine.parse(queryString, stmtName, paramTypes);
        } catch (PostgresException e) {
            new OctopusException(e.getErrorData(), e).emitErrorReport();
        }

        // ParseComplete
        messageStream.putMessage(Message.builder('1').build());
    }

    private void handleBind(Message msg) throws IOException, OctopusException {
        String portalName = msg.getCString();
        String stmtName = msg.getCString();

        LOG.debug("handle Bind message (portal=" + portalName + ", stmt=" + stmtName + ")");

        short numParamFormat = msg.getShort();
        FormatCode[] paramFormats = new FormatCode[numParamFormat];
        for (short i = 0; i < numParamFormat; i++)
            paramFormats[i] = FormatCode.ofCode((int) msg.getShort());

        short numParamValue = msg.getShort();
        byte[][] paramValues = new byte[numParamValue][];
        for (short i = 0; i < numParamValue; i++) {
            int paramLen = msg.getInt(); // -1 indicates NULL parameter
            paramValues[i] = paramLen > -1 ? msg.getBytes(paramLen) : null;
        }

        short numResult = msg.getShort();
        FormatCode[] resultFormats = new FormatCode[numResult];
        for (short i = 0; i < numResult; i++)
            resultFormats[i] = FormatCode.ofCode((int) msg.getShort());

        if (LOG.isDebugEnabled()) {
            for (short i = 0; i < numParamFormat; i++)
                LOG.debug("paramFormats[" + i + "]=" + paramFormats[i].name());
            for (short i = 0; i < numParamValue; i++)
                LOG.debug("paramValues[" + i + "]=" + paramValues[i]);
            for (short i = 0; i < numResult; i++)
                LOG.debug("resultFormats[" + i + "]=" + resultFormats[i].name());
        }

        try {
            queryEngine.bind(stmtName, portalName, paramFormats, paramValues, resultFormats);
        } catch (PostgresException e) {
            new OctopusException(e.getErrorData(), e).emitErrorReport();
        }

        // BindComplete
        messageStream.putMessage(Message.builder('2').build());
    }

    private void handleExecute(Message msg) throws IOException, OctopusException {
        String portalName = msg.getCString();
        int numRows = msg.getInt();

        LOG.debug("handle Execute mesasge (portal=" + portalName + ", rows=" + numRows + ")");

        try {
            Portal p = queryEngine.getPortal(portalName);
            if (p.getCachedQuery().getCommandTag() == null) {
                sendEmptyQueryResponse();
                return;
            }

            /*
             * FIXME: {PortalState} refactoring!
             * To run portal repeatedly, the state of the portal must be
             * either DONE or FAILED.
             * If extended query protocol is ended abnormally, the state
             * of the portal must be set with FAILED.
             */
            LOG.info("run portal '" + p.getName() + "'");
            try {
                TupleSet ts = p.run(numRows);
                if (ts != null) // ts == null if DDL
                    sendDataRow(ts, numRows);
            } catch (Exception e) {
                LOG.error("failed to run portal '" + p.getName() + "'");
                p.setState(Portal.State.FAILED);
                p.close();
                throw e;
            }

            if (p.getState() == Portal.State.ACTIVE) {
                messageStream.putMessage(Message.builder('s').build()); // PortalSuspend
            } else {
                sendCommandComplete(p.getCompletionTag());
                p.close();
            }
        } catch (PostgresException e) {
            new OctopusException(e.getErrorData(), e).emitErrorReport();
        }
    }

    private void handleClose(Message msg) throws IOException, OctopusException {
        char type = msg.getChar(); // 'S' for a prepared statement, 'P' for a portal
        String name = msg.getCString();

        LOG.debug("handle Close message (type='" + type + "', name=" + name + ")");

        try {
            switch (type) {
            case 'S':
                queryEngine.closeCachedQuery(name);
                break;
            case 'P':
                queryEngine.closePortal(name);
                break;
            default:
                PostgresErrorData edata = new PostgresErrorData(PostgresSeverity.ERROR,
                        PostgresSQLState.PROTOCOL_VIOLATION, "invalid CLOSE message subtype '" + type + "'");
                new OctopusException(edata).emitErrorReport();
            }
        } catch (PostgresException e) {
            new OctopusException(e.getErrorData(), e).emitErrorReport();
        }

        // CloseComplete
        messageStream.putMessage(Message.builder('3').build());
    }

    private void sendParameterDescription(PostgresType[] paramTypes) throws IOException {
        LOG.debug("send ParameterDescription message");
        Message.Builder msgBld = Message.builder('t').putShort((short) paramTypes.length);
        for (PostgresType type : paramTypes)
            msgBld.putInt(type.oid());
        messageStream.putMessage(msgBld.build());
    }

    private void sendNoData() throws IOException {
        LOG.debug("send NoData message");
        messageStream.putMessage(Message.builder('n').build());
    }

    private void handleDescribe(Message msg) throws IOException, OctopusException {
        char type = msg.getChar(); // 'S' for a prepared statement, 'P' for a portal
        String name = msg.getCString();

        LOG.debug("handle Describe message (type='" + type + "', name=" + name + ")");

        try {
            TupleDesc tupDesc;
            switch (type) {
            case 'S':
                /*
                PostgresErrorData edata = new PostgresErrorData(
                    PostgresSeverity.FATAL,
                    PostgresSQLState.FEATURE_NOT_SUPPORTED,
                    "unsupported frontend protocol");
                new OctopusException(edata).emitErrorReport();
                 */
                CachedQuery cq = queryEngine.getCachedQuery(name);
                sendParameterDescription(cq.getParamTypes());
                tupDesc = cq.describe();
                if (tupDesc == null)
                    sendNoData();
                else
                    sendRowDescription(tupDesc, new FormatCode[0]);
                break;
            case 'P':
                Portal p = queryEngine.getPortal(name);
                // FIXME: See {PortalState}
                try {
                    tupDesc = p.describe();
                    if (tupDesc == null)
                        sendNoData();
                    else
                        sendRowDescription(tupDesc, tupDesc.getResultFormats());
                } catch (Exception e) {
                    p.setState(Portal.State.FAILED);
                    p.close();
                    throw e;
                }
                break;
            default:
                PostgresErrorData edata = new PostgresErrorData(PostgresSeverity.ERROR,
                        PostgresSQLState.PROTOCOL_VIOLATION, "invalid DESCRIBE message subtype '" + type + "'");
                new OctopusException(edata).emitErrorReport();
            }
        } catch (PostgresException e) {
            new OctopusException(e.getErrorData(), e).emitErrorReport();
        }
    }

    void close() {
        try {
            clientChannel.close();
        } catch (IOException e) {
            LOG.info(ExceptionUtils.getStackTrace(e));
        }

        metaContext.close();
        queryEngine.closeAll();

        eventHandler.onClose(this);
    }

    void cancel() {
        cancelContext.cancel();
    }
}