All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.datarouter.job.lock.TriggerLockService Maven / Gradle / Ivy

The newest version!
/*
 * Copyright © 2009 HotPads ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.datarouter.job.lock;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.datarouter.job.storage.joblock.DatarouterJobLockDao;
import io.datarouter.job.storage.joblock.JobLock;
import io.datarouter.job.storage.joblock.JobLockKey;
import io.datarouter.job.storage.triggerlock.DatarouterTriggerLockDao;
import io.datarouter.job.storage.triggerlock.TriggerLock;
import io.datarouter.job.storage.triggerlock.TriggerLockKey;
import io.datarouter.job.util.DatarouterJobOutcome;
import io.datarouter.model.databean.Databean;
import io.datarouter.storage.config.properties.ServerName;
import io.datarouter.types.MilliTime;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;

@Singleton
public class TriggerLockService{
	private static final Logger logger = LoggerFactory.getLogger(TriggerLockService.class);

	private static final String FORMAT_STRING = "yyyy'y'MM'm'dd'd'HH'h'mm'm'ss's'SSS'ms'";
	private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(FORMAT_STRING);

	@Inject
	private DatarouterJobLockDao jobLockDao;
	@Inject
	private DatarouterTriggerLockDao triggerLockDao;
	@Inject
	private ServerName serverName;

	public DatarouterJobOutcome acquireJobAndTriggerLocks(
			TriggerLockConfig triggerLockConfig,
			Instant triggerTime,
			Duration delay){
		var jobLockAcquired = acquireJobLock(triggerLockConfig, triggerTime, delay);
		if(jobLockAcquired.failed()){
			return jobLockAcquired;
		}
		var triggerLockAcquired = acquireTriggerLock(triggerLockConfig, triggerTime);
		if(triggerLockAcquired.failed()){
			releaseJobLock(triggerLockConfig.jobName);//could contend with another server?
			return triggerLockAcquired;
		}
		return DatarouterJobOutcome.makeSuccess();
	}

	private DatarouterJobOutcome acquireJobLock(
			TriggerLockConfig triggerLockConfig,
			Instant triggerTime,
			Duration delay){
		logStrangeTriggerTime(triggerLockConfig.jobName, triggerTime);
		JobLock jobLock = toJobLock(triggerLockConfig, triggerTime);
		try{
			jobLockDao.putAndAcquire(jobLock);
			logAction(
					triggerLockConfig.jobName,
					triggerTime,
					"acquired clusterJobLock, delay=" + delay.toMillis() + "ms");
			return DatarouterJobOutcome.makeSuccess();
		}catch(Exception e){
			String reason;
			try{
				reason = "JobLock already acquired by: " + jobLockDao.get(jobLock.getKey()).getServerName();
			}catch(Exception ex){
				reason = "Unable to acquire JobLock.";
			}
			return DatarouterJobOutcome.makeFailure(reason + " exception=\"" + e + "\"");
		}
	}

	private DatarouterJobOutcome acquireTriggerLock(
			TriggerLockConfig triggerLockConfig,
			Instant triggerTime){
		TriggerLock triggerLock = toTriggerLock(triggerLockConfig, triggerTime);
		try{
			triggerLockDao.putAndAcquire(triggerLock);
			return DatarouterJobOutcome.makeSuccess();
		}catch(Exception e){
			logAction(
					triggerLockConfig.jobName,
					triggerTime,
					"did not acquire clusterTriggerLock");
			String reason;
			try{
				reason = "ClusterTriggerLock already acquired by: " + triggerLockDao
						.get(triggerLock.getKey()).getServerName();
			}catch(Exception ex){
				reason = "Unable to acquire ClusterTriggerLock.";
			}
			return DatarouterJobOutcome.makeFailure(reason + " exception= " + e);
		}
	}

	public void releaseJobLock(String jobName){
		var jobLockKey = new JobLockKey(jobName);
		jobLockDao.delete(jobLockKey);
		logAction(jobName, "released clusterJobLock");
	}

	/**
	 * Method is not transactionally safe.  Only use if an occasional extra unlock is ok.
	 */
	public void deleteJobLockIfExpired(String jobName){
		var clusterJobLockKey = new JobLockKey(jobName);
		var jobLock = jobLockDao.get(clusterJobLockKey);
		if(jobLock != null && MilliTime.now().isAfter(jobLock.getExpirationTime())){
			jobLockDao.delete(clusterJobLockKey);
			logger.warn("deleteIfExpired unlocked {}", jobLock.getClass().getName());
		}
	}

	public void releaseThisServersJobLocks(){
		jobLockDao.scan()
				.include(triggerLock -> triggerLock.getServerName().equals(serverName.get()))
				.each(lock -> logger.info("releasing clusterJobLock {}", lock.getKey().getJobName()))
				.map(Databean::getKey)
				.forEach(jobLockDao::delete);
	}

	public void releaseTriggerLock(String jobName, Instant triggerTime){
		var key = new TriggerLockKey(jobName, MilliTime.of(triggerTime));
		triggerLockDao.delete(key);
		logger.info("releasing clusterTriggerLock {}, {}", key.getJobName(), key.getTriggerTime());
	}

	public void tryReleasingJobAndTriggerLocks(TriggerLockConfig triggerLockConfig, Instant triggerTime){
		try{
			releaseTriggerLock(triggerLockConfig.jobName, triggerTime);
		}catch(Exception e){
			logger.warn("failed to release clusterTriggerLock {} - {}", triggerLockConfig.jobName, triggerTime, e);
		}
		try{
			releaseJobLock(triggerLockConfig.jobName);
		}catch(Exception e){
			logger.warn("failed to release jobLock for {}", triggerLockConfig.jobName, e);
		}
	}

	// To be used by detached-jobs once the detach job starts
	public void forceAcquireJobLock(TriggerLockConfig triggerLockConfig, Instant triggerTime, Duration delay){
		jobLockDao.find(new JobLockKey(triggerLockConfig.jobName))
				.ifPresentOrElse(
						jobLock -> jobLockDao.forcePut(new JobLock(
								jobLock.getKey().getJobName(),
								jobLock.getTriggerTime(),
								jobLock.getExpirationTime(),
								serverName.get())),
						() -> acquireJobLock(triggerLockConfig, triggerTime, delay));
	}

	private JobLock toJobLock(TriggerLockConfig triggerLockConfig, Instant triggerTime){
		return toTriggerLock(triggerLockConfig, triggerTime).toJobLock();
	}

	private TriggerLock toTriggerLock(TriggerLockConfig triggerLockConfig, Instant triggerTime){
		MilliTime dateTriggerTime = MilliTime.of(triggerTime);
		String lockName = triggerLockConfig.jobName;
		Instant deadline = triggerLockConfig.getSoftDeadline(triggerTime);//should we use hard or soft deadline?
		MilliTime expirationTime = MilliTime.of(deadline);
		return new TriggerLock(lockName, dateTriggerTime, expirationTime, serverName.get());
	}

	private void logAction(String jobName, Instant triggerTime, String action){
		String logName = jobName + "-" + formatTime(triggerTime);
		logAction(logName, action);
	}

	private void logAction(String logName, String action){
		logger.info("{} {} lockId {}", serverName.get(), action, logName);
	}

	private static void logStrangeTriggerTime(String jobName, Instant triggerTime){
		long ms = triggerTime.toEpochMilli() % 1000;
		if(ms != 0){
			logger.info("{} had unexpected partial second triggerTime:{} with {}ms", jobName, triggerTime, ms);
		}
	}

	public static String formatTime(Instant instant){
		return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).format(FORMATTER);
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy