Skip to content

Commit

Permalink
Merge pull request #2 from rjojjr/minor-refactoring
Browse files Browse the repository at this point in the history
Release v1.1.1
  • Loading branch information
rjojjr authored Sep 14, 2024
2 parents 87c87e3 + f81cd7e commit 41bdb38
Show file tree
Hide file tree
Showing 14 changed files with 221 additions and 102 deletions.
7 changes: 5 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# Even though this app is meant to be built with the included
# Gradle Wrapper, this is gradle image(instead of 'openjdk:17-jdk-slim-bullseye')
# is actually faster since it doesn't have
# to download this gradle distribution every time you build it.
FROM gradle:7.6.4-jdk17 AS builder
COPY . /project
WORKDIR /project
RUN echo 'removing build' && (rm -rf build || echo 'no previous build exists')
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"]
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ property to `true`.
You can run the Tasks Lock API as a docker container by building it with the included [Dockerfile](Dockerfile).
The container can be run by setting the proper SQL DB environment variables.

Additionally, you can pull the latest docker image from [Docker Hub](https://hub.docker.com):

`rjojjr91/tasks-lock-api:LATEST`

#### Required SQL Database Environment Variables

When this module is run in API mode, the following environment variables/application properties
Expand Down Expand Up @@ -103,8 +107,9 @@ public class SomeComponent {
var taskLock = this.tasksLockService.acquireLock("someUniqueTaskName", "someContextId", true);
// No need to check `isLocked` because this method will not finish unless it either
// acquires the lock or throws a RuntimeException for some unexpected reason

// Lock acquired, do something and release lock
...
...
taskLock.getRelease().run();
}
}
Expand Down
6 changes: 3 additions & 3 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 = '9'
def build = '11'

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


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package rjojjr.com.github.taskslock;

import lombok.extern.slf4j.Slf4j;
import rjojjr.com.github.taskslock.exception.TasksLockShutdownFailure;
import rjojjr.com.github.taskslock.models.TaskLock;

@Slf4j
abstract class DestroyableTasksLockService extends StatefulTasksLockService {

public DestroyableTasksLockService() {
super();
}

@Override
public void onDestroy() {
log.info("Shutting down TasksLockService and releasing task-locks");
try {
synchronized (dbLock) {
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,32 @@
package rjojjr.com.github.taskslock;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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;
import java.util.stream.Collectors;

@ConditionalOnProperty(name = "tasks-lock.client.enabled", havingValue = "false", matchIfMissing = true)
@Service
@RequiredArgsConstructor
@Slf4j
public class EmbeddedTasksLockService implements TasksLockService {
public class EmbeddedTasksLockService extends DestroyableTasksLockService {

private static final long RETRY_INTERVAL = 50;

private final TaskLockEntityRepository taskLockEntityRepository;

// TODO - Fetch lock status from here before querying the db
private Set<TaskLock> taskLocks = new HashSet<>();
private final Object releaseLock = new Object();
@Autowired
public EmbeddedTasksLockService(TaskLockEntityRepository taskLockEntityRepository) {
this.taskLockEntityRepository = taskLockEntityRepository;
}

@Override
public TaskLock acquireLock(String taskName, String contextId, boolean waitForLock) {
Expand All @@ -40,8 +36,8 @@ public TaskLock acquireLock(String taskName, String contextId, boolean waitForLo
@Override
public TaskLock acquireLock(String taskName, String hostName, String contextId, boolean waitForLock) {
try {
log.debug("Acquiring lock for task {}", taskName);
synchronized (releaseLock) {
log.debug("attempting to acquire lock for task {}, waiting for lock: {} contextId: {}", taskName, waitForLock, contextId);
synchronized (dbLock) {
// TODO - Synchronize at this level across module instances(maybe some kind of db table lock?)
var lockedAt = new Date();
var entity = taskLockEntityRepository.findById(taskName).orElseGet(() -> new TaskLockEntity(taskName, false, hostName, contextId, new Date()));
Expand All @@ -59,8 +55,8 @@ public TaskLock acquireLock(String taskName, String hostName, String contextId,
lockedAt,
() -> releaseLock(taskName)
);
taskLocks.add(taskLock);
log.debug("Task lock acquired for task {}", taskName);
cacheLock(taskLock);
log.debug("acquired lock for task {} contextId: {}", taskName, contextId);
return taskLock;
}
} catch (DataException e) {
Expand All @@ -69,54 +65,41 @@ public TaskLock acquireLock(String taskName, String hostName, String contextId,
}

if (waitForLock) {
log.debug("Task lock not acquired for task {}, retrying in {}ms", taskName, RETRY_INTERVAL);
log.debug("task lock not acquired for task {}, retrying in {}ms contextId: {}", taskName, RETRY_INTERVAL, contextId);
ThreadUtil.sleep(RETRY_INTERVAL);
return acquireLock(taskName, hostName, contextId, true);
}
log.debug("Task lock not acquired for task {}", taskName);
log.debug("did not acquire lock for task {} contextId: {}", taskName, contextId);
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.error("error acquiring lock for task {}: {} contextId: {}", taskName, e.getMessage(), contextId);
throw new AcquireLockFailureException(taskName, contextId, e);
}
}

@Override
public void releaseLock(String taskName) {
log.debug("Releasing lock for task {}", taskName);
public String releaseLock(String taskName) {
log.debug("attempting to release lock for task {}", taskName);
try {
synchronized (releaseLock) {
String contextId;
synchronized (dbLock) {
var entity = taskLockEntityRepository.findById(taskName).orElseGet(() -> new TaskLockEntity(taskName, false, null, null, new Date()));
contextId = entity.getContextId();
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());
}
}
} 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");
try {
synchronized (releaseLock) {
for(TaskLock lock : taskLocks) {
lock.getRelease().run();
}
}
log.info("Shut down TasksLockService and released task-locks");
removeLock(taskName, contextId);
log.debug("released lock for task {}, contextId: {}", taskName, contextId);
return contextId;
} catch (Exception e) {
log.error("error shutting down TasksLock API and releasing task-locks: {}", e.getMessage());
throw new TasksLockShutdownFailure(e);
log.error("error releasing lock for task {}: {}", taskName, e.getMessage());
throw new ReleaseLockFailureException(taskName, null, e);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package rjojjr.com.github.taskslock;

import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import rjojjr.com.github.taskslock.models.TaskLock;

import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@NoArgsConstructor
abstract class StatefulTasksLockService implements TasksLockService {

protected Set<TaskLock> taskLocks = new HashSet<>();
protected final Object dbLock = new Object();
private final Object cacheLock = new Object();

protected void cacheLock(TaskLock taskLock) {
synchronized (cacheLock) {
log.debug("adding lock for task {} to cache contextId: {}", taskLock.getTaskName(), taskLock.getContextId());
taskLocks.add(taskLock);
}
}

protected void removeLock(String taskName, String contextId) {
synchronized (cacheLock) {
log.debug("removing lock for task {} from cache contextId: {}", taskName, contextId);
taskLocks = taskLocks.stream().filter(taskLock -> !taskLock.getTaskName().equals(taskName)).collect(Collectors.toSet());
}
}
}
25 changes: 13 additions & 12 deletions src/main/java/rjojjr/com/github/taskslock/TasksLockService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,34 @@ public interface TasksLockService {

/**
* Acquire lock for task
* @param taskName
* @param contextId
* @param waitForLock
* @return
* @param taskName unique task identifier
* @param contextId a tracing identifier provided by the consumer
* @param waitForLock block until lock is acquired
* @return TaskLock object
*/
TaskLock acquireLock(String taskName, String contextId, boolean waitForLock);

/**
* Release lock for task
* @param taskName
* @param taskName unique task identifier
* @return contextId a tracing identifier provided by the consumer
*/
void releaseLock(String taskName);
String releaseLock(String taskName);

/**
* Acquire lock with embedded impl.
* @param taskName
* @param hostName
* @param contextId
* @param waitForLock
* @return
* @param taskName unique task identifier
* @param hostName hostname of application/container acquiring lock
* @param contextId a tracing identifier provided by the consumer
* @param waitForLock block until lock is acquired
* @return TaskLock object
*/
TaskLock acquireLock(String taskName, String hostName, String contextId, boolean waitForLock);

/**
* Release all locks & cleanup on shutdown
*/
@PreDestroy
void onShutdown();
void onDestroy();

}
Loading

0 comments on commit 41bdb38

Please sign in to comment.