diff --git a/src/main/java/ch/uzh/ifi/access/student/config/SchedulingConfig.java b/src/main/java/ch/uzh/ifi/access/student/config/SchedulingConfig.java new file mode 100644 index 00000000..67699507 --- /dev/null +++ b/src/main/java/ch/uzh/ifi/access/student/config/SchedulingConfig.java @@ -0,0 +1,34 @@ +package ch.uzh.ifi.access.student.config; + +import ch.uzh.ifi.access.student.evaluation.process.EvalMachineRepoService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +@Configuration +@EnableScheduling +public class SchedulingConfig { + + private static Logger logger = LoggerFactory.getLogger(SchedulingConfig.class); + + private static final long FIXED_DELAY_IN_MINUTES = 5; + + private EvalMachineRepoService machineRepository; + + public SchedulingConfig(EvalMachineRepoService machineRepository) { + this.machineRepository = machineRepository; + } + + @Scheduled(fixedDelay = FIXED_DELAY_IN_MINUTES * 60000) + public void cleanUpRepo() { + Instant threshold = Instant.now().minus(5, ChronoUnit.MINUTES); + logger.debug("Starting state machine cleanup. Repository size {}, removing machine older than {}", machineRepository.count(), threshold); + machineRepository.removeMachinesOlderThan(threshold); + logger.debug("Completed cleanup. Repository size {}", machineRepository.count()); + } +} diff --git a/src/main/java/ch/uzh/ifi/access/student/evaluation/process/EvalMachineFactory.java b/src/main/java/ch/uzh/ifi/access/student/evaluation/process/EvalMachineFactory.java index db0855e1..dab1f144 100644 --- a/src/main/java/ch/uzh/ifi/access/student/evaluation/process/EvalMachineFactory.java +++ b/src/main/java/ch/uzh/ifi/access/student/evaluation/process/EvalMachineFactory.java @@ -16,6 +16,7 @@ public class EvalMachineFactory { public static final String EXTENDED_VAR_SUBMISSION_ID = "submissionId"; public static final String EXTENDED_VAR_NEXT_STEP = "nextStep"; public static final String EXTENDED_VAR_NEXT_STEP_DELAY = "nextStepDelay"; + public static final String EXTENDED_VAR_COMPLETION_TIME = "completionTime"; public static StateMachine initSMForSubmission(String submissionId) throws Exception { @@ -57,6 +58,7 @@ public static StateMachine initSMForSubm StateMachine machine = builder.build(); machine.getExtendedState().getVariables().put(EXTENDED_VAR_SUBMISSION_ID, submissionId); + machine.addStateListener(new StateMachineEventListener(machine)); return machine; } diff --git a/src/main/java/ch/uzh/ifi/access/student/evaluation/process/EvalMachineRepoService.java b/src/main/java/ch/uzh/ifi/access/student/evaluation/process/EvalMachineRepoService.java index 73e9283a..3307e2ac 100644 --- a/src/main/java/ch/uzh/ifi/access/student/evaluation/process/EvalMachineRepoService.java +++ b/src/main/java/ch/uzh/ifi/access/student/evaluation/process/EvalMachineRepoService.java @@ -3,6 +3,7 @@ import org.springframework.statemachine.StateMachine; import org.springframework.stereotype.Service; +import java.time.Instant; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -27,4 +28,17 @@ public void store(String key, StateMachine machine) { machines.put(key, machine); } + public long count() { + return machines.size(); + } + + public void removeMachinesOlderThan(Instant threshold) { + for (String machineKey : machines.keySet()) { + StateMachine machine = machines.get(machineKey); + Instant completionTime = (Instant) machine.getExtendedState().getVariables().get(EvalMachineFactory.EXTENDED_VAR_COMPLETION_TIME); + if (completionTime.isBefore(threshold)) { + machines.remove(machineKey); + } + } + } } diff --git a/src/main/java/ch/uzh/ifi/access/student/evaluation/process/StateMachineEventListener.java b/src/main/java/ch/uzh/ifi/access/student/evaluation/process/StateMachineEventListener.java new file mode 100644 index 00000000..3f084208 --- /dev/null +++ b/src/main/java/ch/uzh/ifi/access/student/evaluation/process/StateMachineEventListener.java @@ -0,0 +1,24 @@ +package ch.uzh.ifi.access.student.evaluation.process; + +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.listener.StateMachineListenerAdapter; +import org.springframework.statemachine.state.State; + +import java.time.Instant; + +public class StateMachineEventListener + extends StateMachineListenerAdapter { + + private StateMachine machine; + + public StateMachineEventListener(StateMachine machine) { + this.machine = machine; + } + + @Override + public void stateEntered(State state) { + if (EvalMachine.States.FINISHED.equals(state.getId())) { + machine.getExtendedState().getVariables().put(EvalMachineFactory.EXTENDED_VAR_COMPLETION_TIME, Instant.now()); + } + } +} \ No newline at end of file diff --git a/src/test/java/ch/uzh/ifi/access/student/evaluation/process/EvalMachineRepoServiceTest.java b/src/test/java/ch/uzh/ifi/access/student/evaluation/process/EvalMachineRepoServiceTest.java new file mode 100644 index 00000000..24ee5021 --- /dev/null +++ b/src/test/java/ch/uzh/ifi/access/student/evaluation/process/EvalMachineRepoServiceTest.java @@ -0,0 +1,66 @@ +package ch.uzh.ifi.access.student.evaluation.process; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.springframework.statemachine.StateMachine; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.UUID; + +public class EvalMachineRepoServiceTest { + + @Test + public void cleanUpNoMachines() { + EvalMachineRepoService repo = new EvalMachineRepoService(); + repo.removeMachinesOlderThan(Instant.now()); + } + + @Test + public void cleanUp() throws Exception { + String id1 = UUID.randomUUID().toString(); + String id2 = UUID.randomUUID().toString(); + StateMachine m1 = EvalMachineFactory.initSMForSubmission("123"); + StateMachine m2 = EvalMachineFactory.initSMForSubmission("345"); + + m1.getExtendedState().getVariables().put(EvalMachineFactory.EXTENDED_VAR_COMPLETION_TIME, Instant.now().minus(30, ChronoUnit.MINUTES)); + m2.getExtendedState().getVariables().put(EvalMachineFactory.EXTENDED_VAR_COMPLETION_TIME, Instant.now().minus(1, ChronoUnit.MINUTES)); + + EvalMachineRepoService repo = new EvalMachineRepoService(); + repo.store(id1, m1); + repo.store(id2, m2); + + Assertions.assertThat(repo.get(id1)).isNotNull(); + Assertions.assertThat(repo.get(id2)).isNotNull(); + + Instant fiveMinutesAgo = Instant.now().minus(5, ChronoUnit.MINUTES); + repo.removeMachinesOlderThan(fiveMinutesAgo); + + Assertions.assertThat(repo.get(id1)).isNull(); + Assertions.assertThat(repo.get(id2)).isNotNull(); + } + + @Test + public void noMachinesToClean() throws Exception { + String id1 = UUID.randomUUID().toString(); + String id2 = UUID.randomUUID().toString(); + StateMachine m1 = EvalMachineFactory.initSMForSubmission("123"); + StateMachine m2 = EvalMachineFactory.initSMForSubmission("345"); + + m1.getExtendedState().getVariables().put(EvalMachineFactory.EXTENDED_VAR_COMPLETION_TIME, Instant.now().minus(1, ChronoUnit.MINUTES)); + m2.getExtendedState().getVariables().put(EvalMachineFactory.EXTENDED_VAR_COMPLETION_TIME, Instant.now().minus(1, ChronoUnit.MINUTES)); + + EvalMachineRepoService repo = new EvalMachineRepoService(); + repo.store(id1, m1); + repo.store(id2, m2); + + Assertions.assertThat(repo.get(id1)).isNotNull(); + Assertions.assertThat(repo.get(id2)).isNotNull(); + + Instant fiveMinutesAgo = Instant.now().minus(5, ChronoUnit.MINUTES); + repo.removeMachinesOlderThan(fiveMinutesAgo); + + Assertions.assertThat(repo.get(id1)).isNotNull(); + Assertions.assertThat(repo.get(id2)).isNotNull(); + } +} \ No newline at end of file