Package com.splout.db.common

Source Code of com.splout.db.common.TimeoutThread$QueryAndTime

package com.splout.db.common;

/*
* #%L
* Splout SQL commons
* %%
* Copyright (C) 2012 Datasalt Systems S.L.
* %%
* 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.
* #L%
*/

import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.almworks.sqlite4java.SQLiteConnection;
import com.almworks.sqlite4java.SQLiteException;

/**
* A Thread that is in charge of killing long-running queries.
* TODO Generalize to other than sqlite
*/
public class TimeoutThread extends Thread {

  private final static Log log = LogFactory.getLog(TimeoutThread.class);

  private ConcurrentHashMap<SQLiteConnection, QueryAndTime> currentQueries = new ConcurrentHashMap<SQLiteConnection, QueryAndTime>();
  private CopyOnWriteArrayList<SQLiteConnection> connections = new CopyOnWriteArrayList<SQLiteConnection>();
 
  private long timeout;

  static class QueryAndTime {
    String query;
    Long time;

    public QueryAndTime(String query, Long time) {
      this.query = query;
      this.time = time;
    }
  }

  /**
   * @param timeout
   *          The timeout in milliseconds. If a SQLite connection monitored by this Thread has a query that runs for
   *          more than this, it will be interrupted and the query will return an error.
   */
  public TimeoutThread(long timeout) {
    this.timeout = timeout;
  }

  @Override
  public void run() {
    log.info("Starting Timeout Thread...");
    try {
      while(true) {
        long now = System.currentTimeMillis();
        Iterator<SQLiteConnection> it = connections.iterator();
        while(it.hasNext()) {
          SQLiteConnection conn = it.next();
          QueryAndTime queryAndTime = currentQueries.get(conn);
          long time = queryAndTime.time;
          if(time < 0) { // means this connection is not active
            continue;
          }
          synchronized(conn) {
            if((now - time) > timeout) {
              // Timeout: we should interrupt this connection!

              try {

                /*
                 * Even though SQLiteConnections are not thread-safe, this method *IS* thread-safe and that's why we can
                 * implement this thread. The thread that launched the query is busy waiting for the result so another
                 * thread must interrupt it!
                 *
                 * SQLite4Java docs:
                 * http://almworks.com/sqlite4java/javadoc/com/almworks/sqlite4java/SQLiteConnection.html#interrupt()
                 * SQLite docs: http://www.sqlite.org/c3ref/interrupt.html
                 */
                log.info("Long running query [" + queryAndTime.query + "] ran for more than ["
                    + timeout + "] ms. Interrupting it!");
                conn.interrupt();
              } catch(SQLiteException e) {
                //
              } finally {
                queryAndTime.query = null;
                queryAndTime.time = -1l; // connection will not be considered until a new query is executed
              }
            }
          }
        }
        long andNow = System.currentTimeMillis();
        if((andNow - now) < 1000) {
          Thread.sleep(1000 - (andNow - now));
        }
      }
    } catch(InterruptedException e) {
      log.info("Interrupted!");
    }
  }

  /**
   * A Thread provides its thread-local connection to be monitored when a query starts. The SQL query is provided just
   * for logging purposes.
   */
  public void startQuery(SQLiteConnection connection, String query) {
    synchronized(connection) {
      if(!connections.contains(connection)) {
        connections.add(connection);
      }
      QueryAndTime qAndTime = currentQueries.get(connection);
      if(qAndTime == null) {
        qAndTime = new QueryAndTime(query, System.currentTimeMillis());
        currentQueries.put(connection, qAndTime);
      } else {
        qAndTime.query = query;
        qAndTime.time = System.currentTimeMillis();
      }
    }
  }

  /**
   * The same Thread that provided this connection in startQuery() removes it from the monitoring list. So it is
   * important to understand that this class makes the assumption that each SQLite Thread will use only one connection
   * (Thread-local). {@link SQLite4JavaManager} behaves like this.
   */
  public void endQuery(SQLiteConnection connection) {
    synchronized(connection) {
      QueryAndTime qAndTime = currentQueries.get(connection);
      if(qAndTime == null) {
        return;
      } else {
        qAndTime.query = null;
        qAndTime.time = -1l; // connection will not be considered until a new query is executed
      }
    }
  }

  // --- package-access getters for unit testing --- //
 
  ConcurrentHashMap<SQLiteConnection, QueryAndTime> getCurrentQueries() {
    return currentQueries;
  }
  CopyOnWriteArrayList<SQLiteConnection> getConnections() {
    return connections;
  }
}
TOP

Related Classes of com.splout.db.common.TimeoutThread$QueryAndTime

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.