diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java index 3dabbac45c2d..e84f4689e82f 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionPublishInstrumentation.java @@ -21,7 +21,10 @@ import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import javax.annotation.Nullable; import net.bytebuddy.asm.Advice; +import net.bytebuddy.asm.Advice.AssignReturned; +import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -118,41 +121,54 @@ public static boolean onEnter( @SuppressWarnings("unused") public static class PublishReplyToHeadersBodyAdvice { + public static class AdviceScope { + private final NatsRequest request; + private final Context context; + private final Scope scope; + + private AdviceScope(NatsRequest request, Context context, Scope scope) { + this.request = request; + this.context = context; + this.scope = scope; + } + + @Nullable + public static AdviceScope start(NatsRequest natsRequest) { + Context parentContext = Context.current(); + if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { + return null; + } + Context context = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); + return new AdviceScope(natsRequest, context, context.makeCurrent()); + } + + public void end(@Nullable Throwable throwable) { + scope.close(); + PRODUCER_INSTRUMENTER.end(context, request, null, throwable); + } + } + + @AssignReturned.ToArguments(@ToArgument(value = 2, index = 1)) @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( + public static Object[] onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, @Advice.Argument(1) String replyTo, - @Advice.Argument(value = 2, readOnly = false) Headers headers, - @Advice.Argument(3) byte[] body, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - headers = NatsMessageWritableHeaders.create(headers); - - Context parentContext = Context.current(); - natsRequest = NatsRequest.create(connection, subject, replyTo, headers, body); - - if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { - return; - } - - otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); - otelScope = otelContext.makeCurrent(); + @Advice.Argument(2) Headers originalHeaders, + @Advice.Argument(3) byte[] body) { + Headers headers = NatsMessageWritableHeaders.create(originalHeaders); + NatsRequest natsRequest = NatsRequest.create(connection, subject, replyTo, headers, body); + AdviceScope adviceScope = AdviceScope.start(natsRequest); + return new Object[] {adviceScope, headers}; } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void onExit( - @Advice.Thrown Throwable throwable, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; + @Advice.Thrown @Nullable Throwable throwable, @Advice.Enter Object[] enterResult) { + AdviceScope adviceScope = (AdviceScope) enterResult[0]; + if (adviceScope != null) { + adviceScope.end(throwable); } - - otelScope.close(); - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); } } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java index bf4eaf2e2355..360c9267b778 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/ConnectionRequestInstrumentation.java @@ -24,7 +24,10 @@ import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import java.time.Duration; import java.util.concurrent.CompletableFuture; +import javax.annotation.Nullable; import net.bytebuddy.asm.Advice; +import net.bytebuddy.asm.Advice.AssignReturned; +import net.bytebuddy.asm.Advice.AssignReturned.ToArguments.ToArgument; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -118,6 +121,47 @@ public void transform(TypeTransformer transformer) { ConnectionRequestInstrumentation.class.getName() + "$RequestTimeoutFutureMessageAdvice"); } + public static class MessageFutureAdviceScope { + private final NatsRequest request; + private final Context context; + private final Context parentContext; + private final Scope scope; + + private MessageFutureAdviceScope( + NatsRequest request, Context parentContext, Context context, Scope scope) { + this.request = request; + this.parentContext = parentContext; + this.context = context; + this.scope = scope; + } + + @Nullable + public static MessageFutureAdviceScope start(NatsRequest request) { + Context parentContext = Context.current(); + if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, request)) { + return null; + } + Context context = PRODUCER_INSTRUMENTER.start(parentContext, request); + return new MessageFutureAdviceScope(request, parentContext, context, context.makeCurrent()); + } + + public CompletableFuture end( + Connection connection, + @Nullable CompletableFuture messageFuture, + @Nullable Throwable throwable) { + scope.close(); + if (throwable != null || messageFuture == null) { + PRODUCER_INSTRUMENTER.end(context, request, null, throwable); + return messageFuture; + } + + messageFuture = + messageFuture.whenComplete( + new SpanFinisher(PRODUCER_INSTRUMENTER, context, connection, request)); + return CompletableFutureWrapper.wrap(messageFuture, parentContext); + } + } + @SuppressWarnings("unused") public static class RequestBodyAdvice { @@ -132,56 +176,74 @@ public static Message onEnter( return connection.request(subject, null, body, timeout); } + @AssignReturned.ToReturned @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.Return(readOnly = false) Message result, @Advice.Enter Message message) { - result = message; + public static Message onExit(@Advice.Enter Message message) { + return message; } } @SuppressWarnings("unused") public static class RequestHeadersBodyAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( - @Advice.This Connection connection, - @Advice.Argument(0) String subject, - @Advice.Argument(value = 1, readOnly = false) Headers headers, - @Advice.Argument(2) byte[] body, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - headers = NatsMessageWritableHeaders.create(headers); - natsRequest = NatsRequest.create(connection, subject, null, headers, body); - Context parentContext = Context.current(); + public static class AdviceScope { + private final NatsRequest request; + private final Context context; + private final Scope scope; - if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { - return; + private AdviceScope(NatsRequest request, Context context, Scope scope) { + this.request = request; + this.context = context; + this.scope = scope; } - otelContext = PRODUCER_INSTRUMENTER.start(parentContext, natsRequest); - otelScope = otelContext.makeCurrent(); + @Nullable + public static AdviceScope start(NatsRequest request) { + Context parentContext = Context.current(); + if (!PRODUCER_INSTRUMENTER.shouldStart(parentContext, request)) { + return null; + } + Context context = PRODUCER_INSTRUMENTER.start(parentContext, request); + return new AdviceScope(request, context, context.makeCurrent()); + } + + public void end( + Connection connection, @Nullable Message message, @Nullable Throwable throwable) { + scope.close(); + + NatsRequest response = null; + if (message != null) { + response = NatsRequest.create(connection, message); + } + + scope.close(); + PRODUCER_INSTRUMENTER.end(context, request, response, throwable); + } + } + + @AssignReturned.ToArguments(@ToArgument(value = 1, index = 1)) + @Advice.OnMethodEnter(suppress = Throwable.class) + public static Object[] onEnter( + @Advice.This Connection connection, + @Advice.Argument(0) String subject, + @Advice.Argument(1) Headers originalHeaders, + @Advice.Argument(2) byte[] body) { + Headers headers = NatsMessageWritableHeaders.create(originalHeaders); + NatsRequest request = NatsRequest.create(connection, subject, null, headers, body); + AdviceScope adviceScope = AdviceScope.start(request); + return new Object[] {adviceScope, headers}; } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void onExit( @Advice.This Connection connection, - @Advice.Thrown Throwable throwable, - @Advice.Return Message message, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; - } - - NatsRequest natsResponse = null; - if (message != null) { - natsResponse = NatsRequest.create(connection, message); + @Advice.Thrown @Nullable Throwable throwable, + @Advice.Return @Nullable Message message, + @Advice.Enter Object[] enterResult) { + AdviceScope adviceScope = (AdviceScope) enterResult[0]; + if (adviceScope != null) { + adviceScope.end(connection, message, throwable); } - - otelScope.close(); - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, natsResponse, throwable); } } @@ -203,10 +265,10 @@ public static Message onEnter( request.getSubject(), request.getHeaders(), request.getData(), timeout); } + @AssignReturned.ToReturned @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.Return(readOnly = false) Message result, @Advice.Enter Message response) { - result = response; + public static Message onExit(@Advice.Enter Message response) { + return response; } } @@ -222,61 +284,42 @@ public static CompletableFuture onEnter( return connection.request(subject, null, body); } + @AssignReturned.ToReturned @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.Return(readOnly = false) CompletableFuture result, + public static CompletableFuture onExit( @Advice.Enter CompletableFuture future) { - result = future; + return future; } } @SuppressWarnings("unused") public static class RequestFutureHeadersBodyAdvice { + @AssignReturned.ToArguments(@ToArgument(value = 1, index = 1)) @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( + public static Object[] onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(value = 1, readOnly = false) Headers headers, - @Advice.Argument(2) byte[] body, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelParentContext") Context otelParentContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - headers = NatsMessageWritableHeaders.create(headers); - natsRequest = NatsRequest.create(connection, subject, null, headers, body); - otelParentContext = Context.current(); - - if (!PRODUCER_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { - return; - } - - otelContext = PRODUCER_INSTRUMENTER.start(otelParentContext, natsRequest); - otelScope = otelContext.makeCurrent(); + @Advice.Argument(1) Headers originalHeaders, + @Advice.Argument(2) byte[] body) { + Headers headers = NatsMessageWritableHeaders.create(originalHeaders); + NatsRequest request = NatsRequest.create(connection, subject, null, headers, body); + MessageFutureAdviceScope adviceScope = MessageFutureAdviceScope.start(request); + return new Object[] {adviceScope, headers}; } + @AssignReturned.ToReturned @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( + public static CompletableFuture onExit( @Advice.This Connection connection, @Advice.Thrown Throwable throwable, - @Advice.Return(readOnly = false) CompletableFuture messageFuture, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelParentContext") Context otelParentContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; - } - - otelScope.close(); - if (throwable != null) { - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); - } else { - messageFuture = - messageFuture.whenComplete( - new SpanFinisher(PRODUCER_INSTRUMENTER, otelContext, connection, natsRequest)); - messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); + @Advice.Return CompletableFuture originalReturnValue, + @Advice.Enter Object[] enterResult) { + MessageFutureAdviceScope adviceScope = (MessageFutureAdviceScope) enterResult[0]; + if (adviceScope != null) { + return adviceScope.end(connection, originalReturnValue, throwable); } + return originalReturnValue; } } @@ -295,13 +338,12 @@ public static CompletableFuture onEnter( return connection.request(message.getSubject(), message.getHeaders(), message.getData()); } + @AssignReturned.ToReturned @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.Return(readOnly = false) CompletableFuture result, + public static CompletableFuture onExit( + @Advice.Return CompletableFuture originalResult, @Advice.Enter CompletableFuture future) { - if (future != null) { - result = future; - } + return future != null ? future : originalResult; } } @@ -318,88 +360,77 @@ public static CompletableFuture onEnter( return connection.requestWithTimeout(subject, null, body, timeout); } + @AssignReturned.ToReturned @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit( - @Advice.Return(readOnly = false) CompletableFuture result, + public static CompletableFuture onExit( @Advice.Enter CompletableFuture future) { - result = future; + return future; } } @SuppressWarnings("unused") public static class RequestTimeoutFutureHeadersBodyAdvice { + @AssignReturned.ToArguments(@ToArgument(value = 1, index = 1)) @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( + public static Object[] onEnter( @Advice.This Connection connection, @Advice.Argument(0) String subject, - @Advice.Argument(value = 1, readOnly = false) Headers headers, - @Advice.Argument(2) byte[] body, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelParentContext") Context otelParentContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - headers = NatsMessageWritableHeaders.create(headers); - natsRequest = NatsRequest.create(connection, subject, null, headers, body); - otelParentContext = Context.current(); - - if (!PRODUCER_INSTRUMENTER.shouldStart(otelParentContext, natsRequest)) { - return; - } + @Advice.Argument(1) Headers originalHeaders, + @Advice.Argument(2) byte[] body) { - otelContext = PRODUCER_INSTRUMENTER.start(otelParentContext, natsRequest); - otelScope = otelContext.makeCurrent(); + Headers headers = NatsMessageWritableHeaders.create(originalHeaders); + NatsRequest request = NatsRequest.create(connection, subject, null, headers, body); + MessageFutureAdviceScope adviceScope = MessageFutureAdviceScope.start(request); + return new Object[] {adviceScope, headers}; } + @AssignReturned.ToReturned @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( + public static CompletableFuture onExit( @Advice.This Connection connection, @Advice.Thrown Throwable throwable, - @Advice.Return(readOnly = false) CompletableFuture messageFuture, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelParentContext") Context otelParentContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; - } + @Advice.Return CompletableFuture originalMessageFuture, + @Advice.Enter Object[] enterResult) { - otelScope.close(); - if (throwable != null) { - PRODUCER_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); - } else { - messageFuture = - messageFuture.whenComplete( - new SpanFinisher(PRODUCER_INSTRUMENTER, otelContext, connection, natsRequest)); - messageFuture = CompletableFutureWrapper.wrap(messageFuture, otelParentContext); + CompletableFuture messageFuture = originalMessageFuture; + MessageFutureAdviceScope adviceScope = (MessageFutureAdviceScope) enterResult[0]; + if (adviceScope != null) { + return adviceScope.end(connection, originalMessageFuture, throwable); } + return originalMessageFuture; } } @SuppressWarnings("unused") public static class RequestTimeoutFutureMessageAdvice { + @AssignReturned.ToArguments(@ToArgument(value = 0, index = 1)) @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) - public static CompletableFuture onEnter( + public static Object[] onEnter( @Advice.This Connection connection, - @Advice.Argument(value = 0, readOnly = false) Message message, + @Advice.Argument(0) Message message, @Advice.Argument(1) Duration timeout) { if (message == null) { - return null; + return new Object[] {null, message}; } - // call the instrumented requestWithTimeout method - return connection.requestWithTimeout( - message.getSubject(), message.getHeaders(), message.getData(), timeout); + CompletableFuture future = + connection.requestWithTimeout( + message.getSubject(), message.getHeaders(), message.getData(), timeout); + + return new Object[] {future, message}; } + @AssignReturned.ToReturned @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void onExit( - @Advice.Return(readOnly = false) CompletableFuture result, - @Advice.Enter CompletableFuture future) { - if (future != null) { - result = future; - } + public static CompletableFuture onExit( + @Advice.Return CompletableFuture originalResult, + @Advice.Enter Object[] enterResult) { + + @SuppressWarnings("unchecked") + CompletableFuture future = (CompletableFuture) enterResult[0]; + return future != null ? future : originalResult; } } } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/MessageHandlerInstrumentation.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/MessageHandlerInstrumentation.java index d25df179a09f..bb89142c36de 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/MessageHandlerInstrumentation.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/MessageHandlerInstrumentation.java @@ -18,6 +18,7 @@ import io.opentelemetry.instrumentation.nats.v2_17.internal.NatsRequest; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import javax.annotation.Nullable; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -42,35 +43,45 @@ public void transform(TypeTransformer transformer) { @SuppressWarnings("unused") public static class OnMessageAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter( - @Advice.Argument(0) Message message, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - Context parentContext = Context.current(); - natsRequest = NatsRequest.create(message.getConnection(), message); + public static class AdviceScope { + private final NatsRequest request; + private final Context context; + private final Scope scope; + + public AdviceScope(NatsRequest request, Context context, Scope scope) { + this.request = request; + this.context = context; + this.scope = scope; + } + + @Nullable + public static AdviceScope start(Message message) { + Context parentContext = Context.current(); + NatsRequest request = NatsRequest.create(message.getConnection(), message); + if (!CONSUMER_PROCESS_INSTRUMENTER.shouldStart(parentContext, request)) { + return null; + } + Context context = CONSUMER_PROCESS_INSTRUMENTER.start(parentContext, request); + return new AdviceScope(request, context, context.makeCurrent()); + } - if (!CONSUMER_PROCESS_INSTRUMENTER.shouldStart(parentContext, natsRequest)) { - return; + public void end(@Nullable Throwable throwable) { + scope.close(); + CONSUMER_PROCESS_INSTRUMENTER.end(context, request, null, throwable); } + } - otelContext = CONSUMER_PROCESS_INSTRUMENTER.start(parentContext, natsRequest); - otelScope = otelContext.makeCurrent(); + @Advice.OnMethodEnter(suppress = Throwable.class) + public static AdviceScope onEnter(@Advice.Argument(0) Message message) { + return AdviceScope.start(message); } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void onExit( - @Advice.Thrown Throwable throwable, - @Advice.Local("otelContext") Context otelContext, - @Advice.Local("otelScope") Scope otelScope, - @Advice.Local("natsRequest") NatsRequest natsRequest) { - if (otelScope == null) { - return; + @Advice.Thrown @Nullable Throwable throwable, @Advice.Enter AdviceScope adviceScope) { + if (adviceScope != null) { + adviceScope.end(throwable); } - - otelScope.close(); - CONSUMER_PROCESS_INSTRUMENTER.end(otelContext, natsRequest, null, throwable); } } } diff --git a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationModule.java b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationModule.java index 3701dacf3b25..3c48286120d2 100644 --- a/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationModule.java +++ b/instrumentation/nats/nats-2.17/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nats/v2_17/NatsInstrumentationModule.java @@ -10,10 +10,12 @@ import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule; import java.util.List; @AutoService(InstrumentationModule.class) -public class NatsInstrumentationModule extends InstrumentationModule { +public class NatsInstrumentationModule extends InstrumentationModule + implements ExperimentalInstrumentationModule { public NatsInstrumentationModule() { super("nats", "nats-2.17"); @@ -26,4 +28,9 @@ public List typeInstrumentations() { new ConnectionRequestInstrumentation(), new MessageHandlerInstrumentation()); } + + @Override + public boolean isIndyReady() { + return true; + } }