SkillAgentSearch skills...

ShedLock

Distributed lock for your scheduled tasks

Install / Use

/learn @lukas-krecan/ShedLock
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

ShedLock

Apache License 2 Build Status

ShedLock makes sure that your scheduled tasks are executed at most once at the same time. If a task is being executed on one node, it acquires a lock which prevents execution of the same task from another node (or thread). Please note, that if one task is already being executed on one node, execution on other nodes does not wait, it is simply skipped.

ShedLock uses an external store like Mongo, JDBC database, Redis, Hazelcast, ZooKeeper or others for coordination.

Feedback and pull-requests welcome!

ShedLock is not a distributed scheduler

Please note that ShedLock is not and will never be full-fledged scheduler, it's just a lock. If you need a distributed scheduler, please use another project (db-scheduler, JobRunr). ShedLock is designed to be used in situations where you have scheduled tasks that are not ready to be executed in parallel, but can be safely executed repeatedly. Moreover, the locks are time-based and ShedLock assumes that clocks on the nodes are synchronized.

Components

Shedlock consists of three parts

  • Core - The locking mechanism
  • Integration - integration with your application, using Spring AOP, Micronaut AOP or manual code
  • Lock provider - provides the lock using an external process like SQL database, Mongo, Redis and others

Usage

To use ShedLock, you do the following

  1. Enable and configure Scheduled locking
  2. Annotate your scheduled tasks
  3. Configure a Lock Provider

Enable and configure Scheduled locking (Spring)

First of all, we have to import the project

<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
    <version>7.7.0</version>
</dependency>

Now we need to integrate the library with Spring. In order to enable schedule locking use @EnableSchedulerLock annotation

@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "10m")
class MySpringConfiguration {
    ...
}

Annotate your scheduled tasks

import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;

...

@Scheduled(...)
@SchedulerLock(name = "scheduledTaskName")
public void scheduledTask() {
    // To assert that the lock is held (prevents misconfiguration errors)
    LockAssert.assertLocked();
    // do something
}

Behavior

  1. Only methods annotated by @SchedulerLock are locked, the library ignores all other scheduled tasks. By default, the lock will be applied even if the method is called directly, not only thorough the scheduler.
  2. Only one task with the same name can be executed at the same time.
  3. If the lock is being held by a task, other tasks protected by the same lock are not blocked, but are simply skipped.
  4. The lock is released as soon as the task is finished (unless lockAtLeastFor is specified, see below)
  5. If the JVM crashes before the task finishes, lockAtMostFor attribute comes to play. The lock is always released after lockAtMostFor. You have to set lockAtMostFor to a value which is much longer than normal execution time. If the task takes longer than lockAtMostFor the resulting behavior may be unpredictable (more than one process will effectively hold the lock).
  6. If you don't specify lockAtMostFor in @SchedulerLock, the default value from @EnableSchedulerLock will be used.
  7. You can set lockAtLeastFor attribute which specifies minimum amount of time for which the lock should be kept. Its main purpose is to prevent execution from multiple nodes in case of really short tasks and clock difference between the nodes.
  8. All the annotations support Spring Expression Language (SpEL).

Example

Let's say you have a task which you execute every 15 minutes and which usually takes few minutes to run. Moreover, you want to execute it at most once per 15 minutes. In that case, you can configure it like this:

import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;


@Scheduled(cron = "0 */15 * * * *")
@SchedulerLock(name = "scheduledTaskName", lockAtMostFor = "14m", lockAtLeastFor = "14m")
public void scheduledTask() {
    // do something
}

By setting lockAtMostFor we make sure that the lock is released even if the node dies. By setting lockAtLeastFor we make sure it's not executed more than once in fifteen minutes. Please note that lockAtMostFor is just a safety net in case that the node executing the task dies, so set it to a time that is significantly larger than maximum estimated execution time. If the task takes longer than lockAtMostFor, it may be executed again and the results will be unpredictable (more processes will hold the lock).

Configure LockProvider

There are several implementations of LockProvider.

JdbcTemplate

First, create lock table (please note that name has to be primary key)

# MySQL, MariaDB
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP(3) NOT NULL,
    locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));

# Postgres
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP NOT NULL,
    locked_at TIMESTAMP NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));

# Oracle
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP(3) NOT NULL,
    locked_at TIMESTAMP(3) NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));

# MS SQL
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until datetime2 NOT NULL,
    locked_at datetime2 NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));

# DB2
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL PRIMARY KEY, lock_until TIMESTAMP NOT NULL,
    locked_at TIMESTAMP NOT NULL, locked_by VARCHAR(255) NOT NULL);

Or use this liquibase change-set.

Add dependency

<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-jdbc-template</artifactId>
    <version>7.7.0</version>
</dependency>

Configure:

import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;

...
@Bean
public LockProvider lockProvider(DataSource dataSource) {
    return new JdbcTemplateLockProvider(
        JdbcTemplateLockProvider.Configuration.builder()
        .withJdbcTemplate(new JdbcTemplate(dataSource))
        .usingDbTime() // Works on Postgres, MySQL, MariaDb, MS SQL, Oracle, DB2, HSQL and H2
        .build()
    );
}

By specifying usingDbTime() the lock provider will use UTC time based on the DB server clock. If you do not specify this option, clock from the app server will be used (the clocks on app servers may not be synchronized thus leading to various locking issues).

It's strongly recommended to use usingDbTime() option as it uses DB engine specific SQL that prevents INSERT conflicts. See more details here.

For more fine-grained configuration use other options of the Configuration object

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())

If you need to specify a schema, you can set it in the table name using the usual dot notation new JdbcTemplateLockProvider(datasource, "my_schema.shedlock")

To use a database with case-sensitive table and column names, the .withDbUpperCase(true) flag can be used. Default is false (lowercase).

Warning

Do not manually delete lock row from the DB table. ShedLock has an in-memory cache of existing lock rows so the row will NOT be automatically recreated until application restart. If you need to, you can edit the row/document, risk

Related Skills

View on GitHub
GitHub Stars4.1k
CategoryDevelopment
Updated3h ago
Forks563

Languages

Java

Security Score

95/100

Audited on Mar 28, 2026

No findings