Search in sources :

Example 1 with JobExecutionContext

use of org.opensearch.jobscheduler.spi.JobExecutionContext in project job-scheduler by opensearch-project.

the class LockService method acquireLock.

/**
 * Attempts to acquire lock the job. If the lock does not exists it attempts to create the lock document.
 * If the Lock document exists, it will try to update and acquire lock.
 *
 * @param jobParameter a {@code ScheduledJobParameter} containing the lock duration.
 * @param context a {@code JobExecutionContext} containing job index name and job id.
 * @param listener an {@code ActionListener} that has onResponse and onFailure that is used to return the lock if it was acquired
 *                 or else null. Passes {@code IllegalArgumentException} to onFailure if the {@code ScheduledJobParameter} does not
 *                 have {@code LockDurationSeconds}.
 */
public void acquireLock(final ScheduledJobParameter jobParameter, final JobExecutionContext context, ActionListener<LockModel> listener) {
    final String jobIndexName = context.getJobIndexName();
    final String jobId = context.getJobId();
    if (jobParameter.getLockDurationSeconds() == null) {
        listener.onFailure(new IllegalArgumentException("Job LockDuration should not be null"));
    } else {
        final long lockDurationSecond = jobParameter.getLockDurationSeconds();
        createLockIndex(ActionListener.wrap(created -> {
            if (created) {
                try {
                    findLock(LockModel.generateLockId(jobIndexName, jobId), ActionListener.wrap(existingLock -> {
                        if (existingLock != null) {
                            if (isLockReleasedOrExpired(existingLock)) {
                                // Lock is expired. Attempt to acquire lock.
                                logger.debug("lock is released or expired: " + existingLock);
                                LockModel updateLock = new LockModel(existingLock, getNow(), lockDurationSecond, false);
                                updateLock(updateLock, listener);
                            } else {
                                logger.debug("Lock is NOT released or expired. " + existingLock);
                                // Lock is still not expired. Return null as we cannot acquire lock.
                                listener.onResponse(null);
                            }
                        } else {
                            // There is no lock object and it is first time. Create new lock.
                            LockModel tempLock = new LockModel(jobIndexName, jobId, getNow(), lockDurationSecond, false);
                            logger.debug("Lock does not exist. Creating new lock" + tempLock);
                            createLock(tempLock, listener);
                        }
                    }, listener::onFailure));
                } catch (VersionConflictEngineException e) {
                    logger.debug("could not acquire lock {}", e.getMessage());
                    listener.onResponse(null);
                }
            } else {
                listener.onResponse(null);
            }
        }, listener::onFailure));
    }
}
Also used : SequenceNumbers(org.opensearch.index.seqno.SequenceNumbers) ToXContent(org.opensearch.common.xcontent.ToXContent) XContentParser(org.opensearch.common.xcontent.XContentParser) XContentFactory(org.opensearch.common.xcontent.XContentFactory) ActionListener(org.opensearch.action.ActionListener) LockModel(org.opensearch.jobscheduler.spi.LockModel) CreateIndexRequest(org.opensearch.action.admin.indices.create.CreateIndexRequest) DeleteRequest(org.opensearch.action.delete.DeleteRequest) Client(org.opensearch.client.Client) VersionConflictEngineException(org.opensearch.index.engine.VersionConflictEngineException) IndexNotFoundException(org.opensearch.index.IndexNotFoundException) GetRequest(org.opensearch.action.get.GetRequest) LoggingDeprecationHandler(org.opensearch.common.xcontent.LoggingDeprecationHandler) IOException(java.io.IOException) DocumentMissingException(org.opensearch.index.engine.DocumentMissingException) Instant(java.time.Instant) ScheduledJobParameter(org.opensearch.jobscheduler.spi.ScheduledJobParameter) InputStreamReader(java.io.InputStreamReader) StandardCharsets(java.nio.charset.StandardCharsets) Logger(org.apache.logging.log4j.Logger) NamedXContentRegistry(org.opensearch.common.xcontent.NamedXContentRegistry) ClusterService(org.opensearch.cluster.service.ClusterService) VisibleForTesting(com.cronutils.utils.VisibleForTesting) ResourceAlreadyExistsException(org.opensearch.ResourceAlreadyExistsException) DocWriteResponse(org.opensearch.action.DocWriteResponse) UpdateRequest(org.opensearch.action.update.UpdateRequest) XContentType(org.opensearch.common.xcontent.XContentType) JobExecutionContext(org.opensearch.jobscheduler.spi.JobExecutionContext) BufferedReader(java.io.BufferedReader) IndexRequest(org.opensearch.action.index.IndexRequest) LogManager(org.apache.logging.log4j.LogManager) InputStream(java.io.InputStream) VersionConflictEngineException(org.opensearch.index.engine.VersionConflictEngineException) LockModel(org.opensearch.jobscheduler.spi.LockModel)

Example 2 with JobExecutionContext

use of org.opensearch.jobscheduler.spi.JobExecutionContext in project job-scheduler by opensearch-project.

the class LockServiceIT method testMultiThreadCreateLock.

@Ignore
public void testMultiThreadCreateLock() throws Exception {
    String uniqSuffix = "_multi_thread_create";
    String lockID = randomAlphaOfLengthBetween(6, 15);
    CountDownLatch latch = new CountDownLatch(1);
    final LockService lockService = new LockService(client(), this.clusterService);
    final JobExecutionContext context = new JobExecutionContext(Instant.now(), new JobDocVersion(0, 0, 0), lockService, JOB_INDEX_NAME + uniqSuffix, JOB_ID + uniqSuffix);
    lockService.createLockIndex(ActionListener.wrap(created -> {
        if (created) {
            ExecutorService executor = Executors.newFixedThreadPool(3);
            final AtomicReference<LockModel> lockModelAtomicReference = new AtomicReference<>(null);
            Callable<Boolean> callable = () -> {
                CountDownLatch callableLatch = new CountDownLatch(1);
                lockService.acquireLockWithId(context.getJobIndexName(), LOCK_DURATION_SECONDS, lockID, ActionListener.wrap(lock -> {
                    if (lock != null) {
                        lockModelAtomicReference.set(lock);
                        multiThreadCreateLockCounter.getAndAdd(1);
                    }
                    callableLatch.countDown();
                }, exception -> fail(exception.getMessage())));
                callableLatch.await(5L, TimeUnit.SECONDS);
                return true;
            };
            List<Callable<Boolean>> callables = Arrays.asList(callable, callable, callable);
            executor.invokeAll(callables).forEach(future -> {
                try {
                    future.get();
                } catch (Exception e) {
                    fail(e.getMessage());
                }
            });
            executor.shutdown();
            executor.awaitTermination(10L, TimeUnit.SECONDS);
            assertEquals("There should be only one that grabs the lock.", 1, multiThreadCreateLockCounter.get());
            final LockModel lock = lockModelAtomicReference.get();
            assertNotNull("Expected to successfully grab lock", lock);
            lockService.release(lock, ActionListener.wrap(released -> {
                assertTrue("Failed to release lock.", released);
                lockService.deleteLock(lock.getLockId(), ActionListener.wrap(deleted -> {
                    assertTrue("Failed to delete lock.", deleted);
                    latch.countDown();
                }, exception -> fail(exception.getMessage())));
            }, exception -> fail(exception.getMessage())));
        } else {
            fail("Failed to create lock index.");
        }
    }, exception -> fail(exception.getMessage())));
    assertTrue("Test timed out - possibly leaked into other tests", latch.await(30L, TimeUnit.SECONDS));
}
Also used : Arrays(java.util.Arrays) Schedule(org.opensearch.jobscheduler.spi.schedule.Schedule) IOException(java.io.IOException) Callable(java.util.concurrent.Callable) Instant(java.time.Instant) ScheduledJobParameter(org.opensearch.jobscheduler.spi.ScheduledJobParameter) AtomicReference(java.util.concurrent.atomic.AtomicReference) Executors(java.util.concurrent.Executors) XContentBuilder(org.opensearch.common.xcontent.XContentBuilder) JobDocVersion(org.opensearch.jobscheduler.spi.JobDocVersion) TimeUnit(java.util.concurrent.TimeUnit) Mockito(org.mockito.Mockito) CountDownLatch(java.util.concurrent.CountDownLatch) List(java.util.List) Ignore(org.junit.Ignore) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) Duration(java.time.Duration) ClusterService(org.opensearch.cluster.service.ClusterService) ActionListener(org.opensearch.action.ActionListener) JobExecutionContext(org.opensearch.jobscheduler.spi.JobExecutionContext) LockModel(org.opensearch.jobscheduler.spi.LockModel) OpenSearchIntegTestCase(org.opensearch.test.OpenSearchIntegTestCase) ExecutorService(java.util.concurrent.ExecutorService) Before(org.junit.Before) AtomicReference(java.util.concurrent.atomic.AtomicReference) CountDownLatch(java.util.concurrent.CountDownLatch) Callable(java.util.concurrent.Callable) IOException(java.io.IOException) ExecutorService(java.util.concurrent.ExecutorService) JobExecutionContext(org.opensearch.jobscheduler.spi.JobExecutionContext) List(java.util.List) LockModel(org.opensearch.jobscheduler.spi.LockModel) JobDocVersion(org.opensearch.jobscheduler.spi.JobDocVersion) Ignore(org.junit.Ignore)

Example 3 with JobExecutionContext

use of org.opensearch.jobscheduler.spi.JobExecutionContext in project job-scheduler by opensearch-project.

the class LockServiceIT method testMultiThreadAcquireLock.

@Ignore
public void testMultiThreadAcquireLock() throws Exception {
    String uniqSuffix = "_multi_thread_acquire";
    String lockID = randomAlphaOfLengthBetween(6, 15);
    CountDownLatch latch = new CountDownLatch(1);
    final LockService lockService = new LockService(client(), this.clusterService);
    final JobExecutionContext context = new JobExecutionContext(Instant.now(), new JobDocVersion(0, 0, 0), lockService, JOB_INDEX_NAME + uniqSuffix, JOB_ID + uniqSuffix);
    lockService.createLockIndex(ActionListener.wrap(created -> {
        if (created) {
            // Set lock time in the past.
            lockService.setTime(Instant.now().minus(Duration.ofSeconds(LOCK_DURATION_SECONDS + LOCK_DURATION_SECONDS)));
            lockService.acquireLockWithId(context.getJobIndexName(), LOCK_DURATION_SECONDS, lockID, ActionListener.wrap(createdLock -> {
                assertNotNull(createdLock);
                // Set lock back to current time to make the lock expire.
                lockService.setTime(null);
                ExecutorService executor = Executors.newFixedThreadPool(3);
                final AtomicReference<LockModel> lockModelAtomicReference = new AtomicReference<>(null);
                Callable<Boolean> callable = () -> {
                    CountDownLatch callableLatch = new CountDownLatch(1);
                    lockService.acquireLockWithId(context.getJobIndexName(), LOCK_DURATION_SECONDS, lockID, ActionListener.wrap(lock -> {
                        if (lock != null) {
                            lockModelAtomicReference.set(lock);
                            Integer test = multiThreadAcquireLockCounter.getAndAdd(1);
                        }
                        callableLatch.countDown();
                    }, exception -> fail(exception.getMessage())));
                    callableLatch.await(5L, TimeUnit.SECONDS);
                    return true;
                };
                List<Callable<Boolean>> callables = Arrays.asList(callable, callable, callable);
                executor.invokeAll(callables);
                executor.shutdown();
                executor.awaitTermination(10L, TimeUnit.SECONDS);
                assertEquals("There should be only one that grabs the lock.", 1, multiThreadAcquireLockCounter.get());
                final LockModel lock = lockModelAtomicReference.get();
                assertNotNull("Expected to successfully grab lock", lock);
                lockService.release(lock, ActionListener.wrap(released -> {
                    assertTrue("Failed to release lock.", released);
                    lockService.deleteLock(lock.getLockId(), ActionListener.wrap(deleted -> {
                        assertTrue("Failed to delete lock.", deleted);
                        latch.countDown();
                    }, exception -> fail(exception.getMessage())));
                }, exception -> fail(exception.getMessage())));
            }, exception -> fail(exception.getMessage())));
        } else {
            fail("Failed to create lock index.");
        }
    }, exception -> fail(exception.getMessage())));
    assertTrue("Test timed out - possibly leaked into other tests", latch.await(30L, TimeUnit.SECONDS));
}
Also used : Arrays(java.util.Arrays) Schedule(org.opensearch.jobscheduler.spi.schedule.Schedule) IOException(java.io.IOException) Callable(java.util.concurrent.Callable) Instant(java.time.Instant) ScheduledJobParameter(org.opensearch.jobscheduler.spi.ScheduledJobParameter) AtomicReference(java.util.concurrent.atomic.AtomicReference) Executors(java.util.concurrent.Executors) XContentBuilder(org.opensearch.common.xcontent.XContentBuilder) JobDocVersion(org.opensearch.jobscheduler.spi.JobDocVersion) TimeUnit(java.util.concurrent.TimeUnit) Mockito(org.mockito.Mockito) CountDownLatch(java.util.concurrent.CountDownLatch) List(java.util.List) Ignore(org.junit.Ignore) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) Duration(java.time.Duration) ClusterService(org.opensearch.cluster.service.ClusterService) ActionListener(org.opensearch.action.ActionListener) JobExecutionContext(org.opensearch.jobscheduler.spi.JobExecutionContext) LockModel(org.opensearch.jobscheduler.spi.LockModel) OpenSearchIntegTestCase(org.opensearch.test.OpenSearchIntegTestCase) ExecutorService(java.util.concurrent.ExecutorService) Before(org.junit.Before) AtomicReference(java.util.concurrent.atomic.AtomicReference) CountDownLatch(java.util.concurrent.CountDownLatch) Callable(java.util.concurrent.Callable) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) ExecutorService(java.util.concurrent.ExecutorService) JobExecutionContext(org.opensearch.jobscheduler.spi.JobExecutionContext) LockModel(org.opensearch.jobscheduler.spi.LockModel) JobDocVersion(org.opensearch.jobscheduler.spi.JobDocVersion) Ignore(org.junit.Ignore)

Example 4 with JobExecutionContext

use of org.opensearch.jobscheduler.spi.JobExecutionContext in project job-scheduler by opensearch-project.

the class LockServiceIT method testLockExpired.

public void testLockExpired() throws Exception {
    String uniqSuffix = "_lock_expire";
    String lockID = randomAlphaOfLengthBetween(6, 15);
    CountDownLatch latch = new CountDownLatch(1);
    LockService lockService = new LockService(client(), this.clusterService);
    // Set lock time in the past.
    lockService.setTime(Instant.now().minus(Duration.ofSeconds(LOCK_DURATION_SECONDS + LOCK_DURATION_SECONDS)));
    final JobExecutionContext context = new JobExecutionContext(Instant.now(), new JobDocVersion(0, 0, 0), lockService, JOB_INDEX_NAME + uniqSuffix, JOB_ID + uniqSuffix);
    lockService.acquireLockWithId(context.getJobIndexName(), LOCK_DURATION_SECONDS, lockID, ActionListener.wrap(lock -> {
        assertNotNull("Expected to successfully grab lock", lock);
        // Set lock back to current time to make the lock expire.
        lockService.setTime(null);
        lockService.acquireLockWithId(context.getJobIndexName(), LOCK_DURATION_SECONDS, lockID, ActionListener.wrap(lock2 -> {
            assertNotNull("Expected to successfully grab lock", lock2);
            lockService.release(lock, ActionListener.wrap(released -> {
                assertFalse("Expected to fail releasing lock.", released);
                lockService.release(lock2, ActionListener.wrap(released2 -> {
                    assertTrue("Expecting to successfully release lock.", released2);
                    lockService.deleteLock(lock.getLockId(), ActionListener.wrap(deleted -> {
                        assertTrue("Failed to delete lock.", deleted);
                        latch.countDown();
                    }, exception -> fail(exception.getMessage())));
                }, exception -> fail(exception.getMessage())));
            }, exception -> fail(exception.getMessage())));
        }, exception -> fail(exception.getMessage())));
    }, exception -> fail(exception.getMessage())));
    latch.await(5L, TimeUnit.SECONDS);
}
Also used : Arrays(java.util.Arrays) Schedule(org.opensearch.jobscheduler.spi.schedule.Schedule) IOException(java.io.IOException) Callable(java.util.concurrent.Callable) Instant(java.time.Instant) ScheduledJobParameter(org.opensearch.jobscheduler.spi.ScheduledJobParameter) AtomicReference(java.util.concurrent.atomic.AtomicReference) Executors(java.util.concurrent.Executors) XContentBuilder(org.opensearch.common.xcontent.XContentBuilder) JobDocVersion(org.opensearch.jobscheduler.spi.JobDocVersion) TimeUnit(java.util.concurrent.TimeUnit) Mockito(org.mockito.Mockito) CountDownLatch(java.util.concurrent.CountDownLatch) List(java.util.List) Ignore(org.junit.Ignore) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) Duration(java.time.Duration) ClusterService(org.opensearch.cluster.service.ClusterService) ActionListener(org.opensearch.action.ActionListener) JobExecutionContext(org.opensearch.jobscheduler.spi.JobExecutionContext) LockModel(org.opensearch.jobscheduler.spi.LockModel) OpenSearchIntegTestCase(org.opensearch.test.OpenSearchIntegTestCase) ExecutorService(java.util.concurrent.ExecutorService) Before(org.junit.Before) JobExecutionContext(org.opensearch.jobscheduler.spi.JobExecutionContext) CountDownLatch(java.util.concurrent.CountDownLatch) JobDocVersion(org.opensearch.jobscheduler.spi.JobDocVersion)

Example 5 with JobExecutionContext

use of org.opensearch.jobscheduler.spi.JobExecutionContext in project job-scheduler by opensearch-project.

the class JobScheduler method reschedule.

@VisibleForTesting
boolean reschedule(ScheduledJobParameter jobParameter, JobSchedulingInfo jobInfo, ScheduledJobRunner jobRunner, JobDocVersion version, Double jitterLimit) {
    if (jobParameter.getEnabledTime() == null) {
        log.info("There is no enable time of job {}, this job should never be scheduled.", jobParameter.getName());
        return false;
    }
    Instant nextExecutionTime = jobParameter.getSchedule().getNextExecutionTime(jobInfo.getExpectedExecutionTime());
    if (nextExecutionTime == null) {
        log.info("No next execution time for job {}", jobParameter.getName());
        return true;
    }
    Duration duration = Duration.between(this.clock.instant(), nextExecutionTime);
    // Too many jobs start at the same time point will bring burst. Add random jitter delay to spread out load.
    // Example, if interval is 10 minutes, jitter is 0.6, next job run will be randomly delayed by 0 to 10*0.6 minutes.
    Instant secondExecutionTimeFromNow = jobParameter.getSchedule().getNextExecutionTime(nextExecutionTime);
    if (secondExecutionTimeFromNow != null) {
        Duration interval = Duration.between(nextExecutionTime, secondExecutionTimeFromNow);
        if (interval.toMillis() > 0) {
            double jitter = jobParameter.getJitter() == null ? 0d : jobParameter.getJitter();
            jitter = jitter > jitterLimit ? jitterLimit : jitter;
            jitter = jitter < 0 ? 0 : jitter;
            long randomLong = Randomness.get().nextLong();
            // to ensure the * -1 below doesn't fail to change to positive
            if (randomLong == Long.MIN_VALUE)
                randomLong += 17;
            long randomPositiveLong = randomLong < 0 ? randomLong * -1 : randomLong;
            long jitterMillis = Math.round(randomPositiveLong % interval.toMillis() * jitter);
            if (jitter > 0) {
                log.info("Will delay {} miliseconds for next execution of job {}", jitterMillis, jobParameter.getName());
            }
            duration = duration.plusMillis(jitterMillis);
        }
    }
    jobInfo.setExpectedExecutionTime(nextExecutionTime);
    Runnable runnable = () -> {
        if (jobInfo.isDescheduled()) {
            return;
        }
        jobInfo.setExpectedPreviousExecutionTime(jobInfo.getExpectedExecutionTime());
        jobInfo.setActualPreviousExecutionTime(clock.instant());
        // schedule next execution
        this.reschedule(jobParameter, jobInfo, jobRunner, version, jitterLimit);
        // invoke job runner
        JobExecutionContext context = new JobExecutionContext(jobInfo.getExpectedPreviousExecutionTime(), version, lockService, jobInfo.getIndexName(), jobInfo.getJobId());
        jobRunner.runJob(jobParameter, context);
    };
    if (jobInfo.isDescheduled()) {
        return false;
    }
    jobInfo.setScheduledCancellable(this.threadPool.schedule(runnable, new TimeValue(duration.toNanos(), TimeUnit.NANOSECONDS), JobSchedulerPlugin.OPEN_DISTRO_JOB_SCHEDULER_THREAD_POOL_NAME));
    return true;
}
Also used : Instant(java.time.Instant) JobExecutionContext(org.opensearch.jobscheduler.spi.JobExecutionContext) Duration(java.time.Duration) TimeValue(org.opensearch.common.unit.TimeValue) VisibleForTesting(org.opensearch.jobscheduler.utils.VisibleForTesting)

Aggregations

JobExecutionContext (org.opensearch.jobscheduler.spi.JobExecutionContext)11 Instant (java.time.Instant)10 ActionListener (org.opensearch.action.ActionListener)10 ClusterService (org.opensearch.cluster.service.ClusterService)10 ScheduledJobParameter (org.opensearch.jobscheduler.spi.ScheduledJobParameter)10 IOException (java.io.IOException)9 Duration (java.time.Duration)9 List (java.util.List)9 LockModel (org.opensearch.jobscheduler.spi.LockModel)9 Arrays (java.util.Arrays)8 Callable (java.util.concurrent.Callable)8 CountDownLatch (java.util.concurrent.CountDownLatch)8 ExecutorService (java.util.concurrent.ExecutorService)8 Executors (java.util.concurrent.Executors)8 TimeUnit (java.util.concurrent.TimeUnit)8 AtomicInteger (java.util.concurrent.atomic.AtomicInteger)8 AtomicReference (java.util.concurrent.atomic.AtomicReference)8 Before (org.junit.Before)8 Ignore (org.junit.Ignore)8 Mockito (org.mockito.Mockito)8