### schedule(Runnable, Instant) Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockableTaskScheduler.md Schedules a task to run once at a specified start time. ```APIDOC ## schedule(Runnable, Instant) ### Description Schedules a task to run once at a specified start time. ### Parameters - **task** (Runnable) - Required - Task to schedule (will be wrapped) - **startTime** (Instant) - Required - When to execute the task ### Returns - **ScheduledFuture** - Future for the scheduled task ``` -------------------------------- ### LockProvider Usage Example Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockProvider.md Demonstrates initializing a JdbcTemplateLockProvider and attempting to acquire a lock for a scheduled task. ```java LockProvider lockProvider = new JdbcTemplateLockProvider( JdbcTemplateLockProvider.Configuration.builder() .withJdbcTemplate(jdbcTemplate) .withDefaultLockAtMostFor(Duration.ofMinutes(10)) .build() ); LockConfiguration config = new LockConfiguration( Instant.now(), "myScheduledTask", Duration.ofMinutes(10), Duration.ZERO ); Optional lock = lockProvider.lock(config); if (lock.isPresent()) { try { // Execute protected task } finally { lock.get().unlock(); } } else { // Another node holds the lock System.out.println("Lock not acquired, task skipped"); } ``` -------------------------------- ### Instantiate DefaultLockingTaskExecutor Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/DefaultLockingTaskExecutor.md Example of creating an executor instance using a JdbcTemplateLockProvider. ```java LockProvider lockProvider = new JdbcTemplateLockProvider(dataSource); DefaultLockingTaskExecutor executor = new DefaultLockingTaskExecutor(lockProvider); ``` -------------------------------- ### Unlock Usage Example Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/SimpleLock.md Demonstrates the standard pattern for acquiring and releasing a lock using a try-finally block. ```java Optional lockOpt = lockProvider.lock(lockConfig); if (lockOpt.isPresent()) { SimpleLock lock = lockOpt.get(); try { // Execute protected task } finally { lock.unlock(); // Must always release } } ``` -------------------------------- ### Handling LockException Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/errors.md Example of catching a LockException when a lock provider fails due to storage unavailability. ```java // Thrown by lock providers on storage failures try { Optional lock = lockProvider.lock(config); } catch (LockException e) { // Storage system is unavailable log.error("Failed to acquire lock", e); } ``` -------------------------------- ### Minimal Spring Configuration Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/README.md Basic setup for enabling ShedLock with a JDBC provider in a Spring configuration class. ```java @EnableSchedulerLock(defaultLockAtMostFor = "10m") public class Config { @Bean public LockProvider lockProvider(DataSource dataSource) { return new JdbcTemplateLockProvider(dataSource); } } ``` -------------------------------- ### Configure lockAtMostFor Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/SchedulerLock.md Examples of setting the maximum duration a lock is held as a safety mechanism. ```java @Scheduled(cron = "0 0 * * * *") @SchedulerLock(name = "dailyTask", lockAtMostFor = "23h") public void dailyTask() { // Executes once per day. Lock held for at most 23 hours // as a safety net in case the JVM crashes. } @Scheduled(fixedDelay = 60000) @SchedulerLock(name = "quickTask", lockAtMostFor = "PT1M") public void quickTask() { // Quick task with 1-minute safety timeout } ``` -------------------------------- ### Execute Runnable Task with Lock Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockingTaskExecutor.md Method signature and usage example for executing a simple Runnable task. ```java void executeWithLock(Runnable task, LockConfiguration lockConfig) ``` ```java lockingTaskExecutor.executeWithLock( () -> { // Task code System.out.println("Executing protected task"); }, lockConfig ); ``` -------------------------------- ### Custom Lock Provider with Delegate Pattern Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Example of creating a custom LockProvider by wrapping an existing one (delegate). Allows for custom logic, such as performing actions after a lock is obtained. ```java public class MyLockProvider implements LockProvider { private final LockProvider delegate; public MyLockProvider(LockProvider delegate) { this.delegate = delegate; } @Override public Optional lock(LockConfiguration lockConfiguration) { Optional lock = delegate.lock(lockConfiguration); if (lock.isPresent()) { // do something } return lock; } } ``` -------------------------------- ### Configure LockableTaskScheduler Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockableTaskScheduler.md Setup the scheduler with Spring configuration, enabling scheduling and defining the LockProvider. ```java @Configuration @EnableScheduling @EnableSchedulerLock(defaultLockAtMostFor = "10m") public class SchedulingConfig { @Bean public TaskScheduler taskScheduler() { return new ThreadPoolTaskScheduler(); } @Bean public LockableTaskScheduler lockableTaskScheduler( TaskScheduler taskScheduler, LockManager lockManager) { return new LockableTaskScheduler(taskScheduler, lockManager); } @Bean public LockProvider lockProvider(DataSource dataSource) { return new JdbcTemplateLockProvider(dataSource); } } ``` -------------------------------- ### Nested Lock Extension Example Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockExtender.md Demonstrates how LockExtender tracks locks per thread, allowing nested tasks to extend their specific locks independently. ```java @SchedulerLock(name = "outer") public void outerTask() { try { LockExtender.extendActiveLock(Duration.ofMinutes(20), Duration.ZERO); innerTask(); // Different lock } finally { } } @SchedulerLock(name = "inner") public void innerTask() { // Extends "inner" lock, not "outer" LockExtender.extendActiveLock(Duration.ofMinutes(10), Duration.ZERO); } ``` -------------------------------- ### Spring Usage Pattern Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockManager.md Example of configuring and using LockManager in a Spring application with AOP. ```java @Configuration @EnableScheduling @EnableSchedulerLock(defaultLockAtMostFor = "10m") public class MySchedulingConfig { @Bean public LockProvider lockProvider(DataSource dataSource) { return new JdbcTemplateLockProvider(dataSource); } } @Component public class MyScheduledTasks { @Scheduled(cron = "0 0 * * * *") @SchedulerLock(name = "dailyTask") public void myScheduledTask() { // LockManager is applied automatically via AOP // It extracts @SchedulerLock annotation to get lock parameters performWork(); } } ``` -------------------------------- ### Configure lockAtLeastFor Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/SchedulerLock.md Examples of setting the minimum duration a lock is held to prevent rapid re-execution due to clock skew. ```java @Scheduled(cron = "0 */15 * * * *") @SchedulerLock( name = "frequentTask", lockAtMostFor = "14m", lockAtLeastFor = "14m" ) public void frequentTask() { // Ensures task runs at most every 15 minutes // Lock held for at least 14 minutes prevents // re-execution even if task finishes quickly } @Scheduled(fixedDelay = 5000) @SchedulerLock( name = "instantTask", lockAtMostFor = "10s", lockAtLeastFor = "0s" ) public void instantTask() { // Lock released immediately after completion // Next execution can start immediately on another node } ``` -------------------------------- ### Configure Testing Lock Providers Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/configuration.md Provides examples for using an in-memory provider or a no-op implementation to bypass distributed locking during tests. ```java @Configuration @EnableScheduling public class TestSchedulingConfig { @Bean public LockProvider lockProvider() { // In-memory lock provider for testing return new InMemoryLockProvider(); } @Bean(destroyMethod = "destroy") public LockableTaskScheduler testTaskScheduler( TaskScheduler taskScheduler, LockManager lockManager) { return new LockableTaskScheduler(taskScheduler, lockManager); } } ``` ```java public class NoOpLockProvider implements LockProvider { @Override public Optional lock(LockConfiguration lockConfiguration) { // Always return a no-op lock return Optional.of(new SimpleLock() { @Override public void unlock() { } }); } } ``` -------------------------------- ### Instantiate DefaultLockingTaskExecutor with Custom Listener Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/DefaultLockingTaskExecutor.md Example of creating an executor instance with a custom LockingTaskExecutorListener. ```java DefaultLockingTaskExecutor executor = new DefaultLockingTaskExecutor( lockProvider, new MyLockingTaskExecutorListener() ); ``` -------------------------------- ### Advanced JdbcTemplate Lock Provider Configuration Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Example of fine-grained configuration for JdbcTemplateLockProvider, including custom table/column names, locked-by value, and case sensitivity. ```java new JdbcTemplateLockProvider(builder() .withTableName("shdlck") .withColumnNames(new ColumnNames("n", "lck_untl", "lckd_at", "lckd_by")) .withJdbcTemplate(new JdbcTemplate(getDatasource())) .withLockedByValue("my-value") .withDbUpperCase(true) .build()) ``` -------------------------------- ### Configure Lock Name Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/SchedulerLock.md Examples of using the name attribute to identify locks, including support for SpEL expressions. ```java @Scheduled(cron = "0 0 * * * *") @SchedulerLock(name = "dailyReportGeneration") public void generateDailyReport() { } @Scheduled(fixedDelay = 60000) @SchedulerLock(name = "healthCheck") public void performHealthCheck() { } ``` ```java @SchedulerLock(name = "task-#{T(java.lang.System).getProperty('app.instance')}") public void instanceAwareTask() { } // With method parameters: @SchedulerLock(name = "process-#{#customerId}") public void processForCustomer(String customerId) { } ``` -------------------------------- ### Schedule task at fixed rate Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockableTaskScheduler.md Schedules tasks to run repeatedly at fixed intervals, with optional start time. ```java @Override public ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period) ``` ```java Instant oneHourFromNow = Instant.now().plusSeconds(3600); taskScheduler.scheduleAtFixedRate( () -> System.out.println("Running every 30 seconds"), oneHourFromNow, Duration.ofSeconds(30) ); ``` ```java @Override public ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period) ``` ```java taskScheduler.scheduleAtFixedRate( () -> System.out.println("Running every 30 seconds"), Duration.ofSeconds(30) ); ``` -------------------------------- ### Configure LockableTaskScheduler Bean Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockableTaskScheduler.md Example of defining a LockableTaskScheduler as a Spring bean. ```java @Bean public TaskScheduler lockableTaskScheduler( TaskScheduler defaultTaskScheduler, LockManager lockManager) { return new LockableTaskScheduler(defaultTaskScheduler, lockManager); } ``` -------------------------------- ### Multiple Configuration and Method-Level Overrides Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/EnableSchedulerLock.md Demonstrates how to define a base configuration and override locking parameters on specific scheduled methods. ```java // Base configuration @Configuration @EnableScheduling @EnableSchedulerLock(defaultLockAtMostFor = "10m") public class DefaultSchedulingConfig { @Bean public LockProvider lockProvider(DataSource dataSource) { return new JdbcTemplateLockProvider(dataSource); } } // Specific method configuration @Component public class ScheduledTasks { @Scheduled(cron = "0 0 * * * *") @SchedulerLock( name = "longRunningTask", lockAtMostFor = "12h", // Override default lockAtLeastFor = "0s" ) public void longRunningTask() { } @Scheduled(fixedDelay = 60000) @SchedulerLock(name = "quickTask") // Uses default 10m public void quickTask() { } ``` -------------------------------- ### Get Lock Name Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockConfiguration.md Retrieves the unique identifier for the lock instance. ```java public String getName() ``` -------------------------------- ### Handling LockCanNotBeExtendedException Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockExtender.md Example of catching LockCanNotBeExtendedException when a lock expires before an extension can be applied. ```java @Scheduled(cron = "0 0 * * * *") @SchedulerLock( name = "task", lockAtMostFor = "30m" ) public void myTask() { // Work for 28 minutes performInitialWork(); // Try to extend (but lock expires in 2 minutes) try { LockExtender.extendActiveLock( Duration.ofMinutes(30), Duration.ZERO ); } catch (LockCanNotBeExtendedException e) { // Lock has expired, another node may be running this task log.warn("Cannot extend lock, stopping work"); return; // Stop execution } // Continuation (only if extension succeeded) performContinuationWork(); } ``` -------------------------------- ### Acquire and Release a Lock in Java Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/SimpleLock.md Demonstrates the standard pattern for acquiring a lock, executing a task, and ensuring release in a finally block. ```java LockProvider lockProvider = new JdbcTemplateLockProvider(...); LockConfiguration config = new LockConfiguration( ClockProvider.now(), "longRunningTask", Duration.ofMinutes(30), Duration.ZERO ); Optional lockOpt = lockProvider.lock(config); if (lockOpt.isPresent()) { SimpleLock lock = lockOpt.get(); try { // Execute task that takes 25 minutes performLongRunningTask(); } finally { lock.unlock(); } } else { System.out.println("Could not acquire lock"); } ``` -------------------------------- ### Get current time Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/OVERVIEW.md Retrieves the current time using the configured ClockProvider. ```java Instant now = ClockProvider.now(); ``` -------------------------------- ### Get Lock At Most For Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockConfiguration.md Retrieves the configured maximum duration (safety timeout) for the lock. ```java public Duration getLockAtMostFor() ``` -------------------------------- ### Configure ReactiveRedisLockProvider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Configure the ReactiveRedisLockProvider using a ReactiveRedisConnectionFactory. Use the builder pattern to set the environment. ```java import net.javacrumbs.shedlock.provider.redis.spring.ReactiveRedisLockProvider; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; ... @Bean public LockProvider lockProvider(ReactiveRedisConnectionFactory connectionFactory) { return new ReactiveRedisLockProvider.Builder(connectionFactory) .environment(ENV) .build(); } ``` -------------------------------- ### Configure RedisLockProvider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Configure the RedisLockProvider by injecting a RedisConnectionFactory and specifying the environment. This uses a basic SETNX mechanism. ```java import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider; import org.springframework.data.redis.connection.RedisConnectionFactory; ... @Bean public LockProvider lockProvider(RedisConnectionFactory connectionFactory) { return new RedisLockProvider(connectionFactory, ENV); } ``` -------------------------------- ### Get Lock At Least For Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockConfiguration.md Retrieves the configured minimum duration the lock must be held. ```java public Duration getLockAtLeastFor() ``` -------------------------------- ### Configure Hazelcast Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Include the Hazelcast dependency and set up the HazelcastLockProvider using a HazelcastInstance. ```xml net.javacrumbs.shedlock shedlock-provider-hazelcast4 7.7.0 ``` ```java import net.javacrumbs.shedlock.provider.hazelcast4.HazelcastLockProvider; ... @Bean public HazelcastLockProvider lockProvider(HazelcastInstance hazelcastInstance) { return new HazelcastLockProvider(hazelcastInstance); } ``` -------------------------------- ### Get Lock At Most Until Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockConfiguration.md Returns the safety timeout instant, calculated as createdAt plus lockAtMostFor. ```java public Instant getLockAtMostUntil() ``` -------------------------------- ### Configure DynamoDBLockProvider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Configure the DynamoDBLockProvider by providing a DynamoDbClient and the table name. The lock table requires '_id' as a partition key. ```java import net.javacrumbs.shedlock.provider.dynamodb2.DynamoDBLockProvider; ... @Bean public LockProvider lockProvider(software.amazon.awssdk.services.dynamodb.DynamoDbClient dynamoDB) { return new DynamoDBLockProvider(dynamoDB, "Shedlock"); } ``` -------------------------------- ### Configure RedisLockProvider Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/configuration.md Initializes a LockProvider using a Spring RedisConnectionFactory. ```java @Bean public LockProvider lockProvider(RedisConnectionFactory connectionFactory) { return new RedisLockProvider(connectionFactory); } ``` -------------------------------- ### Executing a Task with Lock Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockingTaskExecutor.md Demonstrates how to execute a task and handle the resulting TaskResult based on whether the lock was acquired. ```java TaskResult result = executor.executeWithLock( () -> calculateValue(), lockConfig ); if (result.wasExecuted()) { Integer value = result.getResult(); processResult(value); } else { // Task was skipped because lock was held by another node log.debug("Task skipped, lock already held"); } ``` -------------------------------- ### Setting Initial lockAtMostFor Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockExtender.md Configure a reasonable initial timeout and use LockExtender only as a fallback for extended execution times. ```java // ✓ GOOD: Initial timeout covers typical execution @SchedulerLock( name = "task", lockAtMostFor = "30m" // Typical case takes 20 minutes ) public void myTask() { performWork(); // Only extend if needed if (needsMoreTime()) { LockExtender.extendActiveLock(Duration.ofMinutes(20), Duration.ZERO); } } ``` -------------------------------- ### Get Lock At Least Until Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockConfiguration.md Returns the instant until which the lock must be held, calculated as createdAt plus lockAtLeastFor. ```java public Instant getLockAtLeastUntil() ``` -------------------------------- ### Verify Lock in Scheduled Methods Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockAssert.md Example of using LockAssert within a Spring-scheduled task to ensure the lock is active. ```java @Component public class ScheduledTasks { @Scheduled(cron = "0 0 * * * *") @SchedulerLock(name = "myTask") public void myTask() { // Verify lock is held LockAssert.assertLocked(); // Safe to proceed with business logic performCriticalOperation(); } } ``` -------------------------------- ### Configure Redis (Lettuce) Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Add the Lettuce dependency and configure the LettuceLockProvider with a Redis connection and environment. ```xml net.javacrumbs.shedlock shedlock-provider-redis-lettuce 7.7.0 ``` ```java import net.javacrumbs.shedlock.provider.redis.lettuce.LettuceLockProvider; ... @Bean public LockProvider lockProvider(StatefulRedisConnection connection) { return new LettuceLockProvider(connection, ENV); } ``` -------------------------------- ### Get Unlock Time Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockConfiguration.md Returns the earliest instant the lock can be released, ensuring the minimum hold duration is respected. ```java public Instant getUnlockTime() ``` -------------------------------- ### Configure Redis (Jedis) Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Add the Jedis dependency and configure the JedisLockProvider with a JedisPool and environment. ```xml net.javacrumbs.shedlock shedlock-provider-redis-jedis4 7.7.0 ``` ```java import net.javacrumbs.shedlock.provider.redis.jedis.JedisLockProvider; ... @Bean public LockProvider lockProvider(JedisPool jedisPool) { return new JedisLockProvider(jedisPool, ENV); } ``` -------------------------------- ### void executeWithLock(Runnable task, LockConfiguration lockConfig) Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockingTaskExecutor.md Executes a simple task with lock protection. If the lock cannot be acquired, the task is skipped. ```APIDOC ## void executeWithLock(Runnable task, LockConfiguration lockConfig) ### Description Executes a task with lock protection. The simplest form for tasks that do not return a value. ### Parameters - **task** (Runnable) - Required - The task to execute - **lockConfig** (LockConfiguration) - Required - The lock configuration specifying lock name and durations ### Example ```java lockingTaskExecutor.executeWithLock( () -> { System.out.println("Executing protected task"); }, lockConfig ); ``` ``` -------------------------------- ### Import Apache Ignite Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Add this dependency to your project to use the Apache Ignite lock provider. ```xml net.javacrumbs.shedlock shedlock-provider-ignite 7.7.0 ``` -------------------------------- ### Handle UnsupportedOperationException for Lock Extension Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/errors.md Demonstrates catching an UnsupportedOperationException when a provider does not support lock extension. ```java SimpleLock lock = lockProvider.lock(config).orElseThrow(); try { Optional extended = lock.extend( Duration.ofMinutes(20), Duration.ZERO ); } catch (UnsupportedOperationException e) { // Provider doesn't support extension log.warn("Lock extension not supported by this provider"); } ``` -------------------------------- ### Configure ReactiveStreamsMongoLockProvider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Configure the ReactiveStreamsMongoLockProvider using a MongoClient. Specify the database name for lock management. ```java import net.javacrumbs.shedlock.provider.mongo.reactivestreams.ReactiveStreamsMongoLockProvider; ... @Bean public LockProvider lockProvider(MongoClient mongo) { return new ReactiveStreamsMongoLockProvider(mongo.getDatabase(databaseName)); } ``` -------------------------------- ### Use ClockProvider utility Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/types.md Provides a global clock instance for timestamps, useful for testing time-dependent logic. ```java public class ClockProvider { public static void setClock(Clock clock) public static Instant now() } ``` -------------------------------- ### Configure OpenSearch Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Add the OpenSearch Java dependency and configure the OpenSearchLockProvider with an OpenSearchClient. ```xml net.javacrumbs.shedlock shedlock-provider-opensearch-java 7.7.0 ``` ```java import net.javacrumbs.shedlock.provider.opensearch.java.OpenSearchLockProvider; ... @Bean public OpenSearchLockProvider lockProvider(OpenSearchClient openSearchClient) { return new OpenSearchLockProvider(openSearchClient); } ``` -------------------------------- ### Kotlin Final Method Handling Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockAssert.md Shows the requirement to use the 'open' keyword in Kotlin to allow proxy-based locking to function correctly with LockAssert. ```kotlin // ❌ In Kotlin, this fails (method is final by default) @Scheduled(cron = "0 0 * * * *") @SchedulerLock(name = "task") fun myTask() { LockAssert.assertLocked() // IllegalStateException performWork() } // ✓ In Kotlin, make method open @Scheduled(cron = "0 0 * * * *") @SchedulerLock(name = "task") open fun myTask() { // 'open' keyword required LockAssert.assertLocked() // OK performWork() } ``` -------------------------------- ### Configure Cassandra Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Include the Cassandra dependency and configure the CassandraLockProvider with a CqlSession and table configuration. ```xml net.javacrumbs.shedlock shedlock-provider-cassandra 7.7.0 ``` ```java import net.javacrumbs.shedlock.provider.cassandra.CassandraLockProvider; import net.javacrumbs.shedlock.provider.cassandra.CassandraLockProvider.Configuration; ... @Bean public CassandraLockProvider lockProvider(CqlSession cqlSession) { return new CassandraLockProvider(Configuration.builder().withCqlSession(cqlSession).withTableName("lock").build()); } ``` ```sql CREATE KEYSPACE shedlock with replication={'class':'SimpleStrategy', 'replication_factor':1} and durable_writes=true; CREATE TABLE shedlock.lock (name text PRIMARY KEY, lockUntil timestamp, lockedAt timestamp, lockedBy text); ``` -------------------------------- ### Configure Exposed Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Kotlin configuration for the Exposed lock provider, requiring a Database instance. ```kotlin import net.javacrumbs.shedlock.provider.exposed.ExposedLockProvider ... @Bean fun getLockProvider(database: Database) = ExposedLockProvider(database) ``` -------------------------------- ### Multi-tenancy Lock Provider Implementation Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Abstract class for implementing multi-tenancy by managing separate LockProviders per tenant. Requires defining how to create a provider and extract the tenant name. ```java private static abstract class MultiTenancyLockProvider implements LockProvider { private final ConcurrentHashMap providers = new ConcurrentHashMap<>(); @Override public Optional lock(LockConfiguration lockConfiguration) { String tenantName = getTenantName(lockConfiguration); return providers.computeIfAbsent(tenantName, this::createLockProvider).lock(lockConfiguration); } protected abstract LockProvider createLockProvider(String tenantName); protected abstract String getTenantName(LockConfiguration lockConfiguration); } ``` -------------------------------- ### Check for Lock Extension Support Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/errors.md Use an instance check to verify if the provider supports extension before attempting to extend a lock. ```java // Check if provider supports extension if (lockProvider instanceof ExtensibleLockProvider) { // Safe to extend } else { // Use high lockAtMostFor instead } ``` -------------------------------- ### Configure AdviceMode Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/EnableSchedulerLock.md Determines the Spring AOP proxy mechanism. Choose between standard PROXY or ASPECTJ weaving. ```java @EnableSchedulerLock(mode = AdviceMode.PROXY, ...) // Default @EnableSchedulerLock(mode = AdviceMode.ASPECTJ, ...) // AspectJ weaving ``` -------------------------------- ### Recommended Exception Handling Pattern Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/errors.md Use this pattern to distinguish between lock misconfigurations, storage provider errors, and business logic failures within a scheduled task. ```java @Scheduled(cron = "0 0 * * * *") @SchedulerLock(name = "task") public void protectedTask() { try { // Verify lock is held LockAssert.assertLocked(); // Business logic performWork(); } catch (LockAssert.IllegalStateException e) { // Lock not held - misconfiguration log.error("Lock not held, check configuration", e); throw e; } catch (LockException e) { // Storage/provider error log.error("Cannot verify lock state", e); throw e; } catch (Exception e) { // Business logic error log.error("Task failed", e); throw e; } } ``` -------------------------------- ### Import Datastore Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Add this dependency to your project to use the Datastore lock provider. ```xml net.javacrumbs.shedlock shedlock-provider-datastore 7.7.0 ``` -------------------------------- ### Configure Etcd Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Configure the Etcd lock provider by providing an Etcd client instance. ```java import net.javacrumbs.shedlock.provider.etcd.jetcd.EtcdLockProvider; ... @Bean public LockProvider lockProvider(Client client) { return new EtcdLockProvider(client); } ``` -------------------------------- ### Configure MongoLockProvider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Configure the MongoLockProvider by injecting a MongoClient instance. Specify the database name for lock operations. ```java import net.javacrumbs.shedlock.provider.mongo.MongoLockProvider; ... @Bean public LockProvider lockProvider(MongoClient mongo) { return new MongoLockProvider(mongo.getDatabase(databaseName)); } ``` -------------------------------- ### Configure Datastore Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Configure the Datastore lock provider by providing a Google Cloud Datastore instance. ```java import net.javacrumbs.shedlock.provider.datastore.DatastoreLockProvider; ... @Bean public LockProvider lockProvider(com.google.cloud.datastore.Datastore datastore) { return new DatastoreLockProvider(datastore); } ``` -------------------------------- ### Configure Couchbase Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Use the Couchbase Java client 3 dependency and configure the CouchbaseLockProvider with a Bucket instance. ```xml net.javacrumbs.shedlock shedlock-provider-couchbase-javaclient3 7.7.0 ``` ```java import net.javacrumbs.shedlock.provider.couchbase.javaclient.CouchbaseLockProvider; ... @Bean public CouchbaseLockProvider lockProvider(Bucket bucket) { return new CouchbaseLockProvider(bucket); } ``` -------------------------------- ### void executeWithLock(Task task, LockConfiguration lockConfig) Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockingTaskExecutor.md Executes a task that may throw checked exceptions with lock protection. ```APIDOC ## void executeWithLock(Task task, LockConfiguration lockConfig) ### Description Executes a task that may throw checked exceptions. ### Parameters - **task** (Task) - Required - A task that can throw any exception - **lockConfig** (LockConfiguration) - Required - The lock configuration ### Example ```java lockingTaskExecutor.executeWithLock( () -> { performDatabaseOperation(); }, lockConfig ); ``` ``` -------------------------------- ### Configure Neo4j Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Configure the Neo4j lock provider by providing a Neo4j driver instance. Ensure the neo4j-java-driver version matches your project's dependency. ```java import net.javacrumbs.shedlock.core.LockConfiguration; ... @Bean Neo4jLockProvider lockProvider(org.neo4j.driver.Driver driver) { return new Neo4jLockProvider(driver); } ``` -------------------------------- ### Configure In-Memory Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Configure the in-memory lock provider. This is suitable for testing environments. ```java import net.javacrumbs.shedlock.provider.inmemory.InMemoryLockProvider; ... @Bean public LockProvider lockProvider() { return new InMemoryLockProvider(); } ``` -------------------------------- ### Initialize DefaultLockingTaskExecutor with LockProvider Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/DefaultLockingTaskExecutor.md Constructor signature for initializing the executor with a lock provider. ```java public DefaultLockingTaskExecutor(LockProvider lockProvider) ``` -------------------------------- ### Import Etcd Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Add this dependency to your project to use the Etcd lock provider with jetcd. ```xml net.javacrumbs.shedlock shedlock-provider-etcd-jetcd 7.7.0 ``` -------------------------------- ### Create Composed Annotations Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/SchedulerLock.md Shows how to wrap @SchedulerLock in a custom meta-annotation for reusable task configurations. ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Scheduled(cron = "0 0 * * * *") @SchedulerLock(lockAtMostFor = "12h", lockAtLeastFor = "11h") public @interface DailyLockedTask { String name(); } // Usage: @Component public class MyTasks { @DailyLockedTask(name = "reportGeneration") public void generateReport() { } } ``` -------------------------------- ### Import In-Memory Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Add this dependency for testing purposes to use the in-memory lock provider. It should have a test scope. ```xml net.javacrumbs.shedlock shedlock-provider-inmemory 7.7.0 test ``` -------------------------------- ### Troubleshooting Lock Application Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/EnableSchedulerLock.md Use these patterns to ensure locks are applied correctly, including verifying with LockAssert and handling Kotlin's final method behavior. ```java // Add @SchedulerLock annotation @SchedulerLock(name = "myTask") // Use LockAssert to verify: LockAssert.assertLocked(); // For Kotlin, use `open` keyword or compiler plugin open fun myTask() { } ``` -------------------------------- ### Checking ExtensibleLockProvider Support Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockExtender.md Verify if the current lock provider supports extension before attempting to use LockExtender. ```java if (lockProvider instanceof ExtensibleLockProvider) { // Safe to use lock extension } else { // Use high lockAtMostFor instead log.warn("Lock extension not supported, using long timeout"); } ``` -------------------------------- ### Configure JdbcTemplateLockProvider Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/types.md Configuration class for JdbcTemplateLockProvider, providing access to database settings and transaction management. Use the builder pattern to instantiate. ```java public static final class Configuration extends SqlConfiguration { public JdbcTemplate getJdbcTemplate() public @Nullable PlatformTransactionManager getTransactionManager() public @Nullable Integer getIsolationLevel() public DatabaseProduct getDatabaseProduct() public static Configuration.Builder builder() } ``` -------------------------------- ### Initialize LockConfiguration in Java Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockConfiguration.md Defines a lock with specific safety and minimum hold durations. Ensure the lock name is unique to the task being protected. ```java // Create a lock configuration that will: // - be held for at most 10 minutes (safety timeout) // - be held for at least 5 minutes (prevent rapid re-execution) // - be identified by "dailyReportGeneration" LockConfiguration lockConfig = new LockConfiguration( ClockProvider.now(), "dailyReportGeneration", Duration.ofMinutes(10), Duration.ofMinutes(5) ); String lockName = lockConfig.getName(); // "dailyReportGeneration" Instant unlockAt = lockConfig.getUnlockTime(); // Current time or 5 min from now Instant maxUntil = lockConfig.getLockAtMostUntil(); // 10 minutes from now ``` -------------------------------- ### Configure Short, Frequent Tasks Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/SchedulerLock.md Pattern for tasks running every 30 seconds, ensuring the lock duration is shorter than the interval. ```java @Scheduled(fixedDelay = 30000) // Every 30 seconds @SchedulerLock( name = "frequentHealthCheck", lockAtMostFor = "25s", // Slightly less than interval lockAtLeastFor = "0s" // Release immediately ) public void frequentHealthCheck() { } ``` -------------------------------- ### Configure ZookeeperCuratorLockProvider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Configure the ZookeeperCuratorLockProvider by injecting a CuratorFramework client. Lock nodes are created under '/shedlock' by default. ```java import net.javacrumbs.shedlock.provider.zookeeper.curator.ZookeeperCuratorLockProvider; ... @Bean public LockProvider lockProvider(org.apache.curator.framework.CuratorFramework client) { return new ZookeeperCuratorLockProvider(client); } ``` -------------------------------- ### Demonstrate re-entrant locking behavior Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/DefaultLockingTaskExecutor.md Shows how nested executeWithLock calls on the same thread for the same lock name reuse the existing lock. ```java // If this is called recursively on the same thread for the same lock: executor.executeWithLock(() -> { System.out.println("Outer task"); executor.executeWithLock(() -> { System.out.println("Inner task"); }, sameLockConfig); }, sameLockConfig); // The second call: // - Skips onLockAttempt and onLockAcquired // - Still calls onTaskStarted and onTaskFinished // - Does NOT acquire a new lock // - Uses the existing lock from the outer call ``` -------------------------------- ### Configure Elasticsearch Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Include the Elasticsearch 9 dependency and configure the ElasticsearchLockProvider with an ElasticsearchClient. ```xml net.javacrumbs.shedlock shedlock-provider-elasticsearch9 7.7.0 ``` ```java import net.javacrumbs.shedlock.provider.elasticsearch9.ElasticsearchLockProvider; ... @Bean public ElasticsearchLockProvider lockProvider(ElasticsearchClient client) { return new ElasticsearchLockProvider(client); } ``` -------------------------------- ### Import Neo4j Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Add this dependency to your project to use the Neo4j lock provider. ```xml net.javacrumbs.shedlock shedlock-provider-neo4j 7.7.0 ``` -------------------------------- ### Configure Apache Ignite Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Configure the Apache Ignite lock provider by providing an Ignite instance. ```java import net.javacrumbs.shedlock.provider.ignite.IgniteLockProvider; ... @Bean public LockProvider lockProvider(Ignite ignite) { return new IgniteLockProvider(ignite); } ``` -------------------------------- ### Configure Memcached Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Configure the Memcached lock provider by providing a MemcachedClient instance and environment configuration. Keys have length limits and expiration times are in seconds. ```java import net.javacrumbs.shedlock.provider.memcached.spy.MemcachedLockProvider; ... @Bean public LockProvider lockProvider(net.spy.memcached.MemcachedClient client) { return new MemcachedLockProvider(client, ENV); } ``` -------------------------------- ### Configure Testing Providers and Assertions Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/OVERVIEW.md Provides methods to mock locking behavior in tests using an in-memory provider or by bypassing lock assertions. ```java // Option 1: In-memory provider @Bean @Primary public LockProvider testLockProvider() { return new InMemoryLockProvider(); } // Option 2: Skip assertion in tests @Test public void test() { LockAssert.TestHelper.makeAllAssertsPass(true); try { // Test code } finally { LockAssert.TestHelper.makeAllAssertsPass(false); } } ``` -------------------------------- ### LockConfiguration Constructor Signature Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockConfiguration.md Constructor signature for initializing a new lock configuration instance. ```java public LockConfiguration( Instant createdAt, String name, Duration lockAtMostFor, Duration lockAtLeastFor ) ``` -------------------------------- ### Execute TaskWithResult Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockingTaskExecutor.md Method signature and usage for tasks that return a value and may throw exceptions. ```java default TaskResult executeWithLock( TaskWithResult task, LockConfiguration lockConfig ) throws Throwable ``` ```java @FunctionalInterface interface TaskWithResult { T call() throws Throwable; } ``` ```java LockingTaskExecutor.TaskResult result = lockingTaskExecutor.executeWithLock( () -> { // Task that returns a value return fetchDataFromDatabase(); }, lockConfig ); if (result.wasExecuted()) { String data = result.getResult(); System.out.println("Task executed, result: " + data); } else { System.out.println("Task was not executed (lock held by another node)"); } ``` -------------------------------- ### Extend an Existing Lock in Java Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/SimpleLock.md Shows how to extend the duration of an existing lock, handling cases where the provider does not support extension. ```java try { Optional extendedOpt = lock.extend( Duration.ofMinutes(20), // New lockAtMostFor Duration.ZERO // New lockAtLeastFor ); if (extendedOpt.isPresent()) { SimpleLock extendedLock = extendedOpt.get(); // Continue working with extendedLock performMoreWork(); extendedLock.unlock(); } else { // Lock expired before extension throw new RuntimeException("Could not extend lock"); } } catch (UnsupportedOperationException e) { // This provider doesn't support extension log.warn("Lock extension not supported"); } ``` -------------------------------- ### Implement global scheduling configuration Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/configuration.md Enable ShedLock and define the LockProvider bean within a Spring configuration class. ```java @Configuration @EnableScheduling @EnableSchedulerLock( interceptMode = EnableSchedulerLock.InterceptMode.PROXY_METHOD, defaultLockAtMostFor = "10m", defaultLockAtLeastFor = "0s", mode = AdviceMode.PROXY, proxyTargetClass = false, order = Ordered.LOWEST_PRECEDENCE ) public class SchedulingConfiguration { // Configure the lock provider @Bean public LockProvider lockProvider( JdbcTemplate jdbcTemplate, @Nullable PlatformTransactionManager transactionManager) { return new JdbcTemplateLockProvider( JdbcTemplateLockProvider.Configuration.builder() .withJdbcTemplate(jdbcTemplate) .withTransactionManager(transactionManager) .withTableName("shedlock") .forceUtcTimeZone() .withIsolationLevel(Connection.TRANSACTION_READ_COMMITTED) .build() ); } // Configure transaction template (optional) @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } } ``` -------------------------------- ### Configure proxyTargetClass Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/EnableSchedulerLock.md Controls whether to use Java interface proxies or CGLIB subclass proxies. Set to true if the class does not implement interfaces. ```java @EnableSchedulerLock(proxyTargetClass = false, ...) // Interface proxies @EnableSchedulerLock(proxyTargetClass = true, ...) // CGLIB proxies ``` -------------------------------- ### Configure a basic scheduled task Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/configuration.md Uses default lock settings defined in @EnableSchedulerLock. ```java @Component public class Tasks { @Scheduled(cron = "0 0 * * * *") // Every hour @SchedulerLock(name = "hourlyTask") public void hourlyTask() { // Uses defaults from @EnableSchedulerLock LockAssert.assertLocked(); performWork(); } } ``` -------------------------------- ### Configure AOP Advice Order Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/EnableSchedulerLock.md Sets the execution priority of the ShedLock advice relative to other AOP advices. Lower values indicate higher priority. ```java @EnableSchedulerLock( defaultLockAtMostFor = "10m", order = 10 // Higher priority than default ) public class MyConfig { } ``` -------------------------------- ### Configure Micronaut JdbcLockProvider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Configure the MicronautJdbcLockProvider by injecting TransactionOperations. This provider offers basic functionality. ```java import net.javacrumbs.shedlock.provider.jdbc.micronaut.MicronautJdbcLockProvider; ... @Singleton public LockProvider lockProvider(TransactionOperations transactionManager) { return new MicronautJdbcLockProvider(transactionManager); } ``` -------------------------------- ### Configure environment-based properties Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/configuration.md Uses Spring property placeholders for lock durations to allow externalized configuration. ```yaml # application.yml app: locks: default-at-most-for: PT10M default-at-least-for: PT0S tasks: daily-report: at-most-for: PT12H at-least-for: PT0S ``` ```java @Configuration @EnableScheduling @EnableSchedulerLock( defaultLockAtMostFor = "${app.locks.default-at-most-for:PT10M}", defaultLockAtLeastFor = "${app.locks.default-at-least-for:PT0S}" ) public class SchedulingConfig { } @Component public class Tasks { @Scheduled(cron = "0 2 * * * *") @SchedulerLock( name = "dailyReport", lockAtMostFor = "${app.locks.tasks.daily-report.at-most-for:PT12H}" ) public void dailyReport() { } ``` -------------------------------- ### void executeWithLock(Runnable task) Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockManager.md Executes a task with lock protection. The lock configuration is extracted from the task, and if found, the lock is acquired before execution. ```APIDOC ## void executeWithLock(Runnable task) ### Description Executes a task with lock protection. The lock configuration is extracted from the task using a LockConfigurationExtractor. If configuration is found, the lock is acquired and the task is executed; otherwise, the task is executed without locking. ### Parameters - **task** (Runnable) - Required - The task to execute; may carry lock configuration metadata. ### Throws - Any exception thrown by the task. ``` -------------------------------- ### Configure ShedLock with S3 Lock Provider Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md Add the S3v2 provider dependency and configure the LockProvider bean using an S3Client and bucket name. ```xml net.javacrumbs.shedlock shedlock-provider-s3v2 7.7.0 ``` ```java import net.javacrumbs.shedlock.provider.s3v2.S3LockProvider; ... @Bean public LockProvider lockProvider(S3Client s3Client) { return new S3LockProvider(s3Client, "BUCKET_NAME"); } ``` -------------------------------- ### Initialize DefaultLockingTaskExecutor with Listener Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/DefaultLockingTaskExecutor.md Constructor signature for initializing the executor with a lock provider and a lifecycle listener. ```java public DefaultLockingTaskExecutor( LockProvider lockProvider, LockingTaskExecutorListener lockingTaskExecutorListener ) ``` -------------------------------- ### Enable locking in Spring Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/OVERVIEW.md Configures global lock duration settings for Spring applications. ```java @EnableSchedulerLock(defaultLockAtMostFor = "10m") ``` -------------------------------- ### Implement Safe Lock Extension with Retry Logic Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/api-reference/LockExtender.md Wraps the extension logic in a helper method to handle failures gracefully without interrupting the main task flow. ```java @Scheduled(cron = "0 0 * * * *") @SchedulerLock( name = "reliableTask", lockAtMostFor = "20m", lockAtLeastFor = "0s" ) public void reliableTask() { try { performInitialWork(); // Try to extend, with fallback if it fails boolean extended = extendWithFallback(Duration.ofMinutes(20)); if (!extended) { log.info("Could not extend lock, wrapping up"); return; } performMoreWork(); } catch (Exception e) { log.error("Task failed", e); throw e; } } private boolean extendWithFallback(Duration extension) { try { LockExtender.extendActiveLock(extension, Duration.ZERO); return true; } catch (LockCanNotBeExtendedException e) { log.warn("Lock extension failed: {}", e.getMessage()); return false; } } ``` -------------------------------- ### Configure JdbcTemplate Lock Provider with Schema Source: https://github.com/lukas-krecan/shedlock/blob/master/README.md How to specify a schema for the ShedLock table when using JdbcTemplateLockProvider. ```java new JdbcTemplateLockProvider(datasource, "my_schema.shedlock") ``` -------------------------------- ### Handling LockCanNotBeExtendedException Source: https://github.com/lukas-krecan/shedlock/blob/master/_autodocs/errors.md Shows how to handle lock expiration during task execution and how to configure lock durations to prevent extension failures. ```java @Scheduled(cron = "0 0 * * * *") @SchedulerLock( name = "longTask", lockAtMostFor = "30m", lockAtLeastFor = "0s" ) public void longTask() { performPhase1(); // Takes 25 minutes // Try to extend for another 30 minutes try { LockExtender.extendActiveLock( Duration.ofMinutes(30), Duration.ZERO ); } catch (LockCanNotBeExtendedException e) { // Lock expired while executing phase1 // Another node may have started phase2 log.error("Cannot extend lock, phase2 may be starting elsewhere"); return; } performPhase2(); // Takes another 25 minutes } ``` ```java // Set lockAtMostFor high enough for the entire task @SchedulerLock( name = "task", lockAtMostFor = "2h", // Higher than actual duration lockAtLeastFor = "0s" ) public void task() { performWork(); // Usually < 2h } // If extension is needed, check for expiration first @SchedulerLock(name = "task", lockAtMostFor = "30m") public void extendableTask() { if (shouldContinue()) { try { LockExtender.extendActiveLock(Duration.ofMinutes(30), Duration.ZERO); } catch (LockCanNotBeExtendedException e) { log.warn("Cannot continue, lock expired"); return; } } } ```