Improve task lifecycle handling and optimize TaskManagerImpl

Redesigned task lifecycle management to properly cascade parent-child removals and synchronize database persistence on task deletion. Refactored task management to address memory leaks, concurrency collisions, and pending-task cleanup issues. Enhanced lookup performance, logging accuracy, and test coverage with a comprehensive unit test suite validating all task scenarios.
This commit is contained in:
anthorob committed 2026-09-23 10:07:26 -04:00
1 parent a507b471b4
commit 5f6265db01
3 files changed
+811 -44

No files matched your search

@@ -23,13 +23,13 @@ import net.dv8tion.jda.api.interactions.InteractionHook;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -41,7 +41,7 @@ public class TaskManagerImpl implements TaskManager {
private static final Logger logger = LogManager.getLogger(TaskManagerImpl.class);
private final TaskService taskService;
private final Map<String, Task> tasks;
private final List<Task> pendingTasks;
private final Map<String, Task> pendingTasks;
private final Map<String, CommandContext> deletableTasksContext;
private final Map<String, ReentrantLock> tasksLock;
private final ApplicationContext context;
@@ -55,7 +55,7 @@ public class TaskManagerImpl implements TaskManager {
public TaskManagerImpl(ApplicationContext context) {
Objects.requireNonNull(context, "ApplicationContext must not be null");
this.tasks = new ConcurrentHashMap<>();
this.pendingTasks = new CopyOnWriteArrayList<>();
this.pendingTasks = new ConcurrentHashMap<>();
this.deletableTasksContext = new ConcurrentHashMap<>();
this.tasksLock = new ConcurrentHashMap<>();
this.taskService = new TaskService(this);
@@ -86,7 +86,14 @@ public class TaskManagerImpl implements TaskManager {
@Override
public Task getTask(String id) {
return tasks.get(id);
if (id == null) {
return null;
}
Task task = tasks.get(id);
if (task != null) {
return task;
}
return pendingTasks.get(id);
}
/**
@@ -96,12 +103,17 @@ public class TaskManagerImpl implements TaskManager {
*/
@Override
public boolean hasTask(String id) {
return tasks.containsKey(id) || pendingTasks.stream().anyMatch(task -> task.getId().equals(id));
if (id == null) {
return false;
}
return tasks.containsKey(id) || pendingTasks.containsKey(id);
}
@Override
public Collection<Task> getTasks() {
return tasks.values();
List<Task> allTasks = new ArrayList<>(tasks.values());
allTasks.addAll(pendingTasks.values());
return allTasks;
}
@Override
@@ -123,7 +135,7 @@ public class TaskManagerImpl implements TaskManager {
}
if (task instanceof StepTask) {
pendingTasks.add(task);
pendingTasks.put(id, task);
} else {
tasks.put(id, task);
}
@@ -136,7 +148,7 @@ public class TaskManagerImpl implements TaskManager {
if (task instanceof WithChildren taskWithChildren) {
List<Task> childrens = taskWithChildren.getChildTasks();
if (childrens == null || childrens.isEmpty()) {
logger.warn("Parent task '{}' doesn't create any childs", task.getName());
logger.warn("Parent task '{}' doesn't create any children", task.getName());
return;
}
@@ -148,23 +160,37 @@ public class TaskManagerImpl implements TaskManager {
@Override
public void activateTask(Task task, String oldId) {
if (pendingTasks.remove(task)) {
tasks.put(task.getId(), task);
logger.info("Updating task {} with new id: {} (was: {})",
task.getName(), task.getId(), oldId);
if (task == null) {
return;
}
boolean removed = oldId != null && pendingTasks.remove(oldId) != null;
if (!removed) {
removed = pendingTasks.remove(task.getId()) != null;
}
if (!removed) {
pendingTasks.values().remove(task);
}
tasks.put(task.getId(), task);
logger.info("Updating task {} with new id: {} (was: {})",
task.getName(), task.getId(), oldId);
}
@Override
public void removeTask(Task task) {
if (task == null) {
return;
}
String id = task.getId();
if (tasks.remove(id) != null) {
boolean removed = tasks.remove(id) != null;
removed |= pendingTasks.remove(id) != null;
removed |= pendingTasks.values().remove(task);
if (removed) {
logger.debug("Removing task: {}", id);
ReentrantLock lock = tasksLock.remove(task.getLockId());
if (lock != null) {
logger.debug("Cleaning up lock for removed task: {}", task.getId());
if (task instanceof PersistableTask persistableTask) {
taskService.unpersistTask(persistableTask);
}
}
}
@@ -181,16 +207,16 @@ public class TaskManagerImpl implements TaskManager {
if (task != null) {
String lockId = task.getLockId();
ReentrantLock lock = (task instanceof PersistableTask || task instanceof BuiltInTask)
? null
: tasksLock.computeIfAbsent(lockId, _ -> new ReentrantLock());
? null
: tasksLock.computeIfAbsent(lockId, _ -> new ReentrantLock());
try {
if (lock != null) {
logger.debug("Aquiring lock for task: {}", taskId);
logger.debug("Acquiring lock for task: {}", taskId);
lock.lock();
if (!tasks.containsKey(taskId)) {
logger.debug("Task {} is no longer available after aquiring lock", taskId);
logger.debug("Task {} is no longer available after acquiring lock", taskId);
return;
}
}
@@ -224,26 +250,35 @@ public class TaskManagerImpl implements TaskManager {
@Override
public <E extends Task> Optional<E> findTaskByPartialId(Class<E> clazz, String partialId) {
List<E> tasks = findTasksByPartialId(clazz, partialId);
return Optional.ofNullable(tasks.isEmpty() ? null : tasks.getFirst());
if (clazz == null || partialId == null) {
return Optional.empty();
}
return tasks.values().stream()
.filter(clazz::isInstance)
.map(clazz::cast)
.filter(task -> task.getId() != null && task.getId().contains(partialId))
.findFirst();
}
@Override
public <E extends Task> List<E> findTasksByPartialId(Class<E> clazz, String partialId) {
if (clazz == null || partialId == null) {
return List.of();
}
return tasks.values().stream()
.filter(clazz::isInstance)
.map(clazz::cast)
.filter(task -> task.getId().contains(partialId))
.collect(Collectors.toList());
.filter(clazz::isInstance)
.map(clazz::cast)
.filter(task -> task.getId() != null && task.getId().contains(partialId))
.collect(Collectors.toList());
}
@Override
public WithButton findButtonTaskByClass(Class<? extends WithButton> clazz) {
return tasks.values().stream()
.filter(clazz::isInstance)
.map(task -> (WithButton) task)
.findFirst()
.orElse(null);
.filter(clazz::isInstance)
.map(task -> (WithButton) task)
.findFirst()
.orElse(null);
}
@Override
@@ -272,6 +307,9 @@ public class TaskManagerImpl implements TaskManager {
private void deleteInteraction(Task task) {
if (task == null) {
return;
}
if (task instanceof WithParent<?> taskWithParent) {
Task parent = taskWithParent.getParentTask();
@@ -289,18 +327,16 @@ public class TaskManagerImpl implements TaskManager {
private void deleteInteraction(String taskId, Deletable deletable) {
logger.debug("Deleting interaction: {}", taskId);
CommandContext context = deletableTasksContext.get(taskId);
if (context != null) {
deletableTasksContext.remove(taskId);
InteractionHook hook = context.getInteractionHook();
CommandContext commandContext = deletableTasksContext.remove(taskId);
if (commandContext != null) {
InteractionHook hook = commandContext.getInteractionHook();
if (hook != null) {
if (hook.isExpired()) {
context.replyEphemeral("This interaction has expired, can't remove it");
}
if (!hook.isExpired()) {
hook.deleteOriginal().queue();
commandContext.replyEphemeral("This interaction has expired, can't remove it");
} else {
hook.deleteOriginal().queue(null, error ->
logger.warn("Failed to delete interaction for task {}: {}", taskId, error.getMessage())
);
}
}
}
@@ -319,12 +355,32 @@ public class TaskManagerImpl implements TaskManager {
break;
case REMOVE_SELF_AND_CHILDREN:
if (task instanceof WithChildren taskWithChildren) {
for (Task child : taskWithChildren.getChildTasks()) {
removeTask(child);
if (task instanceof WithParent<?> taskWithParent) {
Task parent = taskWithParent.getParentTask();
if (parent != null) {
if (parent instanceof WithChildren parentWithChildren) {
List<Task> childTasks = parentWithChildren.getChildTasks();
if (childTasks != null) {
for (Task child : childTasks) {
removeTask(child);
}
}
}
removeTask(parent);
} else {
removeTask(task);
}
} else if (task instanceof WithChildren taskWithChildren) {
List<Task> childTasks = taskWithChildren.getChildTasks();
if (childTasks != null) {
for (Task child : childTasks) {
removeTask(child);
}
}
removeTask(task);
} else {
removeTask(task);
}
removeTask(task);
break;
case KEEP: