From eb4bf1b726d8b1f1bbfb27bfe4393ceb21dc58a9 Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Wed, 22 Jan 2025 09:29:44 +0100 Subject: [PATCH 1/5] add logs --- .../tum/cit/aet/artemis/programming/service/GitService.java | 6 +++--- .../service/ProgrammingExerciseScheduleService.java | 4 ++-- .../service/vcs/AbstractVersionControlService.java | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/GitService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/GitService.java index 438b9d06b3bd..ed87f6145ef7 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/GitService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/GitService.java @@ -320,10 +320,10 @@ public Repository getOrCheckoutRepository(VcsRepositoryUri repoUri, boolean pull return getOrCheckoutRepository(repoUri, repoUri, localPath, pullOnGet, defaultBranch); } - public Repository getOrCheckoutRepositoryIntoTargetDirectory(VcsRepositoryUri repoUri, VcsRepositoryUri targetUrl, boolean pullOnGet) + public Repository getOrCheckoutRepositoryIntoTargetDirectory(VcsRepositoryUri repoUri, VcsRepositoryUri targetUri, boolean pullOnGet) throws GitAPIException, GitException, InvalidPathException { - Path localPath = getDefaultLocalPathOfRepo(targetUrl); - return getOrCheckoutRepository(repoUri, targetUrl, localPath, pullOnGet); + Path localPath = getDefaultLocalPathOfRepo(targetUri); + return getOrCheckoutRepository(repoUri, targetUri, localPath, pullOnGet); } public Repository getOrCheckoutRepository(VcsRepositoryUri repoUri, Path localPath, boolean pullOnGet) throws GitAPIException, GitException, InvalidPathException { diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseScheduleService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseScheduleService.java index 16eebcaca693..ff4d9c80609f 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseScheduleService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseScheduleService.java @@ -302,7 +302,7 @@ private void scheduleTemplateCommitCombination(ProgrammingExercise exercise) { var scheduledRunnable = Set.of(new Tuple<>(exercise.getReleaseDate().minusSeconds(Constants.SECONDS_BEFORE_RELEASE_DATE_FOR_COMBINING_TEMPLATE_COMMITS), combineTemplateCommitsForExercise(exercise))); scheduleService.scheduleTask(exercise, ExerciseLifecycle.RELEASE, scheduledRunnable); - log.debug("Scheduled combining template commits before release date for Programming Exercise \"{}\" (#{}) for {}.", exercise.getTitle(), exercise.getId(), + log.info("Scheduled combining template commits before release date for Programming Exercise \"{}\" (#{}) for {}.", exercise.getTitle(), exercise.getId(), exercise.getReleaseDate()); } } @@ -454,7 +454,7 @@ private Runnable combineTemplateCommitsForExercise(ProgrammingExercise exercise) ProgrammingExercise programmingExerciseWithTemplateParticipation = programmingExerciseRepository .findByIdWithTemplateAndSolutionParticipationElseThrow(exercise.getId()); gitService.combineAllCommitsOfRepositoryIntoOne(programmingExerciseWithTemplateParticipation.getTemplateParticipation().getVcsRepositoryUri()); - log.debug("Combined template repository commits of programming exercise {}.", programmingExerciseWithTemplateParticipation.getId()); + log.info("Combined template repository commits of programming exercise {}.", programmingExerciseWithTemplateParticipation.getId()); } catch (GitAPIException e) { log.error("Failed to communicate with GitAPI for combining template commits of exercise {}", exercise.getId(), e); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/vcs/AbstractVersionControlService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/vcs/AbstractVersionControlService.java index 454814afc442..83f30c289159 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/vcs/AbstractVersionControlService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/vcs/AbstractVersionControlService.java @@ -120,7 +120,6 @@ public VcsRepositoryUri copyRepository(String sourceProjectKey, String sourceRep catch (GitAPIException | VersionControlException ex) { if (isReadFullyShortReadOfBlockException(ex)) { // NOTE: we ignore this particular error: it sometimes happens when pushing code that includes binary files, however the push operation typically worked correctly - // TODO: verify that the push operation actually worked correctly, e.g. by comparing the number of commits in the source and target repo log.warn("TransportException/EOFException with 'Short read of block' when copying repository {} to {}. Will ignore it", sourceRepoUri, targetRepoUri); return targetRepoUri; } From 38d0d2e197206d2e114838c63ca9d3dadab8c490 Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Sun, 26 Jan 2025 11:45:14 +0100 Subject: [PATCH 2/5] improve GitService method names --- build.gradle | 2 +- .../aet/artemis/programming/service/GitService.java | 10 +++++----- .../service/JavaTemplateUpgradeService.java | 4 ++-- .../service/ProgrammingExerciseRepositoryService.java | 4 ++-- .../artemis/programming/service/RepositoryService.java | 8 ++++---- .../cit/aet/artemis/programming/GitServiceTest.java | 8 ++++---- ...rogrammingExerciseParticipationIntegrationTest.java | 4 ++-- .../artemis/programming/RepositoryIntegrationTest.java | 2 +- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/build.gradle b/build.gradle index ca407a7fb966..8154c840843e 100644 --- a/build.gradle +++ b/build.gradle @@ -218,7 +218,7 @@ dependencies { implementation "io.sentry:sentry-logback:${sentry_version}" implementation "io.sentry:sentry-spring-boot-starter-jakarta:${sentry_version}" - // NOTE: the following six dependencies use the newer versions explicitly to avoid other dependencies to use older versions + // NOTE: the following dependencies use the newer versions explicitly to avoid other dependencies to use older versions implementation "ch.qos.logback:logback-classic:${logback_version}" implementation "ch.qos.logback:logback-core:${logback_version}" diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/GitService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/GitService.java index ed87f6145ef7..20a5ab1d6c67 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/GitService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/GitService.java @@ -1015,7 +1015,7 @@ public boolean accept(java.io.File directory, String fileName) { } /** - * Lists all files and directories within the given repository, excluding symbolic links. + * Returns all files and directories within the given repository in a map, excluding symbolic links. * This method performs a file scan and filters out symbolic links. * It supports bare and checked-out repositories. *

@@ -1026,7 +1026,7 @@ public boolean accept(java.io.File directory, String fileName) { * @return A {@link Map} where each key is a {@link File} object representing a file or directory, and each value is * the corresponding {@link FileType} (FILE or FOLDER). The map excludes symbolic links. */ - public Map listFilesAndFolders(Repository repo) { + public Map getFilesAndFolders(Repository repo) { FileAndDirectoryFilter filter = new FileAndDirectoryFilter(); Iterator itr = FileUtils.iterateFilesAndDirs(repo.getLocalPath().toFile(), filter, filter); @@ -1048,13 +1048,13 @@ public Map listFilesAndFolders(Repository repo) { } /** - * List all files in the repository. In an empty git repo, this method returns 0. + * List all files in the repository. In an empty git repo, this method returns en empty list. * * @param repo Local Repository Object. * @return Collection of File objects */ @NotNull - public Collection listFiles(Repository repo) { + public Collection getFiles(Repository repo) { // Check if list of files is already cached if (repo.getFiles() == null) { FileAndDirectoryFilter filter = new FileAndDirectoryFilter(); @@ -1083,7 +1083,7 @@ public Optional getFileByName(Repository repo, String filename) { // Makes sure the requested file is part of the scanned list of files. // Ensures that it is not possible to do bad things like filename="../../passwd" - for (File file : listFilesAndFolders(repo).keySet()) { + for (File file : getFilesAndFolders(repo).keySet()) { if (file.toString().equals(filename)) { return Optional.of(file); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/JavaTemplateUpgradeService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/JavaTemplateUpgradeService.java index 9e6a796307a0..c6be04cd1dc6 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/JavaTemplateUpgradeService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/JavaTemplateUpgradeService.java @@ -103,7 +103,7 @@ private void upgradeTemplateFiles(ProgrammingExercise exercise, RepositoryType r String templatePomDir = repositoryType == RepositoryType.TESTS ? "test/maven/projectTemplate" : repositoryType.getName(); Resource[] templatePoms = getTemplateResources(exercise, templatePomDir + "/**/" + POM_FILE); Repository repository = gitService.getOrCheckoutRepository(exercise.getRepositoryURL(repositoryType), true); - List repositoryPoms = gitService.listFiles(repository).stream().filter(file -> Objects.equals(file.getName(), POM_FILE)).toList(); + List repositoryPoms = gitService.getFiles(repository).stream().filter(file -> Objects.equals(file.getName(), POM_FILE)).toList(); // Validate that template and repository have the same number of pom.xml files, otherwise no upgrade will take place if (templatePoms.length == 1 && repositoryPoms.size() == 1) { @@ -272,7 +272,7 @@ private Predicate isPlugin(String groupId, String artifactId) { } private Optional getFileByName(Repository repository, String filename) { - return gitService.listFilesAndFolders(repository).keySet().stream().filter(file -> Objects.equals(filename, file.getName())).findFirst(); + return gitService.getFilesAndFolders(repository).keySet().stream().filter(file -> Objects.equals(filename, file.getName())).findFirst(); } private Optional getFileByName(Resource[] resources, String filename) { diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseRepositoryService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseRepositoryService.java index ffbf6aa37f0a..2fa7a65c0880 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseRepositoryService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseRepositoryService.java @@ -324,7 +324,7 @@ private void removeAuxiliaryRepository(AuxiliaryRepository auxiliaryRepository) private void setupTemplateAndPush(final RepositoryResources repositoryResources, final String templateName, final ProgrammingExercise programmingExercise, final User user) throws IOException, GitAPIException { // Only copy template if repo is empty - if (!gitService.listFiles(repositoryResources.repository).isEmpty()) { + if (!gitService.getFiles(repositoryResources.repository).isEmpty()) { return; } @@ -358,7 +358,7 @@ private static Path getRepoAbsoluteLocalPath(final Repository repository) { */ private void setupTestTemplateAndPush(final RepositoryResources resources, final ProgrammingExercise programmingExercise, final User user) throws IOException, GitAPIException { // Only copy template if repo is empty - if (gitService.listFiles(resources.repository).isEmpty() + if (gitService.getFiles(resources.repository).isEmpty() && (programmingExercise.getProgrammingLanguage() == ProgrammingLanguage.JAVA || programmingExercise.getProgrammingLanguage() == ProgrammingLanguage.KOTLIN)) { setupJVMTestTemplateAndPush(resources, programmingExercise, user); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/RepositoryService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/RepositoryService.java index 1ca095cac600..330873d2dfff 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/RepositoryService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/RepositoryService.java @@ -97,7 +97,7 @@ public ProgrammingExerciseParticipation getAsProgrammingExerciseParticipationOfE * @return a map of files with the information if they are a file or a folder. */ public Map getFiles(Repository repository) { - var iterator = gitService.listFilesAndFolders(repository).entrySet().iterator(); + var iterator = gitService.getFilesAndFolders(repository).entrySet().iterator(); Map fileList = new HashMap<>(); @@ -158,7 +158,7 @@ public Map getFilesContentAtCommit(ProgrammingExercise programmi * The map includes only those files that could successfully have their contents read; files that cause an IOException are logged but not included. */ public Map getFilesContentFromWorkingCopy(Repository repository) { - var files = gitService.listFilesAndFolders(repository).entrySet().stream().filter(entry -> entry.getValue() == FileType.FILE).map(Map.Entry::getKey).toList(); + var files = gitService.getFilesAndFolders(repository).entrySet().stream().filter(entry -> entry.getValue() == FileType.FILE).map(Map.Entry::getKey).toList(); Map fileListWithContent = new HashMap<>(); files.forEach(file -> { @@ -262,9 +262,9 @@ public byte[] getFile(Repository repository, String filename) throws IOException public Map getFilesWithInformationAboutChange(Repository repository, Repository templateRepository) { Map filesWithInformationAboutChange = new HashMap<>(); - var repoFiles = gitService.listFilesAndFolders(repository).entrySet().stream().filter(entry -> entry.getValue() == FileType.FILE).map(Map.Entry::getKey).toList(); + var repoFiles = gitService.getFilesAndFolders(repository).entrySet().stream().filter(entry -> entry.getValue() == FileType.FILE).map(Map.Entry::getKey).toList(); - Map templateRepoFiles = gitService.listFilesAndFolders(templateRepository).entrySet().stream().filter(entry -> entry.getValue() == FileType.FILE) + Map templateRepoFiles = gitService.getFilesAndFolders(templateRepository).entrySet().stream().filter(entry -> entry.getValue() == FileType.FILE) .collect(Collectors.toMap(entry -> entry.getKey().toString(), Map.Entry::getKey)); repoFiles.forEach(file -> { diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/GitServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/GitServiceTest.java index 41d8926aa61d..7347529dbd36 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/GitServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/GitServiceTest.java @@ -288,10 +288,10 @@ private static Stream getBranchCombinationsToTest() { } @Test - void testListFilesAndFolders() { + void testGetFilesAndFolders() { Repository localRepo = gitUtilService.getRepoByType(GitUtilService.REPOS.LOCAL); - var map = gitService.listFilesAndFolders(localRepo); + var map = gitService.getFilesAndFolders(localRepo); assertThat(map).hasSize(4).containsEntry(new File(gitUtilService.getFile(GitUtilService.REPOS.LOCAL, GitUtilService.FILES.FILE1), localRepo), FileType.FILE) .containsEntry(new File(gitUtilService.getFile(GitUtilService.REPOS.LOCAL, GitUtilService.FILES.FILE2), localRepo), FileType.FILE) @@ -300,10 +300,10 @@ void testListFilesAndFolders() { } @Test - void testListFiles() { + void testGetFiles() { Repository localRepo = gitUtilService.getRepoByType(GitUtilService.REPOS.LOCAL); - var fileList = gitService.listFiles(localRepo); + var fileList = gitService.getFiles(localRepo); assertThat(fileList).hasSize(3).contains(new File(gitUtilService.getFile(GitUtilService.REPOS.LOCAL, GitUtilService.FILES.FILE1), localRepo)) .contains(new File(gitUtilService.getFile(GitUtilService.REPOS.LOCAL, GitUtilService.FILES.FILE2), localRepo)) diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseParticipationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseParticipationIntegrationTest.java index 843cb7e0f378..73b1edd26296 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseParticipationIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseParticipationIntegrationTest.java @@ -763,7 +763,7 @@ void getParticipationRepositoryFilesInstructorSuccess() throws Exception { var commitInfo2 = new CommitInfoDTO("hash2", "msg2", ZonedDateTime.of(2020, 1, 2, 0, 0, 0, 0, ZoneId.of("UTC")), "author2", "authorEmail2"); doReturn(List.of(commitInfo, commitInfo2)).when(gitService).getCommitInfos(participation.getVcsRepositoryUri()); doReturn(new Repository("ab", new VcsRepositoryUri("uri"))).when(gitService).checkoutRepositoryAtCommit(participation.getVcsRepositoryUri(), commitHash, true); - doReturn(Map.of()).when(gitService).listFilesAndFolders(any()); + doReturn(Map.of()).when(gitService).getFilesAndFolders(any()); doNothing().when(gitService).switchBackToDefaultBranchHead(any()); request.getMap("/api/programming-exercise-participations/" + participation.getId() + "/files-content/" + commitHash, HttpStatus.OK, String.class, String.class); @@ -792,7 +792,7 @@ void setup() throws GitAPIException, URISyntaxException, IOException { programmingExerciseIntegrationTestService.addAuxiliaryRepositoryToExercise(programmingExercise); COMMIT_HASH = "commitHash"; - doReturn(Map.of()).when(gitService).listFilesAndFolders(any()); + doReturn(Map.of()).when(gitService).getFilesAndFolders(any()); doNothing().when(gitService).switchBackToDefaultBranchHead(any()); doReturn(new Repository("ab", new VcsRepositoryUri("uri"))).when(gitService).checkoutRepositoryAtCommit(any(VcsRepositoryUri.class), any(String.class), any(Boolean.class)); diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/RepositoryIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/RepositoryIntegrationTest.java index 1fc69fb5eedb..455133ade3a3 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/RepositoryIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/RepositoryIntegrationTest.java @@ -344,7 +344,7 @@ private String getCommitHash(Git repo) throws GitAPIException { void testGetFilesWithContent_shouldNotThrowException() throws Exception { Map mockedFiles = new HashMap<>(); mockedFiles.put(mock(de.tum.cit.aet.artemis.programming.domain.File.class), FileType.FILE); - doReturn(mockedFiles).when(gitService).listFilesAndFolders(any(Repository.class)); + doReturn(mockedFiles).when(gitService).getFilesAndFolders(any(Repository.class)); MockedStatic mockedFileUtils = mockStatic(FileUtils.class); mockedFileUtils.when(() -> FileUtils.readFileToString(any(File.class), eq(StandardCharsets.UTF_8))).thenThrow(IOException.class); From fad2eab4f76a394a43254cee706df22c218670dd Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Sun, 26 Jan 2025 20:13:36 +0100 Subject: [PATCH 3/5] schedule combine commit separatly improve logging and add a REST endpoint to review current schedules --- .../service/NotificationScheduleService.java | 8 +- .../config/TaskSchedulingConfiguration.java | 2 + .../artemis/core/service/ScheduleService.java | 217 ++++++++++++++---- .../tum/cit/aet/artemis/core/util/Tuple.java | 8 +- .../core/web/admin/AdminScheduleResource.java | 49 ++++ .../exercise/domain/ExerciseLifecycle.java | 13 ++ .../service/ExerciseLifecycleService.java | 6 +- .../exercise/web/ParticipationResource.java | 2 +- .../ModelingExerciseScheduleService.java | 5 +- .../ProgrammingExerciseScheduleService.java | 132 +++++++---- .../quiz/service/QuizScheduleService.java | 6 +- .../TitleCacheEvictionServiceTest.java | 10 +- ...rogrammingExerciseScheduleServiceTest.java | 60 ++--- 13 files changed, 371 insertions(+), 147 deletions(-) create mode 100644 src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminScheduleResource.java diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/service/NotificationScheduleService.java b/src/main/java/de/tum/cit/aet/artemis/communication/service/NotificationScheduleService.java index fbd312a44bdd..560c5c7acfe7 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/service/NotificationScheduleService.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/service/NotificationScheduleService.java @@ -114,13 +114,13 @@ public void updateSchedulingForReleasedExercises(Exercise exercise) { private void scheduleNotificationForReleasedExercise(Exercise exercise) { try { checkSecurityUtils(); - scheduleService.scheduleTask(exercise, ExerciseLifecycle.RELEASE, () -> { + scheduleService.scheduleExerciseTask(exercise, ExerciseLifecycle.RELEASE, () -> { checkSecurityUtils(); Exercise foundCurrentVersionOfScheduledExercise = exerciseRepository.findByIdElseThrow(exercise.getId()); if (checkIfTimeIsCorrectForScheduledTask(foundCurrentVersionOfScheduledExercise.getReleaseDate())) { groupNotificationService.notifyAllGroupsAboutReleasedExercise(foundCurrentVersionOfScheduledExercise); } - }); + }, "notify about release exercise"); log.debug("Scheduled notify about started exercise after due date for exercise '{}' (#{}) for {}.", exercise.getTitle(), exercise.getId(), exercise.getReleaseDate()); } catch (Exception exception) { @@ -155,13 +155,13 @@ public void updateSchedulingForAssessedExercisesSubmissions(Exercise exercise) { private void scheduleNotificationForAssessedExercisesSubmissions(Exercise exercise) { try { checkSecurityUtils(); - scheduleService.scheduleTask(exercise, ExerciseLifecycle.ASSESSMENT_DUE, () -> { + scheduleService.scheduleExerciseTask(exercise, ExerciseLifecycle.ASSESSMENT_DUE, () -> { checkSecurityUtils(); Exercise foundCurrentVersionOfScheduledExercise = exerciseRepository.findByIdElseThrow(exercise.getId()); if (checkIfTimeIsCorrectForScheduledTask(foundCurrentVersionOfScheduledExercise.getAssessmentDueDate())) { singleUserNotificationService.notifyUsersAboutAssessedExerciseSubmission(foundCurrentVersionOfScheduledExercise); } - }); + }, "notify about assessed exercise submission"); log.debug("Scheduled notify about assessed exercise submission after assessment due date for exercise '{}' (#{}) at {}.", exercise.getTitle(), exercise.getId(), exercise.getAssessmentDueDate()); } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/TaskSchedulingConfiguration.java b/src/main/java/de/tum/cit/aet/artemis/core/config/TaskSchedulingConfiguration.java index e5f3b318b892..c6d3fcdea04a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/config/TaskSchedulingConfiguration.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/config/TaskSchedulingConfiguration.java @@ -22,7 +22,9 @@ public class TaskSchedulingConfiguration { public TaskScheduler taskScheduler() { log.debug("Creating Task Scheduler "); var scheduler = new ThreadPoolTaskScheduler(); + scheduler.setVirtualThreads(true); scheduler.setPoolSize(4); + scheduler.initialize(); return scheduler; } } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/ScheduleService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/ScheduleService.java index 69ad1cfd5181..2dc50adfc688 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/ScheduleService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/ScheduleService.java @@ -1,19 +1,36 @@ package de.tum.cit.aet.artemis.core.service; import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; +import static java.time.ZoneId.systemDefault; +import java.time.Duration; +import java.time.Instant; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.commons.lang3.tuple.Triple; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.annotation.Profile; +import org.springframework.context.event.EventListener; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.stereotype.Service; import de.tum.cit.aet.artemis.core.util.Tuple; @@ -36,34 +53,127 @@ public class ScheduleService { private final ParticipationLifecycleService participationLifecycleService; - private final ConcurrentMap, Set>> scheduledExerciseTasks = new ConcurrentHashMap<>(); + record ExerciseLifecycleKey(Long exerciseId, ExerciseLifecycle lifecycle) { + } + + record ParticipationLifecycleKey(Long exerciseId, Long participationId, ParticipationLifecycle lifecycle) { + } + + record ScheduledTaskName(ScheduledFuture future, String name) { + } + + public record ScheduledExerciseEvent(Long exerciseId, ExerciseLifecycle lifecycle, String name, ZonedDateTime scheduledTime, Future.State state) { + } + + private final ConcurrentMap> scheduledExerciseTasks = new ConcurrentHashMap<>(); // triple of exercise id, participation id, and lifecycle - private final ConcurrentMap, Set>> scheduledParticipationTasks = new ConcurrentHashMap<>(); + private final ConcurrentMap> scheduledParticipationTasks = new ConcurrentHashMap<>(); + + private final TaskScheduler taskScheduler; + + private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm:ss"); public ScheduleService(ExerciseLifecycleService exerciseLifecycleService, ParticipationLifecycleService participationLifecycleService) { this.exerciseLifecycleService = exerciseLifecycleService; this.participationLifecycleService = participationLifecycleService; + + // Initialize the TaskScheduler + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(1); + scheduler.setVirtualThreads(true); + scheduler.initialize(); + this.taskScheduler = scheduler; + } + + public Page findAllExerciseEvents(Pageable pageable) { + // Flatten the map into a list of ScheduledExerciseEvent + var allEvents = scheduledExerciseTasks.entrySet().stream().flatMap(entry -> entry.getValue().stream().map(task -> { + // Calculate the scheduled time from the future's delay + var scheduledTime = ZonedDateTime.now().plusSeconds(task.future().getDelay(TimeUnit.SECONDS)); + return new ScheduledExerciseEvent(entry.getKey().exerciseId(), entry.getKey().lifecycle(), task.name(), scheduledTime, task.future().state()); + })).sorted(Comparator.comparing(ScheduledExerciseEvent::scheduledTime)).collect(Collectors.toList()); + + // Apply pagination + int start = (int) pageable.getOffset(); + int end = Math.min(start + pageable.getPageSize(), allEvents.size()); + List paginatedEvents = start < allEvents.size() ? allEvents.subList(start, end) : new ArrayList<>(); + + return new PageImpl<>(paginatedEvents, pageable, allEvents.size()); + } + + @EventListener(ApplicationReadyEvent.class) + public void startup() { + taskScheduler.scheduleAtFixedRate(() -> { + log.info("Number of scheduled Exercise Tasks: {}", scheduledExerciseTasks.values().stream().mapToLong(Set::size).sum()); + + // if the map is not empty and there is at least still one future in the values map, log the tasks and remove the ones that are not running anymore + if (!scheduledExerciseTasks.isEmpty() && scheduledExerciseTasks.values().stream().anyMatch(set -> !set.isEmpty())) { + log.info(" Scheduled Exercise Tasks:"); + scheduledExerciseTasks.forEach((key, taskNames) -> { + taskNames.removeIf(taskName -> { + long delay = taskName.future().getDelay(TimeUnit.SECONDS); + var state = taskName.future().state(); + Instant scheduledTime = Instant.now().plusSeconds(delay); + ZonedDateTime zonedScheduledTime = scheduledTime.atZone(systemDefault()); + String formattedTime = zonedScheduledTime.format(formatter); + log.info(" Exercise: {}, Lifecycle: {}, Name: {}, Scheduled Run Time: {}, State: {}, Remaining Delay: {} s", key.exerciseId(), key.lifecycle(), + taskName.name(), formattedTime, state, delay); + return state != Future.State.RUNNING; + }); + }); + } + + // clean up empty entries in the map + scheduledExerciseTasks.entrySet().removeIf(entry -> entry.getValue().isEmpty()); + + log.info("Number of scheduled Participation Tasks: {}", scheduledParticipationTasks.values().stream().mapToLong(Set::size).sum()); + + // if the map is not empty and there is at least still one future in the values map, log the tasks and remove the ones that are not running anymore + if (!scheduledParticipationTasks.isEmpty() && scheduledParticipationTasks.values().stream().anyMatch(set -> !set.isEmpty())) { + log.info(" Scheduled Participation Tasks:"); + scheduledParticipationTasks.forEach((key, taskNames) -> { + taskNames.removeIf(taskName -> { + long delay = taskName.future().getDelay(TimeUnit.SECONDS); + var state = taskName.future().state(); + Instant scheduledTime = Instant.now().plusSeconds(delay); + ZonedDateTime zonedScheduledTime = scheduledTime.atZone(systemDefault()); + String formattedTime = zonedScheduledTime.format(formatter); + log.info(" Exercise: {}, Participation: {}, Lifecycle: {}, Name: {}, Scheduled Run Time: {}, State: {}, Remaining Delay: {} s", key.exerciseId(), + key.participationId(), key.lifecycle(), taskName.name(), formattedTime, state, delay); + return state != Future.State.RUNNING; + }); + }); + } + + // clean up empty entries in the map + scheduledParticipationTasks.entrySet().removeIf(entry -> entry.getValue().isEmpty()); + + }, Duration.ofSeconds(15)); + } + + private void addScheduledExerciseTasks(Exercise exercise, ExerciseLifecycle lifecycle, Set> futures, String name) { + ExerciseLifecycleKey task = new ExerciseLifecycleKey(exercise.getId(), lifecycle); + scheduledExerciseTasks.put(task, convert(futures, name)); } - private void addScheduledTask(Exercise exercise, ExerciseLifecycle lifecycle, Set> futures) { - Tuple taskId = new Tuple<>(exercise.getId(), lifecycle); - scheduledExerciseTasks.put(taskId, futures); + private Set convert(Set> futures, String name) { + return futures.stream().map(future -> new ScheduledTaskName(future, name)).collect(Collectors.toSet()); } - private void removeScheduledTask(Long exerciseId, ExerciseLifecycle lifecycle) { - Tuple taskId = new Tuple<>(exerciseId, lifecycle); - scheduledExerciseTasks.remove(taskId); + private void removeScheduledExerciseTask(Long exerciseId, ExerciseLifecycle lifecycle) { + ExerciseLifecycleKey task = new ExerciseLifecycleKey(exerciseId, lifecycle); + scheduledExerciseTasks.remove(task); } - private void addScheduledTask(Participation participation, ParticipationLifecycle lifecycle, Set> futures) { - Triple taskId = Triple.of(participation.getExercise().getId(), participation.getId(), lifecycle); - scheduledParticipationTasks.put(taskId, futures); + private void addScheduledParticipationTask(Participation participation, ParticipationLifecycle lifecycle, Set> futures, String name) { + ParticipationLifecycleKey task = new ParticipationLifecycleKey(participation.getExercise().getId(), participation.getId(), lifecycle); + scheduledParticipationTasks.put(task, convert(futures, name)); } - private void removeScheduledTask(Long exerciseId, Long participationId, ParticipationLifecycle lifecycle) { - Triple taskId = Triple.of(exerciseId, participationId, lifecycle); - scheduledParticipationTasks.remove(taskId); + private void removeScheduledParticipationTask(Long exerciseId, Long participationId, ParticipationLifecycle lifecycle) { + ParticipationLifecycleKey task = new ParticipationLifecycleKey(exerciseId, participationId, lifecycle); + scheduledParticipationTasks.remove(task); } /** @@ -72,13 +182,14 @@ private void removeScheduledTask(Long exerciseId, Long participationId, Particip * @param exercise Exercise * @param lifecycle ExerciseLifecycle * @param task Runnable task to be executed on the lifecycle hook + * @param name Name of the task */ - public void scheduleTask(Exercise exercise, ExerciseLifecycle lifecycle, Runnable task) { + public void scheduleExerciseTask(Exercise exercise, ExerciseLifecycle lifecycle, Runnable task, String name) { // check if already scheduled for exercise. if so, cancel. // no exercise should be scheduled more than once. cancelScheduledTaskForLifecycle(exercise.getId(), lifecycle); ScheduledFuture scheduledTask = exerciseLifecycleService.scheduleTask(exercise, lifecycle, task); - addScheduledTask(exercise, lifecycle, Set.of(scheduledTask)); + addScheduledExerciseTasks(exercise, lifecycle, new HashSet<>(List.of(scheduledTask)), name); } /** @@ -88,13 +199,29 @@ public void scheduleTask(Exercise exercise, ExerciseLifecycle lifecycle, Runnabl * @param batch QuizBatch * @param lifecycle ExerciseLifecycle * @param task Runnable task to be executed on the lifecycle hook + * @param name Name of the task */ - public void scheduleTask(QuizExercise exercise, QuizBatch batch, ExerciseLifecycle lifecycle, Runnable task) { + public void scheduleExerciseTask(QuizExercise exercise, QuizBatch batch, ExerciseLifecycle lifecycle, Runnable task, String name) { // check if already scheduled for exercise. if so, cancel. // no exercise should be scheduled more than once. cancelScheduledTaskForLifecycle(exercise.getId(), lifecycle); ScheduledFuture scheduledTask = exerciseLifecycleService.scheduleTask(exercise, batch, lifecycle, task); - addScheduledTask(exercise, lifecycle, Set.of(scheduledTask)); + addScheduledExerciseTasks(exercise, lifecycle, new HashSet<>(List.of(scheduledTask)), name); + } + + /** + * Schedule a set of tasks for the given Exercise for the provided ExerciseLifecycle at the given times. + * + * @param exercise Exercise + * @param lifecycle ExerciseLifecycle + * @param scheduledTask One runnable tasks to be executed at the associated ZonedDateTimes + */ + public void scheduleExerciseTask(Exercise exercise, ExerciseLifecycle lifecycle, Tuple scheduledTask, String name) { + // check if already scheduled for exercise. if so, cancel. + // no exercise should be scheduled more than once for each lifecycle + cancelScheduledTaskForLifecycle(exercise.getId(), lifecycle); + ScheduledFuture scheduledFuture = exerciseLifecycleService.scheduleTask(exercise, lifecycle, scheduledTask.second()); + addScheduledExerciseTasks(exercise, lifecycle, new HashSet<>(List.of(scheduledFuture)), name); } /** @@ -102,14 +229,15 @@ public void scheduleTask(QuizExercise exercise, QuizBatch batch, ExerciseLifecyc * * @param exercise Exercise * @param lifecycle ExerciseLifecycle - * @param tasks Runnable tasks to be executed at the associated ZonedDateTimes + * @param tasks Runnable tasks to be executed at the associated ZonedDateTimes, must be a mutable set + * @param name Name of the task */ - public void scheduleTask(Exercise exercise, ExerciseLifecycle lifecycle, Set> tasks) { + public void scheduleExerciseTask(Exercise exercise, ExerciseLifecycle lifecycle, Set> tasks, String name) { // check if already scheduled for exercise. if so, cancel. // no exercise should be scheduled more than once. cancelScheduledTaskForLifecycle(exercise.getId(), lifecycle); Set> scheduledTasks = exerciseLifecycleService.scheduleMultipleTasks(exercise, lifecycle, tasks); - addScheduledTask(exercise, lifecycle, scheduledTasks); + addScheduledExerciseTasks(exercise, lifecycle, scheduledTasks, name); } /** @@ -118,10 +246,12 @@ public void scheduleTask(Exercise exercise, ExerciseLifecycle lifecycle, Set addScheduledTask(participation, lifecycle, Set.of(scheduledTask))); + participationLifecycleService.scheduleTask(participation, lifecycle, task) + .ifPresent(scheduledTask -> addScheduledParticipationTask(participation, lifecycle, new HashSet<>(List.of(scheduledTask)), name)); } /** @@ -133,30 +263,21 @@ public void scheduleParticipationTask(Participation participation, Participation * @param lifecycle the lifecycle (e.g. release, due date) for which the schedule should be canceled */ public void cancelScheduledTaskForLifecycle(Long exerciseId, ExerciseLifecycle lifecycle) { - Tuple taskId = new Tuple<>(exerciseId, lifecycle); - Set> futures = scheduledExerciseTasks.get(taskId); - if (futures != null) { - log.debug("Cancelling scheduled task {} for Exercise (#{}).", lifecycle, exerciseId); - futures.forEach(future -> future.cancel(true)); - removeScheduledTask(exerciseId, lifecycle); + var task = new ExerciseLifecycleKey(exerciseId, lifecycle); + var taskNames = scheduledExerciseTasks.get(task); + if (taskNames != null) { + log.info("Cancelling scheduled task {} for Exercise (#{}).", lifecycle, exerciseId); + taskNames.forEach(taskName -> taskName.future().cancel(true)); + removeScheduledExerciseTask(exerciseId, lifecycle); } ParticipationLifecycle.fromExerciseLifecycle(lifecycle).ifPresent(participationLifecycle -> { - final Stream participationIds = getScheduledParticipationIdsForExercise(exerciseId); + final Stream participationIds = scheduledParticipationTasks.keySet().stream().map(ParticipationLifecycleKey::exerciseId) + .filter(scheduledExerciseId -> Objects.equals(scheduledExerciseId, exerciseId)); participationIds.forEach(participationId -> cancelScheduledTaskForParticipationLifecycle(exerciseId, participationId, participationLifecycle)); }); } - /** - * Finds all individual participations that belong to the given exercise and are scheduled. - * - * @param exerciseId the participations belong to. - * @return a stream of the IDs of participations. - */ - private Stream getScheduledParticipationIdsForExercise(Long exerciseId) { - return scheduledParticipationTasks.keySet().stream().map(Triple::getLeft).filter(scheduledExerciseId -> Objects.equals(scheduledExerciseId, exerciseId)); - } - /** * Cancel possible schedules tasks for a provided participation. * @@ -165,12 +286,12 @@ private Stream getScheduledParticipationIdsForExercise(Long exerciseId) { * @param lifecycle the lifecycle (e.g. release, due date) for which the schedule should be canceled */ public void cancelScheduledTaskForParticipationLifecycle(Long exerciseId, Long participationId, ParticipationLifecycle lifecycle) { - Triple taskId = Triple.of(exerciseId, participationId, lifecycle); - Set> futures = scheduledParticipationTasks.get(taskId); - if (futures != null) { + var task = new ParticipationLifecycleKey(exerciseId, participationId, lifecycle); + Set taskNames = scheduledParticipationTasks.get(task); + if (taskNames != null) { log.debug("Cancelling scheduled task {} for Participation (#{}).", lifecycle, participationId); - futures.forEach(future -> future.cancel(true)); - removeScheduledTask(exerciseId, participationId, lifecycle); + taskNames.forEach(taskName -> taskName.future().cancel(true)); + removeScheduledParticipationTask(exerciseId, participationId, lifecycle); } } @@ -190,8 +311,8 @@ public void cancelAllScheduledParticipationTasks(Long exerciseId, Long participa * Cancels all futures tasks, only use this for testing purposes */ public void clearAllTasks() { - scheduledParticipationTasks.values().forEach(futures -> futures.forEach(future -> future.cancel(true))); - scheduledExerciseTasks.values().forEach(futures -> futures.forEach(future -> future.cancel(true))); + scheduledParticipationTasks.values().forEach(taskNames -> taskNames.forEach(taskName -> taskName.future().cancel(true))); + scheduledExerciseTasks.values().forEach(taskNames -> taskNames.forEach(taskName -> taskName.future().cancel(true))); scheduledParticipationTasks.clear(); scheduledExerciseTasks.clear(); } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/util/Tuple.java b/src/main/java/de/tum/cit/aet/artemis/core/util/Tuple.java index aac92784b327..ac23fbb1feac 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/util/Tuple.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/util/Tuple.java @@ -1,10 +1,12 @@ package de.tum.cit.aet.artemis.core.util; +import java.io.Serializable; + /** * Immutable tuple object. * - * @param first param. - * @param second param. + * @param first param. + * @param second param. */ -public record Tuple(X x, Y y) { +public record Tuple(F first, S second) implements Serializable { } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminScheduleResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminScheduleResource.java new file mode 100644 index 000000000000..0d697550f107 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/admin/AdminScheduleResource.java @@ -0,0 +1,49 @@ +package de.tum.cit.aet.artemis.core.web.admin; + +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; +import static tech.jhipster.web.util.PaginationUtil.generatePaginationHttpHeaders; + +import java.util.List; + +import org.springframework.context.annotation.Profile; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import de.tum.cit.aet.artemis.core.security.annotations.EnforceAdmin; +import de.tum.cit.aet.artemis.core.service.ScheduleService; + +/** + * REST controller for getting the audit events. + */ +@Profile(PROFILE_CORE) +@EnforceAdmin +@RestController +@RequestMapping("api/admin/") +public class AdminScheduleResource { + + private final ScheduleService scheduleService; + + public AdminScheduleResource(ScheduleService scheduleService) { + this.scheduleService = scheduleService; + } + + /** + * GET /exercise-schedules : get a page of scheduled events. + * + * @param pageable the pagination information + * @return the ResponseEntity with status 200 (OK) and the list of ScheduledExerciseEvents in body + */ + @GetMapping("exercise-schedules") + public ResponseEntity> getAll(Pageable pageable) { + Page page = scheduleService.findAllExerciseEvents(pageable); + HttpHeaders headers = generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/domain/ExerciseLifecycle.java b/src/main/java/de/tum/cit/aet/artemis/exercise/domain/ExerciseLifecycle.java index 812b7a6534c7..fe7d3e1c1c32 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/domain/ExerciseLifecycle.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/domain/ExerciseLifecycle.java @@ -8,6 +8,19 @@ import de.tum.cit.aet.artemis.quiz.domain.QuizExercise; public enum ExerciseLifecycle implements IExerciseLifecycle { + SHORTLY_BEFORE_RELEASE { + + @Override + public ZonedDateTime getDateFromExercise(Exercise exercise) { + return exercise.getReleaseDate() != null ? exercise.getReleaseDate().minusSeconds(15) : null; + } + + @Override + public ZonedDateTime getDateFromQuizBatch(QuizBatch quizBatch, QuizExercise quizExercise) { + return getDateFromExercise(quizExercise); + } + }, + RELEASE { @Override diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseLifecycleService.java b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseLifecycleService.java index 48387319740a..297611b2e23c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseLifecycleService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseLifecycleService.java @@ -47,7 +47,7 @@ public ExerciseLifecycleService(@Qualifier("taskScheduler") TaskScheduler schedu */ public ScheduledFuture scheduleTask(Exercise exercise, ZonedDateTime lifecycleDate, ExerciseLifecycle lifecycle, Runnable task) { final ScheduledFuture future = scheduler.schedule(task, lifecycleDate.toInstant()); - log.debug("Scheduled Task for Exercise \"{}\" (#{}) to trigger on {}.", exercise.getTitle(), exercise.getId(), lifecycle); + log.info("Scheduled Task for Exercise \"{}\" (#{}) to trigger on {} - {}", exercise.getTitle(), exercise.getId(), lifecycle, lifecycleDate); return future; } @@ -101,10 +101,10 @@ public ScheduledFuture scheduleTask(QuizExercise exercise, QuizBatch batch, E public Set> scheduleMultipleTasks(Exercise exercise, ExerciseLifecycle lifecycle, Set> tasks) { final Set> futures = new HashSet<>(); for (var task : tasks) { - var future = scheduler.schedule(task.y(), task.x().toInstant()); + var future = scheduler.schedule(task.second(), task.first().toInstant()); futures.add(future); } - log.debug("Scheduled {} Tasks for Exercise \"{}\" (#{}) to trigger on {}.", tasks.size(), exercise.getTitle(), exercise.getId(), lifecycle.toString()); + log.info("Scheduled {} Tasks for Exercise \"{}\" (#{}) to trigger on {}.", tasks.size(), exercise.getTitle(), exercise.getId(), lifecycle.toString()); return futures; } } diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java b/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java index 090af5a73bdd..01b49ce9e40d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java @@ -260,7 +260,7 @@ public ResponseEntity startParticipation(@PathVariable Long exerc // 2) create a scheduled lock operation (see ProgrammingExerciseScheduleService) // var task = programmingExerciseScheduleService.lockStudentRepository(participation); // 3) add the task to the schedule service - // scheduleService.scheduleTask(exercise, ExerciseLifecycle.DUE, task); + // scheduleService.scheduleExerciseTask(exercise, ExerciseLifecycle.DUE, task); } // remove sensitive information before sending participation to the client diff --git a/src/main/java/de/tum/cit/aet/artemis/modeling/service/ModelingExerciseScheduleService.java b/src/main/java/de/tum/cit/aet/artemis/modeling/service/ModelingExerciseScheduleService.java index c6a911757e45..8c800e5a3366 100644 --- a/src/main/java/de/tum/cit/aet/artemis/modeling/service/ModelingExerciseScheduleService.java +++ b/src/main/java/de/tum/cit/aet/artemis/modeling/service/ModelingExerciseScheduleService.java @@ -142,7 +142,7 @@ private void scheduleCourseExercise(ModelingExercise exercise) { // For any course exercise that needsToBeScheduled (buildAndTestAfterDueDate and/or manual assessment) if (exercise.getDueDate() != null && ZonedDateTime.now().isBefore(exercise.getDueDate())) { - scheduleService.scheduleTask(exercise, ExerciseLifecycle.DUE, () -> buildModelingClusters(exercise).run()); + scheduleService.scheduleExerciseTask(exercise, ExerciseLifecycle.DUE, () -> buildModelingClusters(exercise).run(), "build modeling clusters after due date"); log.debug("Scheduled build modeling clusters after due date for Modeling Exercise '{}' (#{}) for {}.", exercise.getTitle(), exercise.getId(), exercise.getDueDate()); } else { @@ -160,7 +160,8 @@ private void scheduleExamExercise(ModelingExercise exercise) { if (ZonedDateTime.now().isBefore(examDateService.getLatestIndividualExamEndDateWithGracePeriod(exam))) { var buildDate = endDate.plusMinutes(EXAM_END_WAIT_TIME_FOR_COMPASS_MINUTES); exercise.setClusterBuildDate(buildDate); - scheduleService.scheduleTask(exercise, ExerciseLifecycle.BUILD_COMPASS_CLUSTERS_AFTER_EXAM, () -> buildModelingClusters(exercise).run()); + scheduleService.scheduleExerciseTask(exercise, ExerciseLifecycle.BUILD_COMPASS_CLUSTERS_AFTER_EXAM, () -> buildModelingClusters(exercise).run(), + "build modeling clusters after exam"); } log.debug("Scheduled Exam Modeling Exercise '{}' (#{}).", exercise.getTitle(), exercise.getId()); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseScheduleService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseScheduleService.java index ff4d9c80609f..b892012fd201 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseScheduleService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseScheduleService.java @@ -6,8 +6,6 @@ import java.time.Instant; import java.time.ZonedDateTime; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.NoSuchElementException; @@ -30,7 +28,6 @@ import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.annotation.Profile; import org.springframework.context.event.EventListener; -import org.springframework.core.env.Environment; import org.springframework.scheduling.TaskScheduler; import org.springframework.stereotype.Service; @@ -42,6 +39,7 @@ import de.tum.cit.aet.artemis.core.config.Constants; import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; import de.tum.cit.aet.artemis.core.security.SecurityUtils; +import de.tum.cit.aet.artemis.core.service.ProfileService; import de.tum.cit.aet.artemis.core.service.ScheduleService; import de.tum.cit.aet.artemis.core.util.Tuple; import de.tum.cit.aet.artemis.exam.domain.Exam; @@ -60,7 +58,6 @@ import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseStudentParticipationRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTestCaseRepository; -import tech.jhipster.config.JHipsterConstants; @Service @Profile(PROFILE_SCHEDULING) @@ -70,8 +67,6 @@ public class ProgrammingExerciseScheduleService implements IExerciseScheduleServ private final ScheduleService scheduleService; - private final Environment env; - private final ProgrammingExerciseRepository programmingExerciseRepository; private final ProgrammingExerciseTestCaseRepository programmingExerciseTestCaseRepository; @@ -102,12 +97,14 @@ public class ProgrammingExerciseScheduleService implements IExerciseScheduleServ private final TaskScheduler scheduler; + private final ProfileService profileService; + public ProgrammingExerciseScheduleService(ScheduleService scheduleService, ProgrammingExerciseRepository programmingExerciseRepository, ProgrammingExerciseTestCaseRepository programmingExerciseTestCaseRepository, ResultRepository resultRepository, ParticipationRepository participationRepository, - ProgrammingExerciseStudentParticipationRepository programmingExerciseParticipationRepository, Environment env, ProgrammingTriggerService programmingTriggerService, + ProgrammingExerciseStudentParticipationRepository programmingExerciseParticipationRepository, ProgrammingTriggerService programmingTriggerService, ProgrammingExerciseGradingService programmingExerciseGradingService, GroupNotificationService groupNotificationService, ExamDateService examDateService, ProgrammingExerciseParticipationService programmingExerciseParticipationService, ExerciseDateService exerciseDateService, ExamRepository examRepository, - StudentExamRepository studentExamRepository, GitService gitService, @Qualifier("taskScheduler") TaskScheduler scheduler) { + StudentExamRepository studentExamRepository, GitService gitService, @Qualifier("taskScheduler") TaskScheduler scheduler, ProfileService profileService) { this.scheduleService = scheduleService; this.programmingExerciseRepository = programmingExerciseRepository; this.programmingExerciseTestCaseRepository = programmingExerciseTestCaseRepository; @@ -122,9 +119,9 @@ public ProgrammingExerciseScheduleService(ScheduleService scheduleService, Progr this.examDateService = examDateService; this.programmingExerciseParticipationService = programmingExerciseParticipationService; this.programmingExerciseGradingService = programmingExerciseGradingService; - this.env = env; this.gitService = gitService; this.scheduler = scheduler; + this.profileService = profileService; } @EventListener(ApplicationReadyEvent.class) @@ -136,8 +133,7 @@ public void applicationReady() { @Override public void scheduleRunningExercisesOnStartup() { try { - Collection activeProfiles = Arrays.asList(env.getActiveProfiles()); - if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) { + if (profileService.isDevActive()) { // only execute this on production server, i.e. when the prod profile is active // NOTE: if you want to test this locally, please comment it out, but do not commit the changes return; @@ -299,9 +295,8 @@ private void scheduleCourseExercise(ProgrammingExercise exercise) { private void scheduleTemplateCommitCombination(ProgrammingExercise exercise) { if (exercise.getReleaseDate() != null) { - var scheduledRunnable = Set.of(new Tuple<>(exercise.getReleaseDate().minusSeconds(Constants.SECONDS_BEFORE_RELEASE_DATE_FOR_COMBINING_TEMPLATE_COMMITS), - combineTemplateCommitsForExercise(exercise))); - scheduleService.scheduleTask(exercise, ExerciseLifecycle.RELEASE, scheduledRunnable); + scheduleService.scheduleExerciseTask(exercise, ExerciseLifecycle.SHORTLY_BEFORE_RELEASE, () -> combineTemplateCommitsForExercise(exercise).run(), + "combine template commits"); log.info("Scheduled combining template commits before release date for Programming Exercise \"{}\" (#{}) for {}.", exercise.getTitle(), exercise.getId(), exercise.getReleaseDate()); } @@ -309,19 +304,32 @@ private void scheduleTemplateCommitCombination(ProgrammingExercise exercise) { private void scheduleDueDateLockAndScoreUpdate(ProgrammingExercise exercise) { final boolean updateScores = isScoreUpdateAfterDueDateNeeded(exercise); + final boolean isLocalVC = profileService.isLocalVcsActive(); - scheduleService.scheduleTask(exercise, ExerciseLifecycle.DUE, () -> { - lockStudentRepositoriesAndParticipationsRegularDueDate(exercise).run(); - if (updateScores) { - updateStudentScoresRegularDueDate(exercise).run(); + if (isLocalVC) { + if (!updateScores) { + // no scheduling is needed + return; } - }); + else { + scheduleService.scheduleExerciseTask(exercise, ExerciseLifecycle.DUE, () -> updateStudentScoresRegularDueDate(exercise).run(), "update student scores"); + } + } + else { + scheduleService.scheduleExerciseTask(exercise, ExerciseLifecycle.DUE, () -> { + lockStudentRepositoriesAndParticipationsRegularDueDate(exercise).run(); + if (updateScores) { + updateStudentScoresRegularDueDate(exercise).run(); + } + }, "lock student repositories and participations"); + } log.debug("Scheduled lock student repositories after due date for Programming Exercise '{}' (#{}) for {}.", exercise.getTitle(), exercise.getId(), exercise.getDueDate()); } private void scheduleBuildAndTestAfterDueDate(ProgrammingExercise exercise) { - scheduleService.scheduleTask(exercise, ExerciseLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE, buildAndTestRunnableForExercise(exercise)); + scheduleService.scheduleExerciseTask(exercise, ExerciseLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE, buildAndTestRunnableForExercise(exercise), + "build and test student submissions"); log.debug("Scheduled build and test for student submissions after due date for Programming Exercise '{}' (#{}) for {}.", exercise.getTitle(), exercise.getId(), exercise.getBuildAndTestStudentSubmissionsAfterDueDate()); } @@ -337,8 +345,7 @@ private void scheduleBuildAndTestAfterDueDate(ProgrammingExercise exercise) { private void scheduleParticipationTasks(final ProgrammingExercise exercise, final ZonedDateTime now) { final boolean isScoreUpdateNeeded = isScoreUpdateAfterDueDateNeeded(exercise); - final List participations = programmingExerciseParticipationRepository - .findWithSubmissionsAndTeamStudentsByExerciseId(exercise.getId()); + final var participations = programmingExerciseParticipationRepository.findWithSubmissionsAndTeamStudentsByExerciseId(exercise.getId()); for (final var participation : participations) { if (exercise.getDueDate() == null || participation.getIndividualDueDate() == null) { scheduleService.cancelAllScheduledParticipationTasks(exercise.getId(), participation.getId()); @@ -349,8 +356,8 @@ private void scheduleParticipationTasks(final ProgrammingExercise exercise, fina } } - private void scheduleParticipationWithIndividualDueDate(final ZonedDateTime now, final ProgrammingExercise exercise, - final ProgrammingExerciseStudentParticipation participation, boolean isScoreUpdateNeeded) { + private void scheduleParticipationWithIndividualDueDate(ZonedDateTime now, ProgrammingExercise exercise, ProgrammingExerciseStudentParticipation participation, + boolean isScoreUpdateNeeded) { final boolean isBeforeDueDate = now.isBefore(participation.getIndividualDueDate()); // Update scores on due date if (isBeforeDueDate) { @@ -372,15 +379,30 @@ private void scheduleParticipationWithIndividualDueDate(final ZonedDateTime now, } private void scheduleAfterDueDateForParticipation(ProgrammingExerciseStudentParticipation participation, boolean isScoreUpdateNeeded) { - scheduleService.scheduleParticipationTask(participation, ParticipationLifecycle.DUE, () -> { - lockStudentRepositoryAndParticipation(participation).run(); - + if (!profileService.isLocalVcsActive()) { if (isScoreUpdateNeeded) { - final List updatedResult = programmingExerciseGradingService.updateParticipationResults(participation); - resultRepository.saveAll(updatedResult); + scheduleService.scheduleParticipationTask(participation, ParticipationLifecycle.DUE, () -> { + lockStudentRepositoryAndParticipation(participation).run(); + final List updatedResult = programmingExerciseGradingService.updateParticipationResults(participation); + resultRepository.saveAll(updatedResult); + }, "lock student repository and update student scores"); + log.debug("Scheduled task to lock student repository and update student scores {} at the individual due date.", participation.getId()); } - }); - log.debug("Scheduled task to lock repository for participation {} at the individual due date.", participation.getId()); + else { + scheduleService.scheduleParticipationTask(participation, ParticipationLifecycle.DUE, () -> lockStudentRepositoryAndParticipation(participation).run(), + "lock student repository"); + log.debug("Scheduled task to lock repository for participation {} at the individual due date.", participation.getId()); + } + } + else { + if (isScoreUpdateNeeded) { + scheduleService.scheduleParticipationTask(participation, ParticipationLifecycle.DUE, () -> { + final List updatedResult = programmingExerciseGradingService.updateParticipationResults(participation); + resultRepository.saveAll(updatedResult); + }, "update student scores"); + } + log.debug("Scheduled task to update student scores {} at the individual due date.", participation.getId()); + } } private void scheduleBuildAndTestAfterDueDateForParticipation(ProgrammingExerciseStudentParticipation participation) { @@ -395,7 +417,7 @@ private void scheduleBuildAndTestAfterDueDateForParticipation(ProgrammingExercis log.error("Programming participation with id {} in exercise {} is no longer available in database for use in scheduled task.", participation.getId(), exercise.getId()); } - }); + }, "build and test student submission"); } private boolean isScoreUpdateAfterDueDateNeeded(ProgrammingExercise exercise) { @@ -421,24 +443,29 @@ private void scheduleExamExercise(ProgrammingExercise exercise) { if (now.isBefore(unlockDate)) { // Schedule unlocking of student repositories // Uses the custom exam unlock date rather than the of the exercise's lifecycle - scheduleService.scheduleTask(exercise, ExerciseLifecycle.RELEASE, Set.of(new Tuple<>(unlockDate, unlockAllStudentRepositoriesAndParticipations(exercise)))); + if (!profileService.isLocalVcsActive()) { + scheduleService.scheduleExerciseTask(exercise, ExerciseLifecycle.RELEASE, new Tuple<>(unlockDate, unlockAllStudentRepositoriesAndParticipations(exercise)), + "unlock student repositories"); + } } // DURING EXAM else if (now.isBefore(examDateService.getLatestIndividualExamEndDate(exam))) { // This is only a backup (e.g. a crash of this node and restart during the exam) // TODO: Christian Femers: this can lead to a weird edge case after the normal exam end date and before the last individual exam end date (in case of working time // extensions) - var scheduledRunnable = Set.of( - new Tuple<>(now.plusSeconds(Constants.SECONDS_AFTER_RELEASE_DATE_FOR_UNLOCKING_STUDENT_EXAM_REPOS), unlockAllStudentRepositoriesAndParticipations(exercise))); - scheduleService.scheduleTask(exercise, ExerciseLifecycle.RELEASE, scheduledRunnable); - + if (!profileService.isLocalVcsActive()) { + var scheduledRunnable = new Tuple<>(now.plusSeconds(Constants.SECONDS_AFTER_RELEASE_DATE_FOR_UNLOCKING_STUDENT_EXAM_REPOS), + unlockAllStudentRepositoriesAndParticipations(exercise)); + scheduleService.scheduleExerciseTask(exercise, ExerciseLifecycle.RELEASE, scheduledRunnable, "unlock student repositories"); + } // Re-schedule the locking of student repositories based on the individual working time rescheduleProgrammingExerciseDuringExamConduction(exercise); } // NOTHING TO DO AFTER EXAM if (exercise.getBuildAndTestStudentSubmissionsAfterDueDate() != null && now.isBefore(exercise.getBuildAndTestStudentSubmissionsAfterDueDate())) { - scheduleService.scheduleTask(exercise, ExerciseLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE, buildAndTestRunnableForExercise(exercise)); + scheduleService.scheduleExerciseTask(exercise, ExerciseLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE, () -> buildAndTestRunnableForExercise(exercise).run(), + "build and test student submissions"); } else { scheduleService.cancelScheduledTaskForLifecycle(exercise.getId(), ExerciseLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE); @@ -449,6 +476,7 @@ else if (now.isBefore(examDateService.getLatestIndividualExamEndDate(exam))) { @NotNull private Runnable combineTemplateCommitsForExercise(ProgrammingExercise exercise) { return () -> { + log.info("Start combine template commits for programming exercise {}.", exercise.getId()); SecurityUtils.setAuthorizationObject(); try { ProgrammingExercise programmingExerciseWithTemplateParticipation = programmingExerciseRepository @@ -457,7 +485,7 @@ private Runnable combineTemplateCommitsForExercise(ProgrammingExercise exercise) log.info("Combined template repository commits of programming exercise {}.", programmingExerciseWithTemplateParticipation.getId()); } catch (GitAPIException e) { - log.error("Failed to communicate with GitAPI for combining template commits of exercise {}", exercise.getId(), e); + log.error("Failed to communicate with GitService for combining template commits of exercise {}", exercise.getId(), e); } }; } @@ -739,7 +767,7 @@ public Runnable runUnlockOperation(ProgrammingExercise exercise, Consumer> futureIndividualDueDates = individualDueDates.stream() - .filter(tuple -> tuple.x() != null && ZonedDateTime.now().isBefore(tuple.x())).collect(Collectors.toSet()); + .filter(tuple -> tuple.first() != null && ZonedDateTime.now().isBefore(tuple.first())).collect(Collectors.toSet()); scheduleIndividualRepositoryAndParticipationLockTasks(exercise, futureIndividualDueDates); }); } @@ -839,23 +867,25 @@ public Runnable unlockAllStudentParticipationsWithEarlierStartDateAndLaterDueDat */ private void scheduleIndividualRepositoryAndParticipationLockTasks(ProgrammingExercise exercise, Set> individualParticipationsWithDueDates) { + if (profileService.isLocalVcsActive()) { + // no scheduling needed + return; + } // 1. Group all participations by due date // TODO: use student exams for safety if some participations are not pre-generated - var participationsGroupedByDueDate = individualParticipationsWithDueDates.stream().filter(tuple -> tuple.x() != null) - .collect(Collectors.groupingBy(Tuple::x, Collectors.mapping(Tuple::y, Collectors.toSet()))); + var participationsGroupedByDueDate = individualParticipationsWithDueDates.stream().filter(tuple -> tuple.first() != null) + .collect(Collectors.groupingBy(Tuple::first, Collectors.mapping(Tuple::second, Collectors.toSet()))); // 2. Transform those groups into lock-repository tasks with times Set> tasks = participationsGroupedByDueDate.entrySet().stream().map(entry -> { // Check that this participation is planned to be locked and has still the same due date Predicate lockingCondition = participation -> entry.getValue().contains(participation) && entry.getKey().equals(exerciseDateService.getIndividualDueDate(exercise, participation)); - - var task = lockStudentRepositoriesAndParticipations(exercise, lockingCondition); - return new Tuple<>(entry.getKey(), task); + return new Tuple<>(entry.getKey(), lockStudentRepositoriesAndParticipations(exercise, lockingCondition)); }).collect(Collectors.toSet()); // 3. Schedule all tasks - scheduleService.scheduleTask(exercise, ExerciseLifecycle.DUE, tasks); + scheduleService.scheduleExerciseTask(exercise, ExerciseLifecycle.DUE, tasks, "lock student repositories and participations"); } /** @@ -893,6 +923,10 @@ private void rescheduleProgrammingExerciseDuringExamConduction(ProgrammingExerci * @param studentExamId the id of the student exam */ public void rescheduleStudentExamDuringConduction(Long studentExamId) { + if (profileService.isLocalVcsActive()) { + // no scheduling needed + return; + } StudentExam studentExam = studentExamRepository.findWithExercisesParticipationsSubmissionsById(studentExamId, false).orElseThrow(NoSuchElementException::new); // iterate over all programming exercises and its student participation in the student's exam @@ -910,7 +944,7 @@ public void rescheduleStudentExamDuringConduction(Long studentExamId) { // get the individual due date of the student's participation in the programming exercise ZonedDateTime dueDate = exerciseDateService.getIndividualDueDate(exercise, programmingParticipation); // schedule repository locks for each programming exercise - scheduleIndividualRepositoryAndParticipationLockTasks(exercise, Set.of(new Tuple<>(dueDate, programmingParticipation))); + scheduleIndividualRepositoryAndParticipationLockTasks(exercise, new HashSet<>(List.of(new Tuple<>(dueDate, programmingParticipation)))); }); } @@ -984,8 +1018,8 @@ private CompletableFuture> invokeO for (var future : futures) { future.whenComplete((participation, exception) -> { if (exception != null) { - log.error(String.format("'%s' failed for programming exercise with id %d for student repository with participation id %d", operationName, - programmingExercise.getId(), participation.getId()), exception); + log.error("'{}' failed for programming exercise with id {} for student repository with participation id {}", operationName, programmingExercise.getId(), + participation.getId(), exception); failedOperations.add(participation); } }); diff --git a/src/main/java/de/tum/cit/aet/artemis/quiz/service/QuizScheduleService.java b/src/main/java/de/tum/cit/aet/artemis/quiz/service/QuizScheduleService.java index 7aa104280256..819467dd700a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/quiz/service/QuizScheduleService.java +++ b/src/main/java/de/tum/cit/aet/artemis/quiz/service/QuizScheduleService.java @@ -89,7 +89,7 @@ public void scheduleQuizStart(QuizExercise quizExercise) { var quizBatch = quizExercise.getQuizBatches().stream().findAny(); if (quizBatch.isPresent() && quizBatch.get().getStartTime() != null) { if (quizBatch.get().getStartTime().isAfter(ZonedDateTime.now())) { - scheduleService.scheduleTask(quizExercise, quizBatch.get(), ExerciseLifecycle.START, () -> executeQuizStartNowTask(quizExercise.getId())); + scheduleService.scheduleExerciseTask(quizExercise, quizBatch.get(), ExerciseLifecycle.START, () -> executeQuizStartNowTask(quizExercise.getId()), "quiz start"); } } } @@ -104,8 +104,8 @@ public void scheduleQuizStart(QuizExercise quizExercise) { public void scheduleCalculateAllResults(QuizExercise quizExercise) { if (quizExercise.getDueDate() != null && !quizExercise.isQuizEnded()) { // we only schedule the task if the quiz is not over yet - scheduleService.scheduleTask(quizExercise, ExerciseLifecycle.DUE, - Set.of(new Tuple<>(quizExercise.getDueDate().plusSeconds(5), () -> quizSubmissionService.calculateAllResults(quizExercise.getId())))); + scheduleService.scheduleExerciseTask(quizExercise, ExerciseLifecycle.DUE, + new Tuple<>(quizExercise.getDueDate().plusSeconds(5), () -> quizSubmissionService.calculateAllResults(quizExercise.getId())), "calculate all quiz results"); } } diff --git a/src/test/java/de/tum/cit/aet/artemis/core/service/TitleCacheEvictionServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/core/service/TitleCacheEvictionServiceTest.java index 9f969fb29328..7701d7f37e19 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/service/TitleCacheEvictionServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/service/TitleCacheEvictionServiceTest.java @@ -212,19 +212,19 @@ private void testCacheEvicted(String cacheName, Supplier> for (var modifier : entityModifiers) { var objInCache = idTitleSupplier.get(); - cache.put(objInCache.x(), objInCache.y()); - var cacheValueWrapper = cache.get(objInCache.x()); + cache.put(objInCache.first(), objInCache.second()); + var cacheValueWrapper = cache.get(objInCache.first()); assertThat(cacheValueWrapper).isNotNull(); - assertThat(cacheValueWrapper.get()).isEqualTo(objInCache.y()); + assertThat(cacheValueWrapper.get()).isEqualTo(objInCache.second()); boolean shouldEvict = modifier.get(); - cacheValueWrapper = cache.get(objInCache.x()); + cacheValueWrapper = cache.get(objInCache.first()); if (shouldEvict) { assertThat(cacheValueWrapper).isNull(); } else { assertThat(cacheValueWrapper).isNotNull(); - assertThat(cacheValueWrapper.get()).isEqualTo(objInCache.y()); + assertThat(cacheValueWrapper.get()).isEqualTo(objInCache.second()); } } } diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseScheduleServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseScheduleServiceTest.java index b386fb22d033..be133cc21e70 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseScheduleServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseScheduleServiceTest.java @@ -342,8 +342,8 @@ void scheduleIndividualDueDateBetweenDueDateAndBuildAndTestDate() throws Excepti instanceMessageReceiveService.processScheduleProgrammingExercise(programmingExercise.getId()); - verify(scheduleService, timeout(DELAY_MS * 2)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any()); - verify(scheduleService, never()).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any()); + verify(scheduleService, timeout(DELAY_MS * 2)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any(), any()); + verify(scheduleService, never()).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any(), any()); // not yet locked on regular due date verify(programmingTriggerService, after(DELAY_MS * 2).never()).triggerInstructorBuildForExercise(programmingExercise.getId()); @@ -371,8 +371,9 @@ void scheduleIndividualDueDateAfterBuildAndTestDate() throws Exception { instanceMessageReceiveService.processScheduleProgrammingExercise(programmingExercise.getId()); // special scheduling for both lock and build and test - verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any()); - verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any()); + verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any(), any()); + verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any(), + any()); } @Test @@ -392,11 +393,11 @@ void scheduleIndividualDueDateTestsAfterDueDateNoBuildAndTestDate() throws Excep instanceMessageReceiveService.processScheduleProgrammingExercise(programmingExercise.getId()); - verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any()); - verify(scheduleService, never()).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any()); + verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any(), any()); + verify(scheduleService, never()).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any(), any()); - verify(scheduleService, timeout(TIMEOUT_MS)).scheduleTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class)); - verify(scheduleService, never()).scheduleTask(eq(programmingExercise), eq(ExerciseLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any(Runnable.class)); + verify(scheduleService, timeout(TIMEOUT_MS)).scheduleExerciseTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class), any()); + verify(scheduleService, never()).scheduleExerciseTask(eq(programmingExercise), eq(ExerciseLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any(Runnable.class), any()); } @Test @@ -415,11 +416,11 @@ void cancelAllSchedulesOnRemovingExerciseDueDate() throws Exception { instanceMessageReceiveService.processScheduleProgrammingExercise(programmingExercise.getId()); - verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any()); - verify(scheduleService, never()).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any()); + verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any(), any()); + verify(scheduleService, never()).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any(), any()); - verify(scheduleService, timeout(TIMEOUT_MS)).scheduleTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class)); - verify(scheduleService, never()).scheduleTask(eq(programmingExercise), eq(ExerciseLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any(Runnable.class)); + verify(scheduleService, timeout(TIMEOUT_MS)).scheduleExerciseTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class), any()); + verify(scheduleService, never()).scheduleExerciseTask(eq(programmingExercise), eq(ExerciseLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any(Runnable.class), any()); // remove due date and schedule again programmingExercise.setDueDate(null); @@ -452,8 +453,8 @@ void cancelIndividualSchedulesOnRemovingIndividualDueDate() throws Exception { instanceMessageReceiveService.processScheduleProgrammingExercise(programmingExercise.getId()); - verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any()); - verify(scheduleService, timeout(TIMEOUT_MS)).scheduleTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class)); + verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any(), any()); + verify(scheduleService, timeout(TIMEOUT_MS)).scheduleExerciseTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class), any()); // remove individual due date and schedule again participationIndividualDueDate.setIndividualDueDate(null); @@ -464,7 +465,7 @@ void cancelIndividualSchedulesOnRemovingIndividualDueDate() throws Exception { // called twice: first time when removing potential old schedules before scheduling, second time only cancelling verify(scheduleService, timeout(TIMEOUT_MS).times(2)).cancelScheduledTaskForParticipationLifecycle(programmingExercise.getId(), participationIndividualDueDate.getId(), ParticipationLifecycle.DUE); - verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any()); + verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any(), any()); } @Test @@ -480,8 +481,8 @@ void updateIndividualScheduleOnIndividualDueDateChange() throws Exception { instanceMessageReceiveService.processScheduleProgrammingExercise(programmingExercise.getId()); - verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any()); - verify(scheduleService, timeout(TIMEOUT_MS)).scheduleTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class)); + verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any(), any()); + verify(scheduleService, timeout(TIMEOUT_MS)).scheduleExerciseTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class), any()); // change individual due date and schedule again participationIndividualDueDate.setIndividualDueDate(nowPlusMillis(DELAY_MS)); @@ -491,7 +492,7 @@ void updateIndividualScheduleOnIndividualDueDateChange() throws Exception { // scheduling called twice, each scheduling cancels potential old schedules verify(scheduleService, timeout(TIMEOUT_MS).times(2)).cancelScheduledTaskForParticipationLifecycle(programmingExercise.getId(), participationIndividualDueDate.getId(), ParticipationLifecycle.DUE); - verify(scheduleService, timeout(TIMEOUT_MS).times(2)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any()); + verify(scheduleService, timeout(TIMEOUT_MS).times(2)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any(), any()); } @Test @@ -510,22 +511,22 @@ void keepIndividualScheduleOnExerciseDueDateChange() throws Exception { instanceMessageReceiveService.processScheduleProgrammingExercise(programmingExercise.getId()); - verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any()); - verify(scheduleService, never()).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any()); + verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any(), any()); + verify(scheduleService, never()).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any(), any()); - verify(scheduleService, timeout(TIMEOUT_MS)).scheduleTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class)); - verify(scheduleService, never()).scheduleTask(eq(programmingExercise), eq(ExerciseLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any(Runnable.class)); + verify(scheduleService, timeout(TIMEOUT_MS)).scheduleExerciseTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class), any()); + verify(scheduleService, never()).scheduleExerciseTask(eq(programmingExercise), eq(ExerciseLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any(Runnable.class), any()); // change exercise due date and schedule again programmingExercise.setDueDate(nowPlusMillis(DELAY_MS)); programmingExercise = programmingExerciseRepository.saveAndFlush(programmingExercise); instanceMessageReceiveService.processScheduleProgrammingExercise(programmingExercise.getId()); - verify(scheduleService, timeout(TIMEOUT_MS).times(2)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any()); - verify(scheduleService, never()).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any()); + verify(scheduleService, timeout(TIMEOUT_MS).times(2)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any(), any()); + verify(scheduleService, never()).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any(), any()); - verify(scheduleService, timeout(TIMEOUT_MS).times(2)).scheduleTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class)); - verify(scheduleService, never()).scheduleTask(eq(programmingExercise), eq(ExerciseLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any(Runnable.class)); + verify(scheduleService, timeout(TIMEOUT_MS).times(2)).scheduleExerciseTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class), any()); + verify(scheduleService, never()).scheduleExerciseTask(eq(programmingExercise), eq(ExerciseLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE), any(Runnable.class), any()); } @Test @@ -543,8 +544,8 @@ void shouldScheduleExerciseIfAnyIndividualDueDateInFuture() throws Exception { instanceMessageReceiveService.processScheduleProgrammingExercise(programmingExercise.getId()); - verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any()); - verify(scheduleService, never()).scheduleTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class)); + verify(scheduleService, timeout(TIMEOUT_MS)).scheduleParticipationTask(eq(participationIndividualDueDate), eq(ParticipationLifecycle.DUE), any(), any()); + verify(scheduleService, never()).scheduleExerciseTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class), any()); } @Test @@ -561,7 +562,8 @@ void shouldCancelAllTasksIfSchedulingNoLongerNeeded() throws Exception { instanceMessageReceiveService.processScheduleProgrammingExercise(programmingExercise.getId()); - verify(scheduleService, after(SCHEDULER_TASK_TRIGGER_DELAY_MS).never()).scheduleTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class)); + verify(scheduleService, after(SCHEDULER_TASK_TRIGGER_DELAY_MS).never()).scheduleExerciseTask(eq(programmingExercise), eq(ExerciseLifecycle.DUE), any(Runnable.class), + any()); verify(scheduleService, timeout(TIMEOUT_MS)).cancelScheduledTaskForLifecycle(programmingExercise.getId(), ExerciseLifecycle.RELEASE); verify(scheduleService, timeout(TIMEOUT_MS)).cancelScheduledTaskForLifecycle(programmingExercise.getId(), ExerciseLifecycle.DUE); verify(scheduleService, timeout(TIMEOUT_MS)).cancelScheduledTaskForLifecycle(programmingExercise.getId(), ExerciseLifecycle.BUILD_AND_TEST_AFTER_DUE_DATE); From 313fc520c3635e214cfe9592d8ce908b401f1fca Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Sun, 26 Jan 2025 20:34:27 +0100 Subject: [PATCH 4/5] fix arch test --- .../de/tum/cit/aet/artemis/core/service/ScheduleService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/ScheduleService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/ScheduleService.java index 2dc50adfc688..04685372079e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/ScheduleService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/ScheduleService.java @@ -92,7 +92,7 @@ public Page findAllExerciseEvents(Pageable pageable) { // Calculate the scheduled time from the future's delay var scheduledTime = ZonedDateTime.now().plusSeconds(task.future().getDelay(TimeUnit.SECONDS)); return new ScheduledExerciseEvent(entry.getKey().exerciseId(), entry.getKey().lifecycle(), task.name(), scheduledTime, task.future().state()); - })).sorted(Comparator.comparing(ScheduledExerciseEvent::scheduledTime)).collect(Collectors.toList()); + })).sorted(Comparator.comparing(ScheduledExerciseEvent::scheduledTime)).toList(); // Apply pagination int start = (int) pageable.getOffset(); From 32a1f489addecf98757fc8dd8cfdd0759ea82754 Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Sun, 2 Feb 2025 09:24:04 +0100 Subject: [PATCH 5/5] fix missing JavaDoc and lower logging again --- .../config/TaskSchedulingConfiguration.java | 5 +++ .../artemis/core/service/ScheduleService.java | 41 +++++++++++++++---- .../service/ExerciseLifecycleService.java | 4 +- .../ProgrammingExerciseScheduleService.java | 6 +-- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/TaskSchedulingConfiguration.java b/src/main/java/de/tum/cit/aet/artemis/core/config/TaskSchedulingConfiguration.java index c6d3fcdea04a..bb0fdac8fbf2 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/config/TaskSchedulingConfiguration.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/config/TaskSchedulingConfiguration.java @@ -18,6 +18,11 @@ public class TaskSchedulingConfiguration { private static final Logger log = LoggerFactory.getLogger(TaskSchedulingConfiguration.class); + /** + * Create a Task Scheduler with virtual threads and a pool size of 4. + * + * @return the Task Scheduler bean that can be injected into any service to schedule tasks + */ @Bean(name = "taskScheduler") public TaskScheduler taskScheduler() { log.debug("Creating Task Scheduler "); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/ScheduleService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/ScheduleService.java index 04685372079e..f8baea63aea0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/ScheduleService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/ScheduleService.java @@ -86,6 +86,12 @@ public ScheduleService(ExerciseLifecycleService exerciseLifecycleService, Partic this.taskScheduler = scheduler; } + /** + * Get all scheduled exercise events. + * + * @param pageable the pagination information + * @return a page of scheduled exercise events + */ public Page findAllExerciseEvents(Pageable pageable) { // Flatten the map into a list of ScheduledExerciseEvent var allEvents = scheduledExerciseTasks.entrySet().stream().flatMap(entry -> entry.getValue().stream().map(task -> { @@ -102,14 +108,34 @@ public Page findAllExerciseEvents(Pageable pageable) { return new PageImpl<>(paginatedEvents, pageable, allEvents.size()); } + /** + * Initializes and schedules periodic logging and cleanup tasks for scheduled exercises + * and participation tasks. This method is triggered automatically when the application + * is fully started. + * + *

+ * Every 15 seconds, this method: + *

+ * + *

+ * The scheduling mechanism ensures that outdated or completed tasks do not persist in + * memory unnecessarily while maintaining visibility into scheduled tasks. + */ @EventListener(ApplicationReadyEvent.class) public void startup() { taskScheduler.scheduleAtFixedRate(() -> { - log.info("Number of scheduled Exercise Tasks: {}", scheduledExerciseTasks.values().stream().mapToLong(Set::size).sum()); + log.debug("Number of scheduled Exercise Tasks: {}", scheduledExerciseTasks.values().stream().mapToLong(Set::size).sum()); // if the map is not empty and there is at least still one future in the values map, log the tasks and remove the ones that are not running anymore if (!scheduledExerciseTasks.isEmpty() && scheduledExerciseTasks.values().stream().anyMatch(set -> !set.isEmpty())) { - log.info(" Scheduled Exercise Tasks:"); + log.debug(" Scheduled Exercise Tasks:"); scheduledExerciseTasks.forEach((key, taskNames) -> { taskNames.removeIf(taskName -> { long delay = taskName.future().getDelay(TimeUnit.SECONDS); @@ -117,7 +143,7 @@ public void startup() { Instant scheduledTime = Instant.now().plusSeconds(delay); ZonedDateTime zonedScheduledTime = scheduledTime.atZone(systemDefault()); String formattedTime = zonedScheduledTime.format(formatter); - log.info(" Exercise: {}, Lifecycle: {}, Name: {}, Scheduled Run Time: {}, State: {}, Remaining Delay: {} s", key.exerciseId(), key.lifecycle(), + log.debug(" Exercise: {}, Lifecycle: {}, Name: {}, Scheduled Run Time: {}, State: {}, Remaining Delay: {} s", key.exerciseId(), key.lifecycle(), taskName.name(), formattedTime, state, delay); return state != Future.State.RUNNING; }); @@ -127,11 +153,11 @@ public void startup() { // clean up empty entries in the map scheduledExerciseTasks.entrySet().removeIf(entry -> entry.getValue().isEmpty()); - log.info("Number of scheduled Participation Tasks: {}", scheduledParticipationTasks.values().stream().mapToLong(Set::size).sum()); + log.debug("Number of scheduled Participation Tasks: {}", scheduledParticipationTasks.values().stream().mapToLong(Set::size).sum()); // if the map is not empty and there is at least still one future in the values map, log the tasks and remove the ones that are not running anymore if (!scheduledParticipationTasks.isEmpty() && scheduledParticipationTasks.values().stream().anyMatch(set -> !set.isEmpty())) { - log.info(" Scheduled Participation Tasks:"); + log.debug(" Scheduled Participation Tasks:"); scheduledParticipationTasks.forEach((key, taskNames) -> { taskNames.removeIf(taskName -> { long delay = taskName.future().getDelay(TimeUnit.SECONDS); @@ -139,7 +165,7 @@ public void startup() { Instant scheduledTime = Instant.now().plusSeconds(delay); ZonedDateTime zonedScheduledTime = scheduledTime.atZone(systemDefault()); String formattedTime = zonedScheduledTime.format(formatter); - log.info(" Exercise: {}, Participation: {}, Lifecycle: {}, Name: {}, Scheduled Run Time: {}, State: {}, Remaining Delay: {} s", key.exerciseId(), + log.debug(" Exercise: {}, Participation: {}, Lifecycle: {}, Name: {}, Scheduled Run Time: {}, State: {}, Remaining Delay: {} s", key.exerciseId(), key.participationId(), key.lifecycle(), taskName.name(), formattedTime, state, delay); return state != Future.State.RUNNING; }); @@ -215,6 +241,7 @@ public void scheduleExerciseTask(QuizExercise exercise, QuizBatch batch, Exercis * @param exercise Exercise * @param lifecycle ExerciseLifecycle * @param scheduledTask One runnable tasks to be executed at the associated ZonedDateTimes + * @param name Name of the task */ public void scheduleExerciseTask(Exercise exercise, ExerciseLifecycle lifecycle, Tuple scheduledTask, String name) { // check if already scheduled for exercise. if so, cancel. @@ -266,7 +293,7 @@ public void cancelScheduledTaskForLifecycle(Long exerciseId, ExerciseLifecycle l var task = new ExerciseLifecycleKey(exerciseId, lifecycle); var taskNames = scheduledExerciseTasks.get(task); if (taskNames != null) { - log.info("Cancelling scheduled task {} for Exercise (#{}).", lifecycle, exerciseId); + log.debug("Cancelling scheduled task {} for Exercise (#{}).", lifecycle, exerciseId); taskNames.forEach(taskName -> taskName.future().cancel(true)); removeScheduledExerciseTask(exerciseId, lifecycle); } diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseLifecycleService.java b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseLifecycleService.java index 297611b2e23c..d8e929b28765 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseLifecycleService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseLifecycleService.java @@ -47,7 +47,7 @@ public ExerciseLifecycleService(@Qualifier("taskScheduler") TaskScheduler schedu */ public ScheduledFuture scheduleTask(Exercise exercise, ZonedDateTime lifecycleDate, ExerciseLifecycle lifecycle, Runnable task) { final ScheduledFuture future = scheduler.schedule(task, lifecycleDate.toInstant()); - log.info("Scheduled Task for Exercise \"{}\" (#{}) to trigger on {} - {}", exercise.getTitle(), exercise.getId(), lifecycle, lifecycleDate); + log.debug("Scheduled Task for Exercise \"{}\" (#{}) to trigger on {} - {}", exercise.getTitle(), exercise.getId(), lifecycle, lifecycleDate); return future; } @@ -104,7 +104,7 @@ public Set> scheduleMultipleTasks(Exercise exercise, Exercise var future = scheduler.schedule(task.second(), task.first().toInstant()); futures.add(future); } - log.info("Scheduled {} Tasks for Exercise \"{}\" (#{}) to trigger on {}.", tasks.size(), exercise.getTitle(), exercise.getId(), lifecycle.toString()); + log.debug("Scheduled {} Tasks for Exercise \"{}\" (#{}) to trigger on {}.", tasks.size(), exercise.getTitle(), exercise.getId(), lifecycle.toString()); return futures; } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseScheduleService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseScheduleService.java index b892012fd201..a0d08476d978 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseScheduleService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseScheduleService.java @@ -297,7 +297,7 @@ private void scheduleTemplateCommitCombination(ProgrammingExercise exercise) { if (exercise.getReleaseDate() != null) { scheduleService.scheduleExerciseTask(exercise, ExerciseLifecycle.SHORTLY_BEFORE_RELEASE, () -> combineTemplateCommitsForExercise(exercise).run(), "combine template commits"); - log.info("Scheduled combining template commits before release date for Programming Exercise \"{}\" (#{}) for {}.", exercise.getTitle(), exercise.getId(), + log.info("Scheduled combining template commits before release date for programming exercise \"{}\" (#{}) for {}.", exercise.getTitle(), exercise.getId(), exercise.getReleaseDate()); } } @@ -476,13 +476,13 @@ else if (now.isBefore(examDateService.getLatestIndividualExamEndDate(exam))) { @NotNull private Runnable combineTemplateCommitsForExercise(ProgrammingExercise exercise) { return () -> { - log.info("Start combine template commits for programming exercise {}.", exercise.getId()); + log.debug("Start combine template commits for programming exercise {}.", exercise.getId()); SecurityUtils.setAuthorizationObject(); try { ProgrammingExercise programmingExerciseWithTemplateParticipation = programmingExerciseRepository .findByIdWithTemplateAndSolutionParticipationElseThrow(exercise.getId()); gitService.combineAllCommitsOfRepositoryIntoOne(programmingExerciseWithTemplateParticipation.getTemplateParticipation().getVcsRepositoryUri()); - log.info("Combined template repository commits of programming exercise {}.", programmingExerciseWithTemplateParticipation.getId()); + log.debug("Combined template repository commits of programming exercise {}.", programmingExerciseWithTemplateParticipation.getId()); } catch (GitAPIException e) { log.error("Failed to communicate with GitService for combining template commits of exercise {}", exercise.getId(), e);