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

com.metamx.tranquility.finagle.DruidTaskResolver.scala Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to Metamarkets Group Inc. (Metamarkets) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  Metamarkets 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 com.metamx.tranquility.finagle

import com.github.nscala_time.time.Imports._
import com.google.common.util.concurrent.ThreadFactoryBuilder
import com.metamx.common.scala.Logging
import com.metamx.common.scala.timekeeper.Timekeeper
import com.metamx.tranquility.druid.IndexService
import com.twitter.finagle.Addr
import com.twitter.finagle.Resolver
import com.twitter.util._
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.atomic.AtomicReference
import scala.util.Random
import scala.util.control.NonFatal

class DruidTaskResolver(
  indexService: IndexService,
  timekeeper: Timekeeper,
  pollPeriod: Period
) extends Resolver with Logging
{
  private val pollExecutor = Executors.newSingleThreadExecutor(
    new ThreadFactoryBuilder().setNameFormat(s"DruidTaskResolver[$indexService]").setDaemon(true).build()
  )

  private val started      = new AtomicBoolean
  private val taskNumberer = new AtomicLong

  private val lock     = new AnyRef
  private var nextPoll = 0L
  private var tasks    = Map[Long, (String, AtomicReference[Addr], Updatable[Addr])]()

  private def ensureStarted() {
    if (started.compareAndSet(false, true)) {
      log.info(s"Starting poll thread for indexService[$indexService], pollPeriod[$pollPeriod].")
      pollExecutor.submit(
        new Runnable
        {
          override def run(): Unit = {
            while (!Thread.currentThread().isInterrupted) {
              try {
                val tasksSnapshot: Map[Long, (String, AtomicReference[Addr], Updatable[Addr])] = lock.synchronized {
                  while (timekeeper.now.getMillis < nextPoll) {
                    val waitMillis = nextPoll - timekeeper.now.getMillis
                    if (waitMillis > 0) {
                      lock.wait(waitMillis)
                    }
                  }
                  tasks
                }

                val now = timekeeper.now
                val baseWait = now.plus(pollPeriod).getMillis - now.getMillis
                val fuzzyWait = (math.max(1 + 0.25 * Random.nextGaussian, 0) * baseWait).toLong
                nextPoll = now.getMillis + fuzzyWait

                val running = Await.result(indexService.runningTasks())

                log.debug(s"Polled[$indexServiceKey] and found ${running.size} tasks " +
                  s"(${running.count(_._2.isBound)} bound).")

                for ((taskNumber, (taskId, addr, updatable)) <- tasksSnapshot) {
                  val runningHostPort = running.get(taskId)
                  val runningAddr: Addr = runningHostPort.map(_.toAddr).getOrElse(Addr.Neg)
                  if (addr.get() != runningAddr) {
                    log.info(s"Updating location for task[$taskId] (#$taskNumber) to[$runningAddr].")
                    addr.set(runningAddr)
                    updatable.update(runningAddr)
                  }
                }
              }
              catch {
                case NonFatal(e) =>
                  log.warn(e, s"Poll failed, trying again at[${new DateTime(nextPoll)}].")
              }
            }
          }
        }
      )
    }
  }

  override val scheme = DruidTaskResolver.mkscheme(indexService.key)

  override def bind(taskId: String) = {
    val taskNumber = taskNumberer.incrementAndGet()

    Var.async[Addr](Addr.Pending) {
      updatable =>
        log.info(s"Started monitoring task[$taskId] (#$taskNumber).")

        lock.synchronized {
          nextPoll = 0L
          tasks = tasks + (taskNumber ->(taskId, new AtomicReference[Addr], updatable))
          lock.notifyAll()
        }

        ensureStarted()
        new Closable
        {
          override def close(deadline: Time): Future[Unit] = {
            lock.synchronized {
              log.info(s"Stopped monitoring task[$taskId] (#$taskNumber).")
              tasks = tasks - taskNumber
              Future.Done
            }
          }
        }
    }
  }

  def indexServiceKey: String = indexService.key
}

object DruidTaskResolver
{
  def mkscheme(indexServiceKey: String) = s"druidTask!$indexServiceKey"
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy