Skip to content

Commit

Permalink
Merge pull request #1 from rjojjr/refactor-lock-unsuccessful
Browse files Browse the repository at this point in the history
Release v1.1.0
  • Loading branch information
rjojjr authored Sep 4, 2024
2 parents 39d0714 + a75a967 commit e227903
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 62 deletions.
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM gradle:7.6.4-jdk17 AS builder
COPY . /project
WORKDIR /project
RUN gradle bootJar

FROM openjdk:17-jdk-slim-bullseye
ARG JAR_FILE=/project/build/libs/*.jar
COPY --from=builder ${JAR_FILE} ./application.jar
ENV TZ=America/Chicago
ENV TASKS_LOCK_API_ENABLED=true
ENV SPRING_PROFILES_ACTIVE=tasks-lock-api
ENTRYPOINT ["java", "-jar", "application.jar"]
5 changes: 3 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ plugins {
id 'java-library'
}

def build = '2'
def build = '9'

group = 'rjojjr.com.github'
//version = "1.0.0.$build-SNAPSHOT"
version = "1.0.0-RELEASE"
version = "1.1.0-RELEASE"
sourceCompatibility = '17'


Expand All @@ -29,6 +29,7 @@ dependencies {
api 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok:1.18.32'
annotationProcessor 'org.projectlombok:lombok:1.18.32'
runtimeOnly 'com.mysql:mysql-connector-j'
}

java {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
import org.springframework.stereotype.Service;
import rjojjr.com.github.taskslock.entity.TaskLockEntity;
import rjojjr.com.github.taskslock.entity.TaskLockEntityRepository;
import rjojjr.com.github.taskslock.exception.AcquireLockFailureException;
import rjojjr.com.github.taskslock.exception.ReleaseLockFailureException;
import rjojjr.com.github.taskslock.exception.TasksLockShutdownFailure;
import rjojjr.com.github.taskslock.models.TaskLock;
import rjojjr.com.github.taskslock.util.HostUtil;
import rjojjr.com.github.taskslock.util.ThreadUtil;

import org.hibernate.exception.DataException;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
Expand All @@ -34,64 +37,83 @@ public TaskLock acquireLock(String taskName, String contextId, boolean waitForLo

@Override
public TaskLock acquireLock(String taskName, String hostName, String contextId, boolean waitForLock) {
log.debug("Acquiring lock for task {}", taskName);
synchronized (releaseLock) {
var lockedAt = new Date();
var entity = taskLockEntityRepository.findById(taskName).orElseGet(() -> new TaskLockEntity(taskName, false, hostName, contextId, new Date()));
if (!entity.getIsLocked()) {
entity.setIsLocked(true);
entity.setLockedAt(lockedAt);
entity.setIsLockedByHost(hostName);
entity.setContextId(contextId);

taskLockEntityRepository.save(entity);
var taskLock = new TaskLock(
taskName,
contextId,
lockedAt,
() -> releaseLock(taskName)
);
taskLocks.add(taskLock);
log.debug("Task lock acquired for task {}", taskName);
return taskLock;
try {
log.debug("Acquiring lock for task {}", taskName);
synchronized (releaseLock) {
var lockedAt = new Date();
var entity = taskLockEntityRepository.findById(taskName).orElseGet(() -> new TaskLockEntity(taskName, false, hostName, contextId, new Date()));
try {
if (!entity.getIsLocked()) {
entity.setIsLocked(true);
entity.setLockedAt(lockedAt);
entity.setIsLockedByHost(hostName);
entity.setContextId(contextId);
taskLockEntityRepository.save(entity);
var taskLock = new TaskLock(
taskName,
contextId,
true,
lockedAt,
() -> releaseLock(taskName)
);
taskLocks.add(taskLock);
log.debug("Task lock acquired for task {}", taskName);
return taskLock;
}
} catch (DataException e) {
log.debug("Task lock not acquired for task {} because this worker lost in a race condition", taskName);
}
}
}

if (waitForLock) {
log.debug("Task lock not acquired for task {}, retrying in {}ms", taskName, RETRY_INTERVAL);
ThreadUtil.sleep(RETRY_INTERVAL);
return acquireLock(taskName, hostName, contextId, waitForLock);
if (waitForLock) {
log.debug("Task lock not acquired for task {}, retrying in {}ms", taskName, RETRY_INTERVAL);
ThreadUtil.sleep(RETRY_INTERVAL);
return acquireLock(taskName, hostName, contextId, true);
}
log.debug("Task lock not acquired for task {}", taskName);
return new TaskLock(taskName, contextId, false, null, () -> {});
} catch (Exception e) {
log.error("Error acquiring lock for task {}: {}", taskName, e.getMessage());
throw new AcquireLockFailureException(taskName, e);
}
log.debug("Task lock not acquired for task {}", taskName);
return null;
}

@Override
public void releaseLock(String taskName) {
log.debug("Releasing lock for task {}", taskName);
synchronized (releaseLock) {
var entity = taskLockEntityRepository.findById(taskName).orElseGet(() -> new TaskLockEntity(taskName, false, null, null, new Date()));
if (entity.getIsLocked()) {
entity.setIsLocked(false);
entity.setLockedAt(null);
entity.setIsLockedByHost(null);
entity.setContextId(null);
try {
synchronized (releaseLock) {
var entity = taskLockEntityRepository.findById(taskName).orElseGet(() -> new TaskLockEntity(taskName, false, null, null, new Date()));
if (entity.getIsLocked()) {
entity.setIsLocked(false);
entity.setLockedAt(null);
entity.setIsLockedByHost(null);
entity.setContextId(null);

taskLockEntityRepository.save(entity);
taskLocks = taskLocks.stream().filter(taskLock -> !taskLock.getTaskName().equals(taskName)).collect(Collectors.toSet());
taskLockEntityRepository.save(entity);
taskLocks = taskLocks.stream().filter(taskLock -> !taskLock.getTaskName().equals(taskName)).collect(Collectors.toSet());
}
}
} catch (Exception e) {
log.error("Error releasing lock for task {}: {}", taskName, e.getMessage());
throw new ReleaseLockFailureException(taskName, e);
}
}

@Override
public void onShutdown() {
log.info("Shutting down TasksLockService and releasing task-locks");
synchronized (releaseLock) {
for(TaskLock lock : taskLocks) {
lock.getRelease().run();
try {
synchronized (releaseLock) {
for(TaskLock lock : taskLocks) {
lock.getRelease().run();
}
}
log.info("Shut down TasksLockService and released task-locks");
} catch (Exception e) {
log.error("error shutting down TasksLock API and releasing task-locks: {}", e.getMessage());
throw new TasksLockShutdownFailure(e);
}
log.info("Shut down TasksLockService and released task-locks");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import rjojjr.com.github.taskslock.exception.AcquireLockFailureException;
import rjojjr.com.github.taskslock.exception.ReleaseLockFailureException;
import rjojjr.com.github.taskslock.exception.TasksLockShutdownFailure;
import rjojjr.com.github.taskslock.models.TaskLock;
import rjojjr.com.github.taskslock.models.TasksLockApiResponse;

Expand All @@ -29,23 +32,33 @@ public class TasksLocksApiClientService implements TasksLockService {

@Override
public TaskLock acquireLock(String taskName, String contextId, boolean waitForLock) {
var response = restTemplate.getForObject(String.format("%s/tasks-lock/api/v1/acquire?taskName=%s&contextId=%s&waitForLock=%s", apiProtoAndHost, taskName, contextId, waitForLock ? "true" : "false"), TasksLockApiResponse.class);
if(!response.getIsLockAcquired()){
return null;
}
var taskLock = new TaskLock(taskName, contextId, response.getLockedAt(), () -> releaseLock(taskName));
synchronized (releaseLock) {
taskLocks.add(taskLock);
try {
var response = restTemplate.getForObject(String.format("%s/tasks-lock/api/v1/acquire?taskName=%s&contextId=%s&waitForLock=%s", apiProtoAndHost, taskName, contextId, waitForLock ? "true" : "false"), TasksLockApiResponse.class);
if(!response.getIsLockAcquired()){
return new TaskLock(taskName, contextId, false, null, () -> {});
}
var taskLock = new TaskLock(taskName, contextId, true, response.getLockedAt(), () -> releaseLock(taskName));
synchronized (releaseLock) {
taskLocks.add(taskLock);
}
return taskLock;
} catch (Exception e) {
log.error("Error acquiring lock from TasksLock API: {}", e.getMessage());
throw new AcquireLockFailureException(taskName, e);
}
return taskLock;
}

@Override
public void releaseLock(String taskName) {
restTemplate.getForObject(String.format("%s/tasks-lock/api/v1/acquire?taskName=%s", apiProtoAndHost, taskName), TasksLockApiResponse.class);
synchronized (releaseLock) {
taskLocks = taskLocks.stream().filter(taskLock -> !taskLock.getTaskName().equals(taskName))
.collect(Collectors.toSet());
try {
restTemplate.getForObject(String.format("%s/tasks-lock/api/v1/release?taskName=%s", apiProtoAndHost, taskName), TasksLockApiResponse.class);
synchronized (releaseLock) {
taskLocks = taskLocks.stream().filter(taskLock -> !taskLock.getTaskName().equals(taskName))
.collect(Collectors.toSet());
}
} catch (Exception e) {
log.error("Error releasing lock from TasksLock API: {}", e.getMessage());
throw new ReleaseLockFailureException(taskName, e);
}
}

Expand All @@ -57,11 +70,17 @@ public TaskLock acquireLock(String taskName, String hostName, String contextId,
@Override
public void onShutdown() {
log.info("Shutting down TasksLockService and releasing task-locks");
synchronized (releaseLock) {
for(TaskLock lock : taskLocks) {
lock.getRelease().run();
try {
synchronized (releaseLock) {
for(TaskLock lock : taskLocks) {
lock.getRelease().run();
}
}
log.info("Shut down TasksLockService and released task-locks");
} catch (Exception e) {
log.error("error shutting down TasksLock API and releasing task-locks: {}", e.getMessage());
throw new TasksLockShutdownFailure(e);
}
log.info("Shut down TasksLockService and released task-locks");

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class TasksLockApiController {
@GetMapping("/acquire")
public TasksLockApiResponse acquire(@RequestParam String taskName, @RequestParam String contextId, @RequestParam(defaultValue = "true") Boolean waitForLock, HttpServletRequest request) {
var lock = tasksLockService.acquireLock(taskName, request.getRemoteHost(), contextId, waitForLock);
return new TasksLockApiResponse(taskName, lock != null, lock != null ? lock.getLockedAt() : null);
return new TasksLockApiResponse(taskName, lock.getIsLocked(), lock.getLockedAt());
}

@GetMapping("/release")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package rjojjr.com.github.taskslock.exception;

public class AcquireLockFailureException extends TasksLockApiException {
public AcquireLockFailureException(String taskName, Exception cause) {
super(String.format("error while acquiring lock for task %s: %s", taskName, cause), cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package rjojjr.com.github.taskslock.exception;

public class ReleaseLockFailureException extends TasksLockApiException {
public ReleaseLockFailureException(String taskName, Exception cause) {
super(String.format("error while releasing lock for %s: %s", taskName, cause.getMessage()), cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package rjojjr.com.github.taskslock.exception;

public class TasksLockApiException extends RuntimeException {
public TasksLockApiException(String message, Exception cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package rjojjr.com.github.taskslock.exception;

public class TasksLockShutdownFailure extends TasksLockApiException {
public TasksLockShutdownFailure(Exception cause) {
super("failed to run TasksLock API shutdown procedure", cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
public class TaskLock {
private String taskName;
private String contextId;
private Boolean isLocked;
private Date lockedAt;
private Runnable release;
}
10 changes: 8 additions & 2 deletions src/main/java/rjojjr/com/github/taskslock/util/HostUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import rjojjr.com.github.taskslock.exception.TasksLockApiException;

import java.net.InetAddress;
import java.net.UnknownHostException;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class HostUtil {
public static String getRemoteHost(){
return InetAddress.getLoopbackAddress().getHostName();
public static String getRemoteHost() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
throw new TasksLockApiException(e.getMessage(), e);
}
}
}
5 changes: 5 additions & 0 deletions src/main/resources/application-tasks-lock-api.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:mysql}:${MYSQL_PORT:3306}/${MYSQL_DB:default}?useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=${MYSQL_USERNAME:user}
spring.datasource.password=${MYSQL_PW:password}

0 comments on commit e227903

Please sign in to comment.