Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

shedlock-provider-redis-quarkus2 impl using vert.x redis client #1553

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ build/
.attach_pid*
micronaut/micronaut-test/dependency-reduced-pom.xml
dependency-reduced-pom.xml
.vscode/settings.json
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<module>providers/zookeeper/shedlock-provider-zookeeper-curator</module>
<module>providers/redis/shedlock-provider-redis-jedis4</module>
<module>providers/redis/shedlock-provider-redis-spring</module>
<module>providers/redis/shedlock-provider-redis-quarkus2</module>
<module>providers/dynamodb/shedlock-provider-dynamodb2</module>
<module>providers/cassandra/shedlock-provider-cassandra</module>
<module>providers/consul/shedlock-provider-consul</module>
Expand Down
118 changes: 118 additions & 0 deletions providers/redis/shedlock-provider-redis-quarkus2/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>shedlock-parent</artifactId>
<groupId>net.javacrumbs.shedlock</groupId>
<version>5.8.1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>shedlock-provider-redis-quarkus2</artifactId>
ricardojlrufino marked this conversation as resolved.
Show resolved Hide resolved

<properties>
<quarkus.platform.version>2.16.11.Final</quarkus.platform.version>
<skipITs>true</skipITs>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus.platform</groupId>
<artifactId>quarkus-bom</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>

<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-core</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-test-support</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-cdi-vintage</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-scheduler</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>

</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.quarkus.platform</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Automatic-Module-Name>
net.javacrumbs.shedlock.provider.redis.quarkus
</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<properties>
<skipITs>false</skipITs>
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
</profiles>
</project>

Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package net.javacrumbs.shedlock.provider.redis.quarkus2;

import static net.javacrumbs.shedlock.support.Utils.getHostname;
import static net.javacrumbs.shedlock.support.Utils.toIsoString;

import java.time.Duration;
import java.time.Instant;
import java.util.Optional;

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

import io.quarkus.redis.datasource.RedisDataSource;
import io.quarkus.redis.datasource.keys.KeyCommands;
import io.quarkus.redis.datasource.value.SetArgs;
import io.quarkus.redis.datasource.value.ValueCommands;
import io.quarkus.runtime.configuration.ConfigUtils;
import net.javacrumbs.shedlock.core.AbstractSimpleLock;
import net.javacrumbs.shedlock.core.ClockProvider;
import net.javacrumbs.shedlock.core.ExtensibleLockProvider;
import net.javacrumbs.shedlock.core.LockConfiguration;
import net.javacrumbs.shedlock.core.SimpleLock;
import net.javacrumbs.shedlock.support.LockException;
import net.javacrumbs.shedlock.support.annotation.NonNull;

/**
* Uses Redis's `SET resource-name anystring NX EX max-lock-ms-time` as locking mechanism.
* <p>
* See <a href="https://redis.io/commands/set">Set command</a>
*/
public class QuarkusRedisLockProvider implements ExtensibleLockProvider {

private static final Logger LOG = LoggerFactory.getLogger(QuarkusRedisLockProvider.class);

private static final String KEY_PREFIX = "lock";

private final RedisDataSource redisDataSource;
private final ValueCommands<String, String> valueCommands;
private final KeyCommands<String> keyCommands;
private final String environment;

private boolean throwsException;

public QuarkusRedisLockProvider(RedisDataSource dataSource, String appNameOrPrefix, boolean throwsException) {
this.redisDataSource = dataSource;
this.throwsException = throwsException;
this.environment = appNameOrPrefix + ":"+ String.join(":", ConfigUtils.getProfiles());
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please explain why the enviroment is used for the key. I do not think we have anything like this in any other lock provider.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto the appNameOrPrefix. If you have a use-case for it, let's just keep the appNameOrPrefix.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part of this code was copied from the spring implementation, it used the profile/environment name to compose the key. I ended up leaving it as it was.

this.valueCommands = redisDataSource.value(String.class);
this.keyCommands = redisDataSource.key(String.class);
}


@Override
@NonNull
public Optional<SimpleLock> lock(@NonNull LockConfiguration lockConfiguration) {

long expireTime = getMillisUntil(lockConfiguration.getLockAtMostUntil());

String key = buildKey(lockConfiguration.getName(), this.environment);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not need to pass instance variable environment as an argument?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't understand your point


String value = valueCommands.setGet(key, buildValue(), new SetArgs().nx().px(expireTime));
if(value != null) {
if(throwsException) throw new LockException("Already locked !");
return Optional.empty();
}else {
return Optional.of(new RedisLock(key, this, lockConfiguration));
}

}

private Optional<SimpleLock> extend(LockConfiguration lockConfiguration) {

long expireTime = getMillisUntil(lockConfiguration.getLockAtMostUntil());

String key = buildKey(lockConfiguration.getName(), this.environment);

boolean success = extendKeyExpiration(key, expireTime);

if (success) {
return Optional.of(new RedisLock(key, this, lockConfiguration));
}

return Optional.empty();
}


private boolean extendKeyExpiration(String key, long expiration) {
String value = valueCommands.setGet(key, buildValue(), new SetArgs().xx().px(expiration));
return value != null;

}

private void deleteKey(String key) {
keyCommands.del(key);
}


private static final class RedisLock extends AbstractSimpleLock {
private final String key;
private final QuarkusRedisLockProvider quarkusLockProvider;

private RedisLock(String key, QuarkusRedisLockProvider jedisLockProvider, LockConfiguration lockConfiguration) {
super(lockConfiguration);
this.key = key;
this.quarkusLockProvider = jedisLockProvider;
}

@Override
public void doUnlock() {
long keepLockFor = getMillisUntil(lockConfiguration.getLockAtLeastUntil());

// lock at least until is in the past
if (keepLockFor <= 0) {
try {
quarkusLockProvider.deleteKey(key);
} catch (Exception e) {
throw new LockException("Can not remove key", e);
}
} else {
quarkusLockProvider.extendKeyExpiration(key, keepLockFor);
}
}

@Override
@NonNull
protected Optional<SimpleLock> doExtend(@NonNull LockConfiguration newConfiguration) {
return quarkusLockProvider.extend(newConfiguration);
}
}

private static long getMillisUntil(Instant instant) {
return Duration.between(ClockProvider.now(), instant).toMillis();
}

static String buildKey(String lockName, String env) {
return String.format("%s:%s:%s", KEY_PREFIX, env, lockName);
}

private static String buildValue() {
return String.format("ADDED:%s@%s", toIsoString(ClockProvider.now()), getHostname());
}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package net.javacrumbs.shedlock.provider.redis.quarkus2;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Singleton;

import org.eclipse.microprofile.config.inject.ConfigProperty;

import io.quarkus.redis.datasource.RedisDataSource;
import net.javacrumbs.shedlock.core.LockProvider;


@ApplicationScoped
public class SchedulerLockFactory {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this? In Spring we let the users to specify the Lock Provider in the aplication code. Having it in the library feels surprising.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if I import a specific provider it's because I want to use it... I don't see the need to configure it manually


@ConfigProperty(name = "quarkus.application.name")
String app;

@ConfigProperty(name = "shedlock.quarkus.throws-exception-if-locked", defaultValue = "false")
boolean throwsException;

@Produces
@Singleton
public LockProvider lockProvider(RedisDataSource redisDataSource) {

return new QuarkusRedisLockProvider(redisDataSource, app, throwsException);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package net.javacrumbs.shedlock.provider.redis.quarkus;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import javax.enterprise.context.ApplicationScoped;

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

import io.smallrye.common.annotation.Blocking;
import net.javacrumbs.shedlock.cdi.SchedulerLock;

@ApplicationScoped
public class LockedService{

private static final Logger LOG = LoggerFactory.getLogger(LockedService.class);

private AtomicInteger count = new AtomicInteger(0);

@SchedulerLock(name = "test")
public void test(int time) {

execute(time);

LOG.info("Executing [DONE]");

}

public void execute(int time) {
count.incrementAndGet();
LOG.info("Executing ....(c="+count.get()+")");

try {
TimeUnit.MILLISECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}


@SchedulerLock(name = "testException")
@Blocking
public void testException() {

try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}


throw new RuntimeException("test");

}

public int count() {
LOG.info("getc=("+count.get()+")");
return count.get();
}

public void countReset() {
count.set(0);
}

}
Loading
Loading