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:
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:
|
||||
|
||||
Reference in new issue
Block a user