diff --git a/.gitignore b/.gitignore
index f8603a0d8..8dab6ac73 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ build/
.attach_pid*
micronaut/micronaut-test/dependency-reduced-pom.xml
dependency-reduced-pom.xml
+.vscode/settings.json
diff --git a/pom.xml b/pom.xml
index a98263772..bb21a5cd5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,6 +58,7 @@
providers/zookeeper/shedlock-provider-zookeeper-curator
providers/redis/shedlock-provider-redis-jedis4
providers/redis/shedlock-provider-redis-spring
+ providers/redis/shedlock-provider-redis-quarkus2
providers/dynamodb/shedlock-provider-dynamodb2
providers/cassandra/shedlock-provider-cassandra
providers/consul/shedlock-provider-consul
diff --git a/providers/redis/shedlock-provider-redis-quarkus2/pom.xml b/providers/redis/shedlock-provider-redis-quarkus2/pom.xml
new file mode 100644
index 000000000..2e272d482
--- /dev/null
+++ b/providers/redis/shedlock-provider-redis-quarkus2/pom.xml
@@ -0,0 +1,118 @@
+
+
+
+ shedlock-parent
+ net.javacrumbs.shedlock
+ 5.8.1-SNAPSHOT
+ ../../../pom.xml
+
+ 4.0.0
+
+ shedlock-provider-redis-quarkus2
+
+
+ 2.16.11.Final
+ true
+
+
+
+
+
+ io.quarkus.platform
+ quarkus-bom
+ ${quarkus.platform.version}
+ pom
+ import
+
+
+
+
+
+
+ net.javacrumbs.shedlock
+ shedlock-core
+ ${project.version}
+
+
+
+ net.javacrumbs.shedlock
+ shedlock-test-support
+ ${project.version}
+ test
+
+
+
+ net.javacrumbs.shedlock
+ shedlock-cdi-vintage
+ ${project.version}
+
+
+
+ io.quarkus
+ quarkus-scheduler
+
+
+ io.quarkus
+ quarkus-redis-client
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+
+
+
+
+ io.quarkus.platform
+ quarkus-maven-plugin
+ ${quarkus.platform.version}
+ true
+
+
+
+ build
+ generate-code
+ generate-code-tests
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+
+ net.javacrumbs.shedlock.provider.redis.quarkus
+
+
+
+
+
+
+
+
+
+ native
+
+
+ native
+
+
+
+ false
+ native
+
+
+
+
+
\ No newline at end of file
diff --git a/providers/redis/shedlock-provider-redis-quarkus2/src/main/java/net/javacrumbs/shedlock/provider/redis/quarkus2/QuarkusRedisLockProvider.java b/providers/redis/shedlock-provider-redis-quarkus2/src/main/java/net/javacrumbs/shedlock/provider/redis/quarkus2/QuarkusRedisLockProvider.java
new file mode 100644
index 000000000..976837fdd
--- /dev/null
+++ b/providers/redis/shedlock-provider-redis-quarkus2/src/main/java/net/javacrumbs/shedlock/provider/redis/quarkus2/QuarkusRedisLockProvider.java
@@ -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.
+ *
+ * See Set command
+ */
+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 valueCommands;
+ private final KeyCommands 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());
+ this.valueCommands = redisDataSource.value(String.class);
+ this.keyCommands = redisDataSource.key(String.class);
+ }
+
+
+ @Override
+ @NonNull
+ public Optional lock(@NonNull LockConfiguration lockConfiguration) {
+
+ long expireTime = getMillisUntil(lockConfiguration.getLockAtMostUntil());
+
+ String key = buildKey(lockConfiguration.getName(), this.environment);
+
+ 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 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 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());
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/providers/redis/shedlock-provider-redis-quarkus2/src/main/java/net/javacrumbs/shedlock/provider/redis/quarkus2/SchedulerLockFactory.java b/providers/redis/shedlock-provider-redis-quarkus2/src/main/java/net/javacrumbs/shedlock/provider/redis/quarkus2/SchedulerLockFactory.java
new file mode 100644
index 000000000..240cf0df5
--- /dev/null
+++ b/providers/redis/shedlock-provider-redis-quarkus2/src/main/java/net/javacrumbs/shedlock/provider/redis/quarkus2/SchedulerLockFactory.java
@@ -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 {
+
+ @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);
+ }
+}
diff --git a/providers/redis/shedlock-provider-redis-quarkus2/src/test/java/net/javacrumbs/shedlock/provider/redis/quarkus/LockedService.java b/providers/redis/shedlock-provider-redis-quarkus2/src/test/java/net/javacrumbs/shedlock/provider/redis/quarkus/LockedService.java
new file mode 100644
index 000000000..8645d45cf
--- /dev/null
+++ b/providers/redis/shedlock-provider-redis-quarkus2/src/test/java/net/javacrumbs/shedlock/provider/redis/quarkus/LockedService.java
@@ -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);
+ }
+
+}
diff --git a/providers/redis/shedlock-provider-redis-quarkus2/src/test/java/net/javacrumbs/shedlock/provider/redis/quarkus/QuarkusRedisLockProviderExceptionsTest.java b/providers/redis/shedlock-provider-redis-quarkus2/src/test/java/net/javacrumbs/shedlock/provider/redis/quarkus/QuarkusRedisLockProviderExceptionsTest.java
new file mode 100644
index 000000000..6e93a5d3a
--- /dev/null
+++ b/providers/redis/shedlock-provider-redis-quarkus2/src/test/java/net/javacrumbs/shedlock/provider/redis/quarkus/QuarkusRedisLockProviderExceptionsTest.java
@@ -0,0 +1,111 @@
+package net.javacrumbs.shedlock.provider.redis.quarkus;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+
+import io.quarkus.redis.datasource.RedisDataSource;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.QuarkusTestProfile;
+import io.quarkus.test.junit.TestProfile;
+import net.javacrumbs.shedlock.support.LockException;
+
+@QuarkusTest
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+@TestProfile(QuarkusRedisLockProviderExceptionsTest.MyProfile.class)
+public class QuarkusRedisLockProviderExceptionsTest {
+
+ public static class MyProfile implements QuarkusTestProfile {
+ @Override
+ public Map getConfigOverrides() {
+ return Map.of("shedlock.quarkus.throws-exception-if-locked", "true");
+ }
+ }
+
+ @Inject
+ LockedService lockedService;
+
+ @Inject
+ RedisDataSource dataSource;
+
+ private ExecutorService executorService = Executors.newFixedThreadPool(5);
+
+ @AfterEach
+ public void afterEach() {
+ lockedService.countReset();
+ try {
+ executorService.awaitTermination(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ @Order(1)
+ void test_warmUp() throws Exception {
+ lockedService.test(100);
+ assertEquals(1, lockedService.count());
+ }
+
+ @Test
+ void test_sequenceCalls() throws Exception {
+ lockedService.test(50);
+ lockedService.test(50);
+ assertEquals(2, lockedService.count());
+ }
+
+ @Test
+ void test_basicLock_withExcepions() throws Exception {
+
+ executorService.execute(() -> { lockedService.test(300); });
+ Thread.sleep(100);
+ assertTrue(isLockExist("test"), this::lockMessage);
+
+ assertThrows(LockException.class, () -> {
+ lockedService.test(300);
+ });
+
+
+ Thread.sleep(300); // wait to release lock and verify
+ assertFalse(isLockExist("test"), this::lockMessage);
+ assertEquals(1, lockedService.count());
+
+ }
+
+
+ @Test
+ void test_highConcurrency() throws Exception {
+
+ for (int i = 0; i < 100; i++) {
+ executorService.execute(() -> { lockedService.test(2000); });
+ Thread.sleep(10);
+ }
+
+ assertTrue(isLockExist("test"), this::lockMessage);
+ assertEquals(1, lockedService.count());
+ }
+
+
+ private boolean isLockExist(String name) {
+ return dataSource.key().exists("lock:my-app:test:" + name);
+ }
+
+ private String lockMessage() {
+ return "Current Keys: " + dataSource.key().keys("*");
+ }
+
+}
diff --git a/providers/redis/shedlock-provider-redis-quarkus2/src/test/java/net/javacrumbs/shedlock/provider/redis/quarkus/QuarkusRedisLockProviderIntegrationTest.java b/providers/redis/shedlock-provider-redis-quarkus2/src/test/java/net/javacrumbs/shedlock/provider/redis/quarkus/QuarkusRedisLockProviderIntegrationTest.java
new file mode 100644
index 000000000..50030b3a0
--- /dev/null
+++ b/providers/redis/shedlock-provider-redis-quarkus2/src/test/java/net/javacrumbs/shedlock/provider/redis/quarkus/QuarkusRedisLockProviderIntegrationTest.java
@@ -0,0 +1,59 @@
+package net.javacrumbs.shedlock.provider.redis.quarkus;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import javax.inject.Inject;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.redis.datasource.RedisDataSource;
+import io.quarkus.redis.datasource.value.ValueCommands;
+import io.quarkus.test.junit.QuarkusTest;
+import net.javacrumbs.shedlock.core.ExtensibleLockProvider;
+import net.javacrumbs.shedlock.core.LockProvider;
+import net.javacrumbs.shedlock.test.support.AbstractExtensibleLockProviderIntegrationTest;
+
+
+@QuarkusTest
+public class QuarkusRedisLockProviderIntegrationTest extends AbstractExtensibleLockProviderIntegrationTest {
+
+ @Inject
+ LockProvider lockProvider;
+
+ @Inject
+ RedisDataSource dataSource;
+
+ private ValueCommands values;
+
+ @BeforeEach
+ public void beforeEach() {
+ this.values = dataSource.value(String.class);
+ }
+
+ @Test
+ void warmUp() throws Exception {
+ this.values.getDataSource();
+ }
+
+ @Override
+ protected void assertUnlocked(String lockName) {
+ assertThat(getLock(lockName)).isNull();
+ }
+
+ @Override
+ protected void assertLocked(String lockName) {
+ assertThat(getLock(lockName)).isNotNull();
+ }
+
+ private String getLock(String lockName) {
+ return values.get("lock:my-app:test:"+lockName);
+ }
+
+ @Override
+ protected ExtensibleLockProvider getLockProvider() {
+ return (ExtensibleLockProvider) lockProvider;
+ }
+
+
+}
diff --git a/providers/redis/shedlock-provider-redis-quarkus2/src/test/resources/application.properties b/providers/redis/shedlock-provider-redis-quarkus2/src/test/resources/application.properties
new file mode 100644
index 000000000..e837805ef
--- /dev/null
+++ b/providers/redis/shedlock-provider-redis-quarkus2/src/test/resources/application.properties
@@ -0,0 +1,10 @@
+quarkus.application.name=my-app
+
+shedlock.defaults.lock-at-most-for=PT1h
+shedlock.quarkus.throws-exception-if-locked=false
+
+#Logs
+quarkus.log.console.format=%d{dd-MM-yyyy HH:mm:ss.SSS} %-5p [%c{2.}] (%t) %s%e%n
+quarkus.log.level=INFO
+quarkus.log.category."org.testcontainers".level=INFO
+