Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.axonframework.messaging.core.timeout;

import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;

import java.util.concurrent.Future;
Expand Down Expand Up @@ -46,12 +47,13 @@
private final String taskName;
private final ScheduledExecutorService scheduledExecutorService;
private final Logger logger;
@Nullable
private final String callerClassName; // stored as name to avoid getName() on every stack frame check
private boolean completed = false;
private boolean interrupted = false;
private boolean interruptedExternally = false;
private long startTimeMs = -1;
private Future<?> currentScheduledFuture = null;
private String startStackTrace;


/**
Expand All @@ -72,12 +74,37 @@
int timeout,
int warningThreshold,
int warningInterval) {
this(taskName, timeout, warningThreshold, warningInterval, AxonTaskJanitor.INSTANCE, AxonTaskJanitor.LOGGER, null);

Check warning on line 77 in messaging/src/main/java/org/axonframework/messaging/core/timeout/AxonTimeLimitedTask.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Annotate the parameter with @javax.annotation.Nullable in constructor declaration, or make sure that null can not be passed as argument.

See more on https://sonarcloud.io/project/issues?id=AxonIQ_AxonFramework&issues=AZ6XCFBAIX_cXiZBiE1v&open=AZ6XCFBAIX_cXiZBiE1v&pullRequest=4635
}

/**
* Creates a new {@link AxonTimeLimitedTask} for the given {@code task} with the given {@code timeout},
* {@code warningThreshold} and {@code warningInterval}. Runs the provided task on the current thread after
* scheduling a timeout and warnings on another thread.
* <p>
* The {@code callerClass} is used to trim the stack trace in timeout/warning logs, cutting off framework internals
* below the caller. If you do not need trimming, use
* {@link #AxonTimeLimitedTask(String, int, int, int)}.
*
* @param taskName The task's name to be included in the logging
* @param timeout The timeout in milliseconds
* @param warningThreshold The threshold in milliseconds after which a warning is logged. Setting this to a value
* equal or higher than {@code timeout} will disable warnings.
* @param warningInterval The interval in milliseconds between warnings.
* @param callerClass the class of the direct caller, used to trim the stack trace in timeout/warning logs
*/
public AxonTimeLimitedTask(String taskName,
int timeout,
int warningThreshold,
int warningInterval,
Class<?> callerClass) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically a breaking change as this class is not marked internal. Maybe we should overload this and use a sensible default

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can, except no default will work to cut the stack trace short I think, so if you fail to specify a suitable value, you'll get the whole trace :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's fair. It's just to not break the contract, but I don't think it will actually happen

this(taskName,
timeout,
warningThreshold,
warningInterval,
AxonTaskJanitor.INSTANCE,
AxonTaskJanitor.LOGGER
AxonTaskJanitor.LOGGER,
callerClass
);
}

Expand All @@ -97,15 +124,45 @@
* @param warningThreshold The threshold in milliseconds after which a warning is logged. Setting this to a
* value equal or higher than {@code timeout} will disable warnings.
* @param warningInterval The interval in milliseconds between warnings.
* @param scheduledExecutorService The executor service to schedule the timeout and warnings
* @param logger The logger to log the warnings and errors
* @param scheduledExecutorService the executor service to schedule the timeout and warnings
* @param logger the logger to log the warnings and errors
*/
public AxonTimeLimitedTask(String taskName,
int timeout,
int warningThreshold,
int warningInterval,
ScheduledExecutorService scheduledExecutorService,
Logger logger) {
this(taskName, timeout, warningThreshold, warningInterval, scheduledExecutorService, logger, null);

Check warning on line 136 in messaging/src/main/java/org/axonframework/messaging/core/timeout/AxonTimeLimitedTask.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Annotate the parameter with @javax.annotation.Nullable in constructor declaration, or make sure that null can not be passed as argument.

See more on https://sonarcloud.io/project/issues?id=AxonIQ_AxonFramework&issues=AZ6XCFBAIX_cXiZBiE1w&open=AZ6XCFBAIX_cXiZBiE1w&pullRequest=4635
}

/**
* Creates a new {@link AxonTimeLimitedTask} for the given {@code task} with the given {@code timeout},
* {@code warningThreshold} and {@code warningInterval}. For scheduling, the provided
* {@code scheduledExecutorService} will be used. To log warnings and errors, the provided {@code logger} will be
* used.
* <p>
* The {@code callerClass} is used to trim the stack trace in timeout/warning logs, cutting off framework internals
* below the caller. If you do not need trimming, use
* {@link #AxonTimeLimitedTask(String, int, int, int, ScheduledExecutorService, Logger)}.
*
* @param taskName The task's name to be included in the logging
* @param timeout The timeout in milliseconds
* @param warningThreshold The threshold in milliseconds after which a warning is logged. Setting this to a
* value equal or higher than {@code timeout} will disable warnings.
* @param warningInterval The interval in milliseconds between warnings.
* @param scheduledExecutorService the executor service to schedule the timeout and warnings
* @param logger the logger to log the warnings and errors
* @param callerClass the class of the direct caller, used to trim the stack trace in timeout/warning
* logs
*/
public AxonTimeLimitedTask(String taskName,
int timeout,
int warningThreshold,
int warningInterval,
ScheduledExecutorService scheduledExecutorService,
Logger logger,
Class<?> callerClass) {
if (taskName == null || taskName.isEmpty()) {
throw new IllegalArgumentException("Task name cannot be null or empty");
}
Expand All @@ -115,6 +172,7 @@
this.warningInterval = warningInterval;
this.scheduledExecutorService = scheduledExecutorService;
this.logger = logger;
this.callerClassName = callerClass != null ? callerClass.getName() : null;

Check warning on line 175 in messaging/src/main/java/org/axonframework/messaging/core/timeout/AxonTimeLimitedTask.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change this condition so that it does not always evaluate to "true"

See more on https://sonarcloud.io/project/issues?id=AxonIQ_AxonFramework&issues=AZ6XCFBAIX_cXiZBiE1u&open=AZ6XCFBAIX_cXiZBiE1u&pullRequest=4635
this.thread = Thread.currentThread();
}

Expand All @@ -130,7 +188,6 @@
throw new IllegalStateException("Task can only be run once");
}
startTimeMs = System.currentTimeMillis();
startStackTrace = thread.getStackTrace()[2].getClassName();

if (warningThreshold < 0 || warningThreshold >= timeout) {
scheduleImmediateInterrupt();
Expand Down Expand Up @@ -311,8 +368,7 @@
StringBuilder sb = new StringBuilder();
for (StackTraceElement element : stackTrace) {
sb.append(element).append("\n");
// This is the start of the stack trace of the framework internals calling the method
if (element.toString().contains(startStackTrace)) {
if (callerClassName != null && element.getClassName().equals(callerClassName)) {
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public Object handleSync(Message message, ProcessingContext context, @Nullable T
taskName,
timeout,
warningThreshold,
warningInterval
warningInterval,
getClass()
);
task.start();
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ void initializeTimeoutIfNotInitialized(ProcessingContext context) {
warningThreshold,
warningInterval,
executorService,
logger
logger,
UnitOfWorkTimeoutInterceptorBuilder.class
);
context.putResource(TRANSACTION_TIME_LIMIT_CONTEXT_RESOURCE_KEY, taskTimeout);
taskTimeout.start();
Expand Down