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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,10 @@ public void start() {
* <li>Put back in queue for retry</li>
* <li>Generate a JobAbandonedEvent</li>
* </ol>
* Public so callers (e.g. integration tests) can trigger an immediate scan instead of
* waiting for the next scheduled tick; the scheduled executor uses this same method.
*/
private void detectAbandonedJobs() {
public void detectAbandonedJobs() {

try {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.dotcms.jobs.business.detector.AbandonedJobDetector;
import com.dotcms.jobs.business.error.ExponentialBackoffRetryStrategy;
import com.dotcms.jobs.business.error.RetryStrategy;
import com.dotcms.jobs.business.job.Job;
Expand Down Expand Up @@ -59,6 +60,9 @@ public class JobQueueManagerAPIIntegrationTest extends com.dotcms.Junit5WeldBase
@Inject
JobQueueManagerAPI jobQueueManagerAPI;

@Inject
AbandonedJobDetector abandonedJobDetector;

/**
* Sets up the test environment before all tests are run.
* Initializes the test environment and obtains an instance of JobQueueManagerAPI.
Expand Down Expand Up @@ -542,7 +546,11 @@ void test_AbandonedJobDetection() throws Exception {
}
});

boolean abandoned = latch.await(3, TimeUnit.MINUTES);
// Trigger detection directly instead of waiting up to a minute for the
// detector's scheduled tick (the inserted job is already past the threshold)
abandonedJobDetector.detectAbandonedJobs();

boolean abandoned = latch.await(30, TimeUnit.SECONDS);
assertTrue(abandoned, "Job should be marked as abandoned within timeout period");

// Verify the abandoned job state and error details
Expand Down Expand Up @@ -655,7 +663,11 @@ void test_Abandoned_Permanetly_Job() throws Exception {
}
});

boolean abandoned = latch.await(3, TimeUnit.MINUTES);
// Trigger detection directly instead of waiting up to a minute for the
// detector's scheduled tick (the inserted job is already past the threshold)
abandonedJobDetector.detectAbandonedJobs();

boolean abandoned = latch.await(30, TimeUnit.SECONDS);
assertTrue(abandoned, "Job should be marked as abandoned within timeout period");

// Verify the abandoned job state and error details
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

import org.awaitility.Awaitility;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
Expand Down Expand Up @@ -46,11 +49,13 @@ public static void beforeClass() throws Exception {
@Test
public void testJob()
throws SchedulerException, InterruptedException, DotDataException, DotSecurityException {
final Instant NOW_PLUS_TWO_MINUTES = Instant.now().plus(2, ChronoUnit.MINUTES);
// Short windows: validateScheduling only requires dates after now-1min, so seconds
// are enough — the old 1/2-minute windows forced a 2-minute Thread.sleep
final Instant NOW_PLUS_TWENTY_SECONDS = Instant.now().plus(20, ChronoUnit.SECONDS);

// create experiment that will end soon
Experiment scheduledToEndExperiment = new ExperimentDataGen()
.scheduling(Scheduling.builder().endDate(NOW_PLUS_TWO_MINUTES).build())
.scheduling(Scheduling.builder().endDate(NOW_PLUS_TWENTY_SECONDS).build())
.status(Status.RUNNING)
.nextPersisted();

Expand All @@ -61,28 +66,34 @@ public void testJob()
assertEquals(Status.RUNNING, scheduledToEndExperiment.status());

// create experiment that should have started
final Instant NOW_PLUS_ONE_MINUTE = Instant.now().plus(1, ChronoUnit.MINUTES);
final Instant NOW_PLUS_TEN_SECONDS = Instant.now().plus(10, ChronoUnit.SECONDS);

scheduledToStartExperiment = new ExperimentDataGen()
.scheduling(Scheduling.builder().startDate(NOW_PLUS_ONE_MINUTE).build())
.scheduling(Scheduling.builder().startDate(NOW_PLUS_TEN_SECONDS).build())
.nextPersisted();

scheduledToStartExperiment = experimentsAPI.start(scheduledToStartExperiment.id().orElseThrow(),
APILocator.systemUser());

// wait some minutes for its end date to be reached
Thread.sleep(2 * 60 * 1000);

assertEquals(Status.SCHEDULED, scheduledToStartExperiment.status());

new StartEndScheduledExperimentsJob().run(null);

assertEquals(Status.RUNNING,
experimentsAPI.find(scheduledToStartExperiment.id().orElseThrow()
, APILocator.systemUser()).orElseThrow().status());
assertEquals(Status.ENDED,
experimentsAPI.find(scheduledToEndExperiment.id().orElseThrow()
, APILocator.systemUser()).orElseThrow().status());
// Re-run the job (as Quartz would) until both scheduling dates have passed and
// the transitions land: no fixed sleep, completes as soon as the dates are
// reached (~20s); the 2-minute cap is failure-path only, sized for slow runners
final String startId = scheduledToStartExperiment.id().orElseThrow();
final String endId = scheduledToEndExperiment.id().orElseThrow();
Awaitility.await()
.atMost(2, TimeUnit.MINUTES)
.pollInterval(2, TimeUnit.SECONDS)
.untilAsserted(() -> {
new StartEndScheduledExperimentsJob().run(null);
assertEquals(Status.RUNNING,
experimentsAPI.find(startId, APILocator.systemUser())
.orElseThrow().status());
assertEquals(Status.ENDED,
experimentsAPI.find(endId, APILocator.systemUser())
.orElseThrow().status());
});
} finally {
final Experiment shouldBeRunning = experimentsAPI.find(scheduledToStartExperiment.id().orElseThrow()
, APILocator.systemUser()).orElseThrow();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1504,11 +1504,13 @@ public void findTopTags_should_be_not_null_not_empty_and_contains_one_popular_ta
final String tagvalue1 = "mytesttag1";

final Tag tag1 = Try.of(()->APILocator.getTagAPI().saveTag(tagvalue1, testUser.getUserId(), defaultHostId)).getOrNull();
// One content type for all iterations: getWikiLikeContentType() creates a brand-new
// content type per call, and 100 of them means 100 ES mapping updates
final ContentType contentType = TestDataUtils.getWikiLikeContentType();
IntStream.range(0, 100).forEach(r -> {

try {
final Contentlet contentAsset = new Contentlet();
ContentType contentType = TestDataUtils.getWikiLikeContentType();
contentAsset.setContentTypeId(contentType.id());
contentAsset.setHost(defaultHostId);
contentAsset.setProperty(WIKI_SYSPUBLISHDATE_VARNAME, new Date());
Expand Down
Loading