Reputation: 332
We are using Spring Shedlock library to run one instance of task at a time in clustered enviroment. But looks like lock is held until specific time (configured by lockAtMostFor attribute If we dont configure it takes default value from spring config) not until task is finished.
Lets say if I configured lockAtMostFor=15 mins no parellel task runs on any of node for 15 mins, but after 15 mins if any of node gets chance to run(or manually trigger) task its able to start it even if previous task is not finished its processing.
1.Is Shedlock is time based ?
2.Documentation(https://github.com/lukas-krecan/ShedLock) says lock is only released only when task is finished and lockAtMostFor attribute is only used in case node terminition event , but its not working like that even if task is not finished other nodes are able to run task , other nodes are blocked from running task only if time is not elapsed (lockAtMostFor attribute)
Upvotes: 5
Views: 8916
Reputation: 14303
Quoting from the documentation:
You have to set
lockAtMostFor
to a value that is much longer than normal execution time. If the task takes longer thanlockAtMostFor
the resulting behavior may be unpredictable (more than one process will effectively hold the lock).
Upvotes: 1
Reputation: 91
You can find in the documentation of Shedlock that in order to extend the maximum locked time, lockAtMostUntil
, you have to create both SimpleLock
and extend()
method manually. Remember that, you also have to invoke the extend()
method manually, or by implementing a check()
method that checks if your process is finished and invokes the extend()
method if needed, otherwise, the lock will be automatically unlocked when it reaches the lockAtMostUntil
time, no matter if your process is still running.
Here I am explaining how you can create the SimpleLock
manually from LockProvider
.
For questions about how to set the database, I recommend you read this part of the documentation of Shedlock.
I have used spring boot
for my application, so the annotations that you see come from spring boot
.
Dependencies:
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>4.23.0</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>4.23.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
Configuration of LockProvider
:
@EnableAsync
@Profile("!unit_test")
@Configuration
public class LockConfiguration {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(
JdbcTemplateLockProvider.Configuration.builder()
.withTimeZone(TimeZone.getTimeZone("Europe/Rome"))
.withJdbcTemplate(new JdbcTemplate(dataSource))
.withTableName("shared_lock")
.usingDbTime()
.build());
}
}
DistributedLock here is where we implement SimpleLock
and all the needed related methods:
@Component
@Slf4j
public class DistributedLockWithExtend {
@Autowired
private LockProvider lockProvider;
private final Map<String, SimpleLock> locks = new HashMap<>();
public boolean tryLock(String lockname, Duration lockDuration){
Duration minDuration = Duration.ZERO;
Instant now = ClockProvider.now();
LockConfiguration config = new LockConfiguration(now, lockname, lockDuration, minDuration);
Optional<SimpleLock> lockLocal = lockProvider.lock(config);
if(lockLocal.isPresent()) {
locks.put(lockname, lockLocal.get());
log.debug("lock is created!");
return true;
}else {
log.debug("lock is locked!");
return false;
}
}
public boolean extendLock(String lockname, Duration duration){
Duration minDuration = Duration.ZERO;
SimpleLock lock = locks.get(lockname);
if(lock != null) {
Optional<SimpleLock> localLock = lock.extend(duration, duration);
locks.put(lockname, localLock.get());
log.debug("Lock is extended");
return true;
}else {
log.debug("There is no lock or the lock is already unlocked! Create a lock with tryLock() if you need!");
return false;
}
}
public String unLock(String lockname){
SimpleLock lock = locks.get(lockname);
if(lock != null) {
locks.remove(lockname);
lock.unlock();
log.debug("Lock is unLocked!");
return "Lock is unLocked!";
}else {
log.debug("There is no lock or the lock is already unlocked! Create a lock if you need!");
return "There is no lock or the lock is already unlocked! Create a lock if you need!";
}
}
}
And here is a test RestController that I created to demonstrate the usage of the DistributedLockWithExtend
methods:
@Profile("!unit_test")
@RestController
@RequestMapping(TestLockController.PATH)
public class TestLockController {
public static final String PATH = BaseController.PATH + "/test-lock";
@Autowired
ApplyPolicyScheduler applyPolicySchedular;
@Autowired
DistributedLockWithExtend distributedLockWithExtend;
@GetMapping("/up")
public String testService(){
return "Service is up!";
}
@GetMapping("/invoke")
public String invokeTest(){
return distributedLockWithExtend.tryLock("testLockUtil", Duration.ofMinutes(1)) + "";
}
@GetMapping("/extend")
public String extendTest(){
return distributedLockWithExtend.extendLock("testLockUtil", Duration.ofMinutes(3)) + "";
}
@GetMapping("/unlock")
public String unlockTest(){
distributedLockWithExtend.unLock("testLockUtil");
return "Unlock called!";
}
}
Upvotes: 4