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

com.twitter.zipkin.collector.ItemQueue.scala Maven / Gradle / Ivy

/*
 * Copyright 2012 Twitter Inc.
 *
 * 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 com.twitter.zipkin.collector

import com.twitter.concurrent.NamedPoolThreadFactory
import com.twitter.finagle.stats.{StatsReceiver, DefaultStatsReceiver, Stat}
import com.twitter.util._
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.{ArrayBlockingQueue, Executors, TimeUnit}

class QueueFullException(size: Int) extends Exception("Queue is full. MaxSize: %d".format(size))
class QueueClosedException extends Exception("Queue is closed")

/**
 * A queue with configurable size and concurrency characteristics.
 *
 * This queue is backed by ArrayBlockingQueue of `maxSize`. Items are processed by a number of workers
 * limited by `maxConcurrency`. Each worker will pull an item from the queue and process it via the
 * provided `process` function. Workers will Await on `process` thus backpressure is based on the workers'
 * ability to fully process an item.
 *
 * On close the queue will drain itelf before completing the close Future.
 *
 * The queue can be awaited on and will not complete until it's been closed and drained.
 */
class ItemQueue[A, B](
  maxSize: Int,
  maxConcurrency: Int,
  process: A => Future[B],
  timeout: Duration = Duration.Top,
  stats: StatsReceiver = DefaultStatsReceiver.scope("ItemQueue")
) extends Closable with CloseAwaitably {

  @volatile protected[this] var running: Boolean = true
  protected[this] val queue = new ArrayBlockingQueue[A](maxSize)
  private[this] val queueSizeGauge = stats.addGauge("queueSize") { queue.size }
  protected[this] val queueFullCounter = stats.counter("queueFull")
  private[this] val activeWorkers = new AtomicInteger(0)
  private[this] val activeWorkerGauge = stats.addGauge("activeWorkers") { activeWorkers.get }
  private[this] val maxConcurrencyGauge = stats.addGauge("maxConcurrency") { maxConcurrency }
  private[this] val failuresCounter = stats.counter("failures")
  private[this] val successesCounter = stats.counter("successes")

  private[this] val futurePool = new ExecutorServiceFuturePool(Executors.newCachedThreadPool(
    new NamedPoolThreadFactory("ItemQueuePool", makeDaemons = true)))
  private[this] val workers = Seq.fill(maxConcurrency) { futurePool(loop()) }

  private[this] def loop() {
    while (running || !queue.isEmpty) {
      val item = queue.poll(500, TimeUnit.MILLISECONDS)
      if (item != null) {
        activeWorkers.incrementAndGet()
        Try(Await.result(Stat.timeFuture(stats.stat("processing_time_ms"))(process(item)), timeout))
          .onSuccess(_ => successesCounter.incr())
          .onFailure(_ => failuresCounter.incr())
        activeWorkers.decrementAndGet()
      }
    }
  }

  def size(): Int = {
    queue.size()
  }

  def close(deadline: Time): Future[Unit] = closeAwaitably {
    running = false
    Future.join(workers)
  }

  protected[this] val QueueFull = Future.exception(new QueueFullException(maxSize))
  protected[this] val QueueClosed = Future.exception(new QueueClosedException)

  def add(item: A): Future[Unit] =
    if (!running) {
      QueueClosed
    } else if (!queue.offer(item)) {
      queueFullCounter.incr()
      QueueFull
    } else {
      Future.Done
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy