com.twitter.aurora.scheduler.async.AsyncModule.java Source code

Java tutorial

Introduction

Here is the source code for com.twitter.aurora.scheduler.async.AsyncModule.java

Source

/*
 * Copyright 2013 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.aurora.scheduler.async;

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.logging.Logger;

import javax.inject.Singleton;

import com.google.common.base.Optional;
import com.google.common.util.concurrent.RateLimiter;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.AbstractModule;
import com.google.inject.PrivateModule;
import com.google.inject.TypeLiteral;

import com.twitter.aurora.scheduler.async.OfferQueue.OfferQueueImpl;
import com.twitter.aurora.scheduler.async.OfferQueue.OfferReturnDelay;
import com.twitter.aurora.scheduler.async.RescheduleCalculator.RescheduleCalculatorImpl;
import com.twitter.aurora.scheduler.async.TaskGroups.SchedulingAction;
import com.twitter.aurora.scheduler.async.TaskGroups.TaskGroupsSettings;
import com.twitter.aurora.scheduler.events.PubsubEventModule;
import com.twitter.common.args.Arg;
import com.twitter.common.args.CmdLine;
import com.twitter.common.quantity.Amount;
import com.twitter.common.quantity.Time;
import com.twitter.common.stats.StatImpl;
import com.twitter.common.stats.Stats;
import com.twitter.common.stats.StatsProvider;
import com.twitter.common.util.Random;
import com.twitter.common.util.TruncatedBinaryBackoff;

import static com.twitter.aurora.scheduler.async.HistoryPruner.PruneThreshold;
import static com.twitter.aurora.scheduler.async.Preemptor.PreemptorImpl;
import static com.twitter.aurora.scheduler.async.Preemptor.PreemptorImpl.PreemptionDelay;

/**
 * Binding module for async task management.
 */
public class AsyncModule extends AbstractModule {

    private static final Logger LOG = Logger.getLogger(AsyncModule.class.getName());

    @CmdLine(name = "async_worker_threads", help = "The number of worker threads to process async task operations with.")
    private static final Arg<Integer> ASYNC_WORKER_THREADS = Arg.create(1);

    @CmdLine(name = "transient_task_state_timeout", help = "The amount of time after which to treat a task stuck in a transient state as LOST.")
    private static final Arg<Amount<Long, Time>> TRANSIENT_TASK_STATE_TIMEOUT = Arg
            .create(Amount.of(5L, Time.MINUTES));

    @CmdLine(name = "initial_schedule_delay", help = "Initial amount of time to wait before attempting to schedule a PENDING task.")
    private static final Arg<Amount<Long, Time>> INITIAL_SCHEDULE_DELAY = Arg.create(Amount.of(1L, Time.SECONDS));

    @CmdLine(name = "max_schedule_delay", help = "Maximum delay between attempts to schedule a PENDING tasks.")
    private static final Arg<Amount<Long, Time>> MAX_SCHEDULE_DELAY = Arg.create(Amount.of(30L, Time.SECONDS));

    @CmdLine(name = "min_offer_hold_time", help = "Minimum amount of time to hold a resource offer before declining.")
    private static final Arg<Amount<Integer, Time>> MIN_OFFER_HOLD_TIME = Arg.create(Amount.of(5, Time.MINUTES));

    @CmdLine(name = "history_prune_threshold", help = "Time after which the scheduler will prune terminated task history.")
    private static final Arg<Amount<Long, Time>> HISTORY_PRUNE_THRESHOLD = Arg.create(Amount.of(2L, Time.DAYS));

    @CmdLine(name = "max_schedule_attempts_per_sec", help = "Maximum number of scheduling attempts to make per second.")
    private static final Arg<Double> MAX_SCHEDULE_ATTEMPTS_PER_SEC = Arg.create(10D);

    @CmdLine(name = "flapping_task_threshold", help = "A task that repeatedly runs for less than this time is considered to be flapping.")
    private static final Arg<Amount<Long, Time>> FLAPPING_THRESHOLD = Arg.create(Amount.of(5L, Time.MINUTES));

    @CmdLine(name = "initial_flapping_task_delay", help = "Initial amount of time to wait before attempting to schedule a flapping task.")
    private static final Arg<Amount<Long, Time>> INITIAL_FLAPPING_DELAY = Arg.create(Amount.of(30L, Time.SECONDS));

    @CmdLine(name = "max_flapping_task_delay", help = "Maximum delay between attempts to schedule a flapping task.")
    private static final Arg<Amount<Long, Time>> MAX_FLAPPING_DELAY = Arg.create(Amount.of(5L, Time.MINUTES));

    @CmdLine(name = "max_reschedule_task_delay_on_startup", help = "Upper bound of random delay for pending task rescheduling on scheduler startup.")
    private static final Arg<Amount<Integer, Time>> MAX_RESCHEDULING_DELAY = Arg
            .create(Amount.of(30, Time.SECONDS));

    @CmdLine(name = "preemption_delay", help = "Time interval after which a pending task becomes eligible to preempt other tasks")
    private static final Arg<Amount<Long, Time>> PREEMPTION_DELAY = Arg.create(Amount.of(10L, Time.MINUTES));

    @CmdLine(name = "enable_preemptor", help = "Enable the preemptor and preemption")
    private static final Arg<Boolean> ENABLE_PREEMPTOR = Arg.create(true);

    private static final Preemptor NULL_PREEMPTOR = new Preemptor() {
        @Override
        public Optional<String> findPreemptionSlotFor(String taskId) {
            return Optional.absent();
        }
    };

    @Override
    protected void configure() {
        // Don't worry about clean shutdown, these can be daemon and cleanup-free.
        final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(ASYNC_WORKER_THREADS.get(),
                new ThreadFactoryBuilder().setNameFormat("AsyncProcessor-%d").setDaemon(true).build());
        Stats.exportSize("timeout_queue_size", executor.getQueue());
        Stats.export(new StatImpl<Long>("async_tasks_completed") {
            @Override
            public Long read() {
                return executor.getCompletedTaskCount();
            }
        });

        // AsyncModule itself is not a subclass of PrivateModule because TaskEventModule internally uses
        // a MultiBinder, which cannot span multiple injectors.
        binder().install(new PrivateModule() {
            @Override
            protected void configure() {
                bind(new TypeLiteral<Amount<Long, Time>>() {
                }).toInstance(TRANSIENT_TASK_STATE_TIMEOUT.get());
                bind(ScheduledExecutorService.class).toInstance(executor);

                bind(TaskTimeout.class).in(Singleton.class);
                requireBinding(StatsProvider.class);
                expose(TaskTimeout.class);
            }
        });
        PubsubEventModule.bindSubscriber(binder(), TaskTimeout.class);

        binder().install(new PrivateModule() {
            @Override
            protected void configure() {
                bind(TaskGroupsSettings.class).toInstance(new TaskGroupsSettings(
                        new TruncatedBinaryBackoff(INITIAL_SCHEDULE_DELAY.get(), MAX_SCHEDULE_DELAY.get()),
                        RateLimiter.create(MAX_SCHEDULE_ATTEMPTS_PER_SEC.get())));

                bind(RescheduleCalculatorImpl.RescheduleCalculatorSettings.class)
                        .toInstance(new RescheduleCalculatorImpl.RescheduleCalculatorSettings(
                                new TruncatedBinaryBackoff(INITIAL_FLAPPING_DELAY.get(), MAX_FLAPPING_DELAY.get()),
                                FLAPPING_THRESHOLD.get(), MAX_RESCHEDULING_DELAY.get()));

                bind(RescheduleCalculator.class).to(RescheduleCalculatorImpl.class).in(Singleton.class);
                bind(SchedulingAction.class).to(TaskScheduler.class);
                bind(TaskScheduler.class).in(Singleton.class);
                if (ENABLE_PREEMPTOR.get()) {
                    bind(Preemptor.class).to(PreemptorImpl.class);
                    bind(PreemptorImpl.class).in(Singleton.class);
                    LOG.info("Preemptor Enabled.");
                } else {
                    bind(Preemptor.class).toInstance(NULL_PREEMPTOR);
                    LOG.warning("Preemptor Disabled.");
                }
                bind(new TypeLiteral<Amount<Long, Time>>() {
                }).annotatedWith(PreemptionDelay.class).toInstance(PREEMPTION_DELAY.get());
                bind(TaskGroups.class).in(Singleton.class);
                expose(TaskGroups.class);
            }
        });
        PubsubEventModule.bindSubscriber(binder(), TaskGroups.class);

        binder().install(new PrivateModule() {
            @Override
            protected void configure() {
                bind(OfferReturnDelay.class).to(RandomJitterReturnDelay.class);
                bind(ScheduledExecutorService.class).toInstance(executor);
                bind(OfferQueue.class).to(OfferQueueImpl.class);
                bind(OfferQueueImpl.class).in(Singleton.class);
                expose(OfferQueue.class);
            }
        });
        PubsubEventModule.bindSubscriber(binder(), OfferQueue.class);

        binder().install(new PrivateModule() {
            @Override
            protected void configure() {
                // TODO(ksweeney): Create a configuration validator module so this can be injected.
                // TODO(William Farner): Revert this once large task counts is cheap ala hierarchichal store
                bind(Integer.class).annotatedWith(PruneThreshold.class).toInstance(100);
                bind(new TypeLiteral<Amount<Long, Time>>() {
                }).annotatedWith(PruneThreshold.class).toInstance(HISTORY_PRUNE_THRESHOLD.get());
                bind(ScheduledExecutorService.class).toInstance(executor);

                bind(HistoryPruner.class).in(Singleton.class);
                expose(HistoryPruner.class);
            }
        });
        PubsubEventModule.bindSubscriber(binder(), HistoryPruner.class);
    }

    /**
     * Returns offers after a random duration within a fixed window.
     */
    private static class RandomJitterReturnDelay implements OfferReturnDelay {
        private static final int JITTER_WINDOW_MS = Amount.of(1, Time.MINUTES).as(Time.MILLISECONDS);

        private final int minHoldTimeMs = MIN_OFFER_HOLD_TIME.get().as(Time.MILLISECONDS);
        private final Random random = new Random.SystemRandom(new java.util.Random());

        @Override
        public Amount<Integer, Time> get() {
            return Amount.of(minHoldTimeMs + random.nextInt(JITTER_WINDOW_MS), Time.MILLISECONDS);
        }
    }
}