/* * Copyright 2016-2017 the original author or authors. * * 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 org.glowroot.agent.plugin.cassandra; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.glowroot.agent.plugin.api.AsyncQueryEntry; import org.glowroot.agent.plugin.api.ThreadContext; import org.glowroot.agent.plugin.api.Timer; import org.glowroot.agent.plugin.api.weaving.BindParameter; import org.glowroot.agent.plugin.api.weaving.BindReceiver; import org.glowroot.agent.plugin.api.weaving.BindReturn; import org.glowroot.agent.plugin.api.weaving.BindTraveler; import org.glowroot.agent.plugin.api.weaving.IsEnabled; import org.glowroot.agent.plugin.api.weaving.Mixin; import org.glowroot.agent.plugin.api.weaving.OnAfter; import org.glowroot.agent.plugin.api.weaving.OnBefore; import org.glowroot.agent.plugin.api.weaving.OnReturn; import org.glowroot.agent.plugin.api.weaving.Pointcut; import org.glowroot.agent.plugin.cassandra.ResultSetAspect.ResultSet; public class ResultSetFutureAspect { // the field and method names are verbose to avoid conflict since they will become fields // and methods in all classes that extend com.datastax.driver.core.ResultSetFuture @Mixin("com.datastax.driver.core.ResultSetFuture") public static class ResultSetFutureImpl implements ResultSetFutureMixin { private volatile boolean glowroot$completed; private volatile @Nullable Throwable glowroot$exception; private volatile @Nullable AsyncQueryEntry glowroot$asyncQueryEntry; @Override public void glowroot$setCompleted() { glowroot$completed = true; } @Override public boolean glowroot$isCompleted() { return glowroot$completed; } @Override public void glowroot$setException(Throwable exception) { glowroot$exception = exception; } @Override public @Nullable Throwable glowroot$getException() { return glowroot$exception; } @Override public @Nullable AsyncQueryEntry glowroot$getAsyncQueryEntry() { return glowroot$asyncQueryEntry; } @Override public void glowroot$setAsyncQueryEntry(@Nullable AsyncQueryEntry asyncQueryEntry) { this.glowroot$asyncQueryEntry = asyncQueryEntry; } } // the method names are verbose to avoid conflict since they will become methods in all classes // that extend com.datastax.driver.core.ResultSetFuture public interface ResultSetFutureMixin { void glowroot$setCompleted(); boolean glowroot$isCompleted(); void glowroot$setException(Throwable t); @Nullable Throwable glowroot$getException(); @Nullable AsyncQueryEntry glowroot$getAsyncQueryEntry(); void glowroot$setAsyncQueryEntry(@Nullable AsyncQueryEntry asyncQueryEntry); } @Pointcut(className = "java.util.concurrent.Future", subTypeRestriction = "com.datastax.driver.core.ResultSetFuture", methodName = "get", methodParameterTypes = {".."}, suppressionKey = "wait-on-future") public static class FutureGetAdvice { @IsEnabled public static boolean isEnabled(@BindReceiver ResultSetFutureMixin resultSetFuture) { return resultSetFuture.glowroot$getAsyncQueryEntry() != null; } @OnBefore public static Timer onBefore(ThreadContext threadContext, @BindReceiver ResultSetFutureMixin resultSetFuture) { @SuppressWarnings("nullness") // just checked above in isEnabled() @Nonnull AsyncQueryEntry asyncQueryEntry = resultSetFuture.glowroot$getAsyncQueryEntry(); return asyncQueryEntry.extendSyncTimer(threadContext); } @OnReturn public static void onReturn(@BindReturn @Nullable ResultSet resultSet, @BindReceiver ResultSetFutureMixin resultSetFuture) { if (resultSet == null) { return; } // pass query entry to the result set so it can be used when iterating the result set AsyncQueryEntry asyncQueryEntry = resultSetFuture.glowroot$getAsyncQueryEntry(); resultSet.glowroot$setLastQueryEntry(asyncQueryEntry); } @OnAfter public static void onAfter(@BindTraveler Timer timer) { timer.stop(); } } // waiting on async result @Pointcut(className = "com.datastax.driver.core.ResultSetFuture", methodName = "getUninterruptibly", methodParameterTypes = {".."}) public static class FutureGetUninterruptiblyAdvice { @IsEnabled public static boolean isEnabled(@BindReceiver ResultSetFutureMixin resultSetFuture) { return FutureGetAdvice.isEnabled(resultSetFuture); } @OnBefore public static Timer onBefore(ThreadContext threadContext, @BindReceiver ResultSetFutureMixin resultSetFuture) { return FutureGetAdvice.onBefore(threadContext, resultSetFuture); } @OnReturn public static void onReturn(@BindReturn @Nullable ResultSet resultSet, @BindReceiver ResultSetFutureMixin resultSetFuture) { FutureGetAdvice.onReturn(resultSet, resultSetFuture); } @OnAfter public static void onAfter(@BindTraveler Timer timer) { FutureGetAdvice.onAfter(timer); } } @Pointcut(className = "com.google.common.util.concurrent.AbstractFuture", subTypeRestriction = "com.datastax.driver.core.DefaultResultSetFuture", methodName = "setException", methodParameterTypes = {"java.lang.Throwable"}) public static class FutureSetExceptionAdvice { // using @OnBefore instead of @OnReturn to ensure that async trace entry is ended prior to // an overall transaction that may be waiting on this future has a chance to end @OnBefore public static void onBefore(@BindReceiver ResultSetFutureMixin resultSetFuture, @BindParameter @Nullable Throwable t) { if (t == null) { return; } // to prevent race condition, setting completed/exception status before getting async // query entry, and the converse is done when setting async query entry // ok if end() happens to get called twice resultSetFuture.glowroot$setCompleted(); resultSetFuture.glowroot$setException(t); AsyncQueryEntry asyncQueryEntry = resultSetFuture.glowroot$getAsyncQueryEntry(); if (asyncQueryEntry != null) { asyncQueryEntry.endWithError(t); } } } @Pointcut(className = "com.google.common.util.concurrent.AbstractFuture", subTypeRestriction = "com.datastax.driver.core.DefaultResultSetFuture", methodName = "set", methodParameterTypes = {"java.lang.Object"}) public static class FutureSetAdvice { // using @OnBefore instead of @OnReturn to ensure that async trace entry is ended prior to // an overall transaction that may be waiting on this future has a chance to end @OnBefore public static void onBefore(@BindReceiver ResultSetFutureMixin resultSetFuture) { // to prevent race condition, setting completed status before getting async query entry, // and the converse is done when setting async query entry // ok if end() happens to get called twice resultSetFuture.glowroot$setCompleted(); AsyncQueryEntry asyncQueryEntry = resultSetFuture.glowroot$getAsyncQueryEntry(); if (asyncQueryEntry != null) { asyncQueryEntry.end(); } } } }