All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.matteobertozzi.easerinsights.jdbc.connection.DbRefChecker Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 io.github.matteobertozzi.easerinsights.jdbc.connection;

import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import io.github.matteobertozzi.easerinsights.jdbc.DbInfo;
import io.github.matteobertozzi.easerinsights.logging.Logger;
import io.github.matteobertozzi.rednaco.strings.StringConverter;

interface DbRefChecker {
  int SUSPICIOUS_OPEN_RESULT_SET_COUNT = StringConverter.toInt(System.getProperty("easer.insights.jdbc.suspicious.open.resultset.count"), 10);
  int SUSPICIOUS_OPEN_STATEMENT_COUNT = StringConverter.toInt(System.getProperty("easer.insights.jdbc.suspicious.open.statement.count"), 20);

  boolean ENABLE_REF_CHECKER = StringConverter.toBoolean(System.getProperty("easer.insights.jdbc.ref.checker.enabled"), false);
  Function REF_CHECKER_FACTORY = ENABLE_REF_CHECKER ? SimpleRefChecker::get : NoOpRefChecker::get;

  static DbRefChecker get(final DbInfo dbInfo) {
    return REF_CHECKER_FACTORY.apply(dbInfo);
  }

  DbConnectionRefChecker addConnection(DbConnection connection);
  void closeConnection(DbConnection connection);

  interface DbConnectionRefChecker {
    void destroy();

    void addStatement(Statement statement);
    boolean removeStatement(Statement statement);

    void addResultSet(ResultSet rs);
    boolean removeResultSet(ResultSet rs);
  }

  // ==========================================================================================
  //  No-Op Ref Checker
  // ==========================================================================================
  final class NoOpRefChecker implements DbRefChecker {
    private static final NoOpRefChecker INSTANCE = new NoOpRefChecker();

    static DbRefChecker get(final DbInfo dbInfo) {
      return NoOpRefChecker.INSTANCE;
    }

    @Override
    public DbConnectionRefChecker addConnection(final DbConnection connection) {
      return NoOpConnectionRefChecker.INSTANCE;
    }

    @Override
    public void closeConnection(final DbConnection connection) {
      // no-op
    }
  }

  final class NoOpConnectionRefChecker implements DbConnectionRefChecker {
    private static final NoOpConnectionRefChecker INSTANCE = new NoOpConnectionRefChecker();

    @Override public void destroy() {}
    @Override public void addStatement(final Statement statement) {}
    @Override public boolean removeStatement(final Statement statement) { return true; }
    @Override public void addResultSet(final ResultSet rs) {}
    @Override public boolean removeResultSet(final ResultSet rs) { return true; }
  }

  // ==========================================================================================
  //  Simple Ref Checker
  // ==========================================================================================
  final class SimpleRefChecker implements DbRefChecker {
    private static final ConcurrentHashMap refsMap = new ConcurrentHashMap<>(32);

    private final ConcurrentHashMap connectionRefs = new ConcurrentHashMap<>(64);
    //private final Set pooledRefs = ConcurrentHashMap.newKeySet(64);

    private SimpleRefChecker(final DbInfo dbInfo) {
      // no-op
    }

    static DbRefChecker get(final DbInfo dbInfo) {
      final DbRefChecker refChecker = refsMap.get(dbInfo);
      if (refChecker != null) return refChecker;
      return refsMap.computeIfAbsent(dbInfo, SimpleRefChecker::new);
    }

    @Override
    public DbConnectionRefChecker addConnection(final DbConnection connection) {
      //pooledRefs.remove(connection);

      final SimpleConnectionRefChecker refChecker = new SimpleConnectionRefChecker(connection);
      if (connectionRefs.put(connection, refChecker) != null) {
        Logger.warn("CONNECTION ALREADY REGISTERED: {}", connection);
      }
      return refChecker;
    }

    @Override
    public void closeConnection(final DbConnection connection) {
      final DbConnectionRefChecker refChecker = connectionRefs.remove(connection);
      if (refChecker != null) {
        refChecker.destroy();
      //} else if (!pooledRefs.remove(connection)) {
      } else {
        Logger.warn("CONNECTION ALREADY CLOSED: {}", connection);
      }

      //if (connection.hasPool()) {
        //pooledRefs.add(connection);
      //}
    }
  }

  final class SimpleConnectionRefChecker implements DbConnectionRefChecker {
    private final ConcurrentHashMap statementRefs = new ConcurrentHashMap<>();
    private final ConcurrentHashMap resultSetRefs = new ConcurrentHashMap<>();
    private final String creationStackTrace;
    private long lastUpdate;

    private SimpleConnectionRefChecker(final DbConnection connection) {
      this.lastUpdate = System.currentTimeMillis();
      this.creationStackTrace = buildStackTraceForRef(connection);
    }

    @Override
    public void destroy() {
      if (statementRefs.isEmpty() && resultSetRefs.isEmpty()) {
        return;
      }

      Logger.error("connection closed with {} resultSet active and {} statements active", resultSetRefs.size(), statementRefs.size());
      for (final Entry entry: resultSetRefs.entrySet()) {
        Logger.warn("RESULT-SET NOT CLOSED: {}", entry.getKey());
        DbConnection.closeQuietly(entry.getKey());
      }
      resultSetRefs.clear();

      for (final Entry entry: statementRefs.entrySet()) {
        Logger.warn("STATEMENT NOT CLOSED: {}", entry.getKey());
        DbConnection.closeQuietly(entry.getKey());
      }
      statementRefs.clear();
    }

    @Override
    public void addStatement(final Statement statement) {
      lastUpdate = System.nanoTime();
      final String oldTrace = statementRefs.put(statement, buildStackTraceForRef(statement));
      if (oldTrace != null) {
        Logger.error(new IllegalStateException(), "STATEMENT REGISTERED TWICE: {}, oldTrace: {}", statement, oldTrace);
      }

      if (statementRefs.size() >= SUSPICIOUS_OPEN_STATEMENT_COUNT) {
        Logger.warn("suspicious open Statement {count}: {}", statementRefs.size(), String.join("-----\n", statementRefs.values()));
      }
    }

    @Override
    public boolean removeStatement(final Statement statement) {
      lastUpdate = System.nanoTime();
      if (statementRefs.remove(statement) == null) {
        Logger.error(new IllegalStateException(), "STATEMENT NOT REGISTERED: {}", statement);
        return false;
      }
      return true;
    }

    @Override
    public void addResultSet(final ResultSet rs) {
      lastUpdate = System.nanoTime();
      final String oldTrace = resultSetRefs.put(rs, buildStackTraceForRef(rs));
      if (oldTrace != null) {
        Logger.error(new IllegalStateException(), "RESULT-SET REGISTERED TWICE {}, oldTrace: {}", rs, oldTrace);
      }

      if (resultSetRefs.size() >= SUSPICIOUS_OPEN_RESULT_SET_COUNT) {
        Logger.warn("suspicious open ResultSet {count}", resultSetRefs.size(), String.join("-----\n", statementRefs.values()));
      }
    }

    @Override
    public boolean removeResultSet(final ResultSet rs) {
      lastUpdate = System.nanoTime();
      if (resultSetRefs.remove(rs) == null) {
        Logger.error(new IllegalStateException(), "RESULT-SET NOT REGISTERED: {}", rs);
        return false;
      }
      return true;
    }
  }

  private static String buildStackTraceForRef(final Object ref) {
    final StringBuilder buffer = new StringBuilder(512);
    buffer.append(Thread.currentThread()).append(" - ").append(ref).append(System.lineSeparator());
    StackWalker.getInstance().forEach(s -> buffer.append("  ").append(s).append(System.lineSeparator()));
    return buffer.toString();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy