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));
}
}
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));
}
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));
}
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);
}
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;
}
Aggregations