com.crossbusiness.resiliency.aspect.spring.AnnotationTimeoutAspect.java Source code

Java tutorial

Introduction

Here is the source code for com.crossbusiness.resiliency.aspect.spring.AnnotationTimeoutAspect.java

Source

/**
 * The MIT License (MIT)
 *
 * Copyright (c)  2014 CrossBusiness, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.crossbusiness.resiliency.aspect.spring;

import com.crossbusiness.resiliency.annotation.Timeout;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareError;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Set;
import java.util.concurrent.*;

/**
 * Created by Sumanth Chinthagunta <xmlking@gmail.com> on 3/12/14.
 */
@Component
@Order(115)
@Aspect
public class AnnotationTimeoutAspect {
    static final Logger log = LoggerFactory.getLogger(AnnotationTimeoutAspect.class);

    /**
     * Calls being watched.
     */
    private final transient Set<AnnotationTimeoutAspect.Call> calls = new ConcurrentSkipListSet<AnnotationTimeoutAspect.Call>();

    /**
     * Service that interrupts threads.
     */
    private final transient ScheduledExecutorService interrupter = Executors
            .newSingleThreadScheduledExecutor(threadFactory());

    private ThreadFactory threadFactory() {
        CustomizableThreadFactory tf = new CustomizableThreadFactory("sumo-timeout-");
        tf.setThreadPriority(Thread.MAX_PRIORITY);
        tf.setDaemon(true);
        tf.setThreadGroupName("resiliency");
        return tf;
    }

    /**
     * Public ctor.
     */
    public AnnotationTimeoutAspect() {
        this.interrupter.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                AnnotationTimeoutAspect.this.interrupt();
            }
        }, 1, 1, TimeUnit.SECONDS //Time resolution is one second
        );
    }

    @Around("timeoutAnnotatedMethod(timeoutConfig)")
    public Object timeoutOnMethodLevel(final ProceedingJoinPoint point, Timeout timeoutConfig) throws Throwable {
        return doTimeout(point, timeoutConfig);
    }

    @Around("timeoutAnnotatedClass(timeoutConfig)")
    public Object timeoutOnClassLevel(final ProceedingJoinPoint point, Timeout timeoutConfig) throws Throwable {
        return doTimeout(point, timeoutConfig);
    }

    // @Around("timeoutMethodExecution(timeoutConfig)")
    public Object doTimeout(final ProceedingJoinPoint point, Timeout timeoutConfig) throws Throwable {
        log.debug(point + " -> " + timeoutConfig);
        final AnnotationTimeoutAspect.Call call = new AnnotationTimeoutAspect.Call(point, timeoutConfig);
        this.calls.add(call);
        Object output;
        try {
            output = point.proceed();
        } finally {
            this.calls.remove(call);
        }
        return output;
    }

    /**
     * Interrupt threads when needed.
     */
    private void interrupt() {
        synchronized (this.interrupter) {
            for (AnnotationTimeoutAspect.Call call : this.calls) {
                if (call.expired() && call.interrupted()) {
                    this.calls.remove(call);
                }
            }
        }
    }

    /**
     * A call being watched.
     */
    private static final class Call implements Comparable<AnnotationTimeoutAspect.Call> {
        /**
         * The thread called.
         */
        private final transient Thread thread = Thread.currentThread();
        /**
         * When started.
         */
        private final transient long start = System.currentTimeMillis();
        /**
         * When will expire.
         */
        private final transient long deadline;
        /**
         * Join point.
         */
        private final transient ProceedingJoinPoint point;

        /**
         * Public ctor.
         * @param pnt Joint point
         */
        public Call(final ProceedingJoinPoint pnt, Timeout timeoutConfig) {
            this.point = pnt;
            this.deadline = this.start + timeoutConfig.unit().toMillis(timeoutConfig.value());
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int compareTo(final Call obj) {
            int compare;
            if (this.deadline > obj.deadline) {
                compare = 1;
            } else if (this.deadline < obj.deadline) {
                compare = -1;
            } else {
                compare = 0;
            }
            return compare;
        }

        /**
         * Is it expired already?
         * @return TRUE if expired
         */
        public boolean expired() {
            return this.deadline < System.currentTimeMillis();
        }

        /**
         * This thread is stopped already (interrupt if not)?
         * @return TRUE if it's already dead
         */
        public boolean interrupted() {
            boolean dead;
            if (this.thread.isAlive()) {
                this.thread.interrupt();
                final Method method = MethodSignature.class.cast(this.point.getSignature()).getMethod();
                log.warn("{}: interrupted on {}ms timeout (over {}ms)", new Object[] { method,
                        System.currentTimeMillis() - this.start, this.deadline - this.start });
                dead = false;
            } else {
                dead = true;
            }
            return dead;
        }
    }

    /**
     * Matches the execution of any method with the @{@link com.crossbusiness.resiliency.annotation.Timeout} annotation.
     */
    @Pointcut("execution(@com.crossbusiness.resiliency.annotation.Timeout * *(..)) && @annotation(timeoutConfig)")
    public void timeoutAnnotatedMethod(Timeout timeoutConfig) {
    }

    /**
     * Matches the execution of any public method in a type with the @{@link com.crossbusiness.resiliency.annotation.Timeout}
     * annotation, or any subtype of a type with the {@code Timeout} annotation.
     */
    @Pointcut("execution(public * ((@com.crossbusiness.resiliency.annotation.Timeout *)+).*(..)) "
            + "&& within(@com.crossbusiness.resiliency.annotation.Timeout *) && @target(timeoutConfig) "
            + "&& !com.crossbusiness.resiliency.aspect.SystemArchitecture.groovyMOPMethods()")
    public void timeoutAnnotatedClass(Timeout timeoutConfig) {
    }

    /**
     * Definition of pointcut from super aspect - matched join points
     * will have Timeout Aspect applied.
     */
    @Pointcut("(timeoutAnnotatedMethod(timeoutConfig) || timeoutAnnotatedClass(timeoutConfig))")
    public void timeoutMethodExecution(Timeout timeoutConfig) {
    }

    @DeclareError("execution(@com.crossbusiness.resiliency.annotation.Timeout  * *(..) throws !java.lang.InterruptedException)")
    static final String anError = "Only methods that are declared with throws InterruptedException may have an @Timeout annotation";
}