Skip to content
This repository has been archived by the owner on Sep 18, 2021. It is now read-only.

Commit

Permalink
Merge branch 'master' into improved_stats
Browse files Browse the repository at this point in the history
Conflicts:
	project/build.properties
  • Loading branch information
freels committed Jun 28, 2011
2 parents b88a640 + ae05bd7 commit b1e60d9
Show file tree
Hide file tree
Showing 29 changed files with 647 additions and 111 deletions.
27 changes: 27 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,33 @@ Suppose you want to automatically disable all connections to a particular host a

val queryEvaluatorFactory = new AutoDisablingQueryEvaluatorFactory(new StandardQueryEvaluatorFactory(databaseFactory, queryFactory))

### Async API

Querulous also contains an async API based on
[`com.twitter.util.Future`](http://github.com/twitter/util). The trait
`AsyncQueryEvaluator` mirrors `QueryEvaluator` in terms of
functionality, the key difference being that methods immediately
return values wrapped in a `Future`. Internally, blocking JDBC calls
are executed within a thread pool.

// returns Future[Seq[User]]
val future = queryEvaluator.select("SELECT * FROM users WHERE id IN (?) OR name = ?", List(1,2,3), "Jacques") { row =>
new User(row.getInt("id"), row.getString("name"))
}

// Futures support a functional, monadic interface:
val tweetsFuture = future flatMap { users =>
queryEvaluator.select("SELECT * FROM tweets WHERE user_id IN (?)", users.map(_.id)) { row =>
new Tweet(row.getInt("id"), row.getString("text"))
}
}

// futures only block when unwrapped.
val tweets = tweetsFuture.apply()

See [the Future API reference](http://twitter.github.com/util/util-core/target/site/doc/main/api/com/twitter/util/Future.html)
for more information.

### Recommended Configuration Options

* Set minActive equal to maxActive. This ensures that the system is fully utilizing the connection resource even when the system is idle. This is good because you will not be surprised by connection usage (and e.g., unexpectedly hit server-side connection limits) during peak load.
Expand Down
4 changes: 2 additions & 2 deletions project/build.properties
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#Project properties
#Thu May 19 13:06:29 PDT 2011
#Mon Jun 06 13:52:29 PDT 2011
project.organization=com.twitter
project.name=querulous
sbt.version=0.7.4
project.version=2.1.6-stats-alpha1-SNAPSHOT
project.version=2.2.1-stats-alpha2-SNAPSHOT
build.scala.versions=2.8.1
project.initialize=false
29 changes: 15 additions & 14 deletions project/build/QuerulousProject.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,23 @@ import sbt._
import Process._
import com.twitter.sbt._

class QuerulousProject(info: ProjectInfo) extends StandardProject(info) with SubversionPublisher {
override def filterScalaJars = false
class QuerulousProject(info: ProjectInfo) extends StandardLibraryProject(info)
with DefaultRepos
with SubversionPublisher {

val util = "com.twitter" % "util" % "1.8.11"
val utilCore = "com.twitter" % "util-core" % "1.8.13"
val dbcp = "commons-dbcp" % "commons-dbcp" % "1.4"
val mysqljdbc = "mysql" % "mysql-connector-java" % "5.1.13"
val pool = "commons-pool" % "commons-pool" % "1.5.4"

val dbcp = "commons-dbcp" % "commons-dbcp" % "1.4"
val mysqljdbc = "mysql" % "mysql-connector-java" % "5.1.13"
val pool = "commons-pool" % "commons-pool" % "1.5.4"

val scalaTools = "org.scala-lang" % "scala-compiler" % "2.8.1" % "test"
val hamcrest = "org.hamcrest" % "hamcrest-all" % "1.1" % "test"
val specs = "org.scala-tools.testing" % "specs_2.8.0" % "1.6.5" % "test"
val objenesis = "org.objenesis" % "objenesis" % "1.1" % "test"
val jmock = "org.jmock" % "jmock" % "2.4.0" % "test"
val cglib = "cglib" % "cglib" % "2.2" % "test"
val asm = "asm" % "asm" % "1.5.3" % "test"
val utilEval = "com.twitter" % "util-eval" % "1.8.13" % "test"
val scalaTools = "org.scala-lang" % "scala-compiler" % "2.8.1" % "test"
val hamcrest = "org.hamcrest" % "hamcrest-all" % "1.1" % "test"
val specs = "org.scala-tools.testing" % "specs_2.8.0" % "1.6.5" % "test"
val objenesis = "org.objenesis" % "objenesis" % "1.1" % "test"
val jmock = "org.jmock" % "jmock" % "2.4.0" % "test"
val cglib = "cglib" % "cglib" % "2.2" % "test"
val asm = "asm" % "asm" % "1.5.3" % "test"

override def subversionRepository = Some("http://svn.local.twitter.com/maven-public/")
}
2 changes: 1 addition & 1 deletion project/plugins/Plugins.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import sbt._

class Plugins(info: ProjectInfo) extends PluginDefinition(info) {
val twitter = "twitter.com" at "http://maven.twttr.com/"
val defaultProject = "com.twitter" % "standard-project" % "0.7.17"
val standardProject = "com.twitter" % "standard-project" % "0.12.7"
}
4 changes: 4 additions & 0 deletions project/release.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#Automatically generated by ReleaseManagement
#Mon Jun 06 13:52:29 PDT 2011
version=2.2.0
sha1=c8773aac4ff6f4890d8b7dbb59fd68ea60d53118
22 changes: 22 additions & 0 deletions src/main/scala/com/twitter/querulous/DaemonThreadFactory.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.twitter.querulous

import java.util.concurrent.{ThreadFactory, TimeoutException => JTimeoutException, _}
import java.util.concurrent.atomic.AtomicInteger
import com.twitter.util.Duration


class DaemonThreadFactory extends ThreadFactory {
val group = new ThreadGroup(Thread.currentThread().getThreadGroup(), "querulous")
val threadNumber = new AtomicInteger(1)

def newThread(r: Runnable) = {
val thread = new Thread(group, r, "querulous-" + threadNumber.getAndIncrement())
if (!thread.isDaemon) {
thread.setDaemon(true)
}
if (thread.getPriority != Thread.NORM_PRIORITY) {
thread.setPriority(Thread.NORM_PRIORITY)
}
thread
}
}
27 changes: 9 additions & 18 deletions src/main/scala/com/twitter/querulous/FutureTimeout.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,16 @@ import java.util.concurrent.{ThreadFactory, TimeoutException => JTimeoutExceptio
import java.util.concurrent.atomic.AtomicInteger
import com.twitter.util.Duration

class FutureTimeout(poolSize: Int, queueSize: Int) {
object DaemonThreadFactory extends ThreadFactory {
val group = new ThreadGroup(Thread.currentThread().getThreadGroup(), "querulous")
val threadNumber = new AtomicInteger(1)

def newThread(r: Runnable) = {
val thread = new Thread(group, r, "querulous-" + threadNumber.getAndIncrement())
if (!thread.isDaemon) {
thread.setDaemon(true)
}
if (thread.getPriority != Thread.NORM_PRIORITY) {
thread.setPriority(Thread.NORM_PRIORITY)
}
thread
}
}
private val executor = new ThreadPoolExecutor(1, poolSize, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue[Runnable](queueSize),
DaemonThreadFactory)
class FutureTimeout(poolSize: Int, queueSize: Int) {
private val executor = new ThreadPoolExecutor(
1,
poolSize,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue[Runnable](queueSize),
new DaemonThreadFactory()
)

class Task[T](f: => T)(onTimeout: T => Unit) extends Callable[T] {
private var cancelled = false
Expand Down
27 changes: 27 additions & 0 deletions src/main/scala/com/twitter/querulous/async/AsyncDatabase.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.twitter.querulous.async

import java.sql.Connection
import com.twitter.util.Future


trait AsyncDatabaseFactory {
def apply(
hosts: List[String],
name: String,
username: String,
password: String,
urlOptions: Map[String, String]
): AsyncDatabase

def apply(hosts: List[String], name: String, username: String, password: String): AsyncDatabase = {
apply(hosts, name, username, password, Map.empty)
}

def apply(hosts: List[String], username: String, password: String): AsyncDatabase = {
apply(hosts, null, username, password, Map.empty)
}
}

trait AsyncDatabase {
def withConnection[R](f: Connection => R): Future[R]
}
112 changes: 112 additions & 0 deletions src/main/scala/com/twitter/querulous/async/AsyncQueryEvaluator.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.twitter.querulous.async

import java.util.concurrent.Executors
import java.sql.ResultSet
import com.twitter.util.{Future, FuturePool}
import com.twitter.querulous.config.{Connection => ConnectionConfig}
import com.twitter.querulous.DaemonThreadFactory
import com.twitter.querulous.evaluator._
import com.twitter.querulous.query.{QueryClass, SqlQueryFactory}
import com.twitter.querulous.database.ThrottledPoolingDatabaseFactory
import com.twitter.conversions.time._


object AsyncQueryEvaluator extends AsyncQueryEvaluatorFactory {
lazy val defaultFuturePool = FuturePool(Executors.newCachedThreadPool(new DaemonThreadFactory))

private def createEvaluatorFactory() = {
new StandardAsyncQueryEvaluatorFactory(
new BlockingDatabaseWrapperFactory(
defaultFuturePool,
new ThrottledPoolingDatabaseFactory(10, 100.millis, 10.seconds, 1.second)
),
new SqlQueryFactory
)
}

def apply(
dbhosts: List[String],
dbname: String,
username: String,
password: String,
urlOptions: Map[String, String]
): AsyncQueryEvaluator = {
createEvaluatorFactory()(dbhosts, dbname, username, password, urlOptions)
}
}

trait AsyncQueryEvaluatorFactory {
def apply(
dbhosts: List[String],
dbname: String,
username: String,
password: String,
urlOptions: Map[String, String]
): AsyncQueryEvaluator

def apply(dbhost: String, dbname: String, username: String, password: String, urlOptions: Map[String, String]): AsyncQueryEvaluator = {
apply(List(dbhost), dbname, username, password, urlOptions)
}

def apply(dbhosts: List[String], dbname: String, username: String, password: String): AsyncQueryEvaluator = {
apply(dbhosts, dbname, username, password, Map[String,String]())
}

def apply(dbhost: String, dbname: String, username: String, password: String): AsyncQueryEvaluator = {
apply(List(dbhost), dbname, username, password, Map[String,String]())
}

def apply(dbhost: String, username: String, password: String): AsyncQueryEvaluator = {
apply(List(dbhost), null, username, password, Map[String,String]())
}

def apply(dbhosts: List[String], username: String, password: String): AsyncQueryEvaluator = {
apply(dbhosts, null, username, password, Map[String,String]())
}

def apply(connection: ConnectionConfig): AsyncQueryEvaluator = {
apply(connection.hostnames.toList, connection.database, connection.username, connection.password, connection.urlOptions)
}
}

trait AsyncQueryEvaluator {
def select[A](queryClass: QueryClass, query: String, params: Any*)(f: ResultSet => A): Future[Seq[A]]

def select[A](query: String, params: Any*)(f: ResultSet => A): Future[Seq[A]] = {
select(QueryClass.Select, query, params: _*)(f)
}

def selectOne[A](queryClass: QueryClass, query: String, params: Any*)(f: ResultSet => A): Future[Option[A]]

def selectOne[A](query: String, params: Any*)(f: ResultSet => A): Future[Option[A]] = {
selectOne(QueryClass.Select, query, params: _*)(f)
}

def count(queryClass: QueryClass, query: String, params: Any*): Future[Int]

def count(query: String, params: Any*): Future[Int] = {
count(QueryClass.Select, query, params: _*)
}

def execute(queryClass: QueryClass, query: String, params: Any*): Future[Int]

def execute(query: String, params: Any*): Future[Int] = {
execute(QueryClass.Execute, query, params: _*)
}

def executeBatch(queryClass: QueryClass, query: String)(f: ParamsApplier => Unit): Future[Int]

def executeBatch(query: String)(f: ParamsApplier => Unit): Future[Int] = {
executeBatch(QueryClass.Execute, query)(f)
}

def nextId(tableName: String): Future[Long]

def insert(queryClass: QueryClass, query: String, params: Any*): Future[Long]

def insert(query: String, params: Any*): Future[Long] = {
insert(QueryClass.Execute, query, params: _*)
}

def transaction[T](f: Transaction => T): Future[T]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.twitter.querulous.async

import java.util.concurrent.Executors
import java.sql.Connection
import com.twitter.util.{Return, Throw, Future, Promise, FuturePool, JavaTimer, TimeoutException}
import com.twitter.querulous.DaemonThreadFactory
import com.twitter.querulous.database.{Database, DatabaseFactory}


class BlockingDatabaseWrapperFactory(pool: => FuturePool, factory: DatabaseFactory)
extends AsyncDatabaseFactory {
def apply(
hosts: List[String],
name: String,
username: String,
password: String,
urlOptions: Map[String, String]
): AsyncDatabase = {
new BlockingDatabaseWrapper(
pool,
factory(hosts, name, username, password, urlOptions)
)
}
}

private object AsyncConnectionCheckout {
lazy val checkoutTimer = new JavaTimer(true)
}

class BlockingDatabaseWrapper(
pool: FuturePool,
protected[async] val database: Database)
extends AsyncDatabase {

import AsyncConnectionCheckout._

// XXX: this probably should be configurable as well.
private val checkoutPool = FuturePool(Executors.newSingleThreadExecutor(new DaemonThreadFactory))
private val openTimeout = database.openTimeout

def withConnection[R](f: Connection => R) = {
checkoutConnection() flatMap { conn =>
pool {
try {
f(conn)
} finally {
database.close(conn)
}
}
}
}

private def checkoutConnection(): Future[Connection] = {
val promise = new Promise[Connection]

checkoutPool(database.open()) respond { rv =>
// if the promise has already timed out, we need to close the connection here.
if (!promise.updateIfEmpty(rv)) rv foreach database.close
}

checkoutTimer.schedule(openTimeout.fromNow) {
promise.updateIfEmpty(Throw(new TimeoutException(openTimeout.toString)))
}

promise
}

// equality overrides

override def equals(other: Any) = other match {
case other: BlockingDatabaseWrapper => database eq other.database
case _ => false
}

override def hashCode = database.hashCode
}
Loading

0 comments on commit b1e60d9

Please sign in to comment.