or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-configuration.mdconfiguration.mdindex.mdmethod-locking.mdtask-scheduler.md

method-locking.mddocs/

0

# Method-Level Locking

1

2

Annotations and infrastructure for applying distributed locks directly to scheduled methods.

3

4

## @SchedulerLock Annotation

5

6

The core annotation for marking methods that should be protected by distributed locks.

7

8

```java { .api }

9

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})

10

@Retention(RetentionPolicy.RUNTIME)

11

public @interface SchedulerLock {

12

String name() default ""; // Lock name (required in practice)

13

String lockAtMostFor() default ""; // Maximum lock duration

14

String lockAtLeastFor() default ""; // Minimum lock duration

15

}

16

```

17

18

### Parameters

19

20

- **name**: The lock name used to identify the lock across nodes. Can include SpEL expressions when parameters are available.

21

- **lockAtMostFor**: Maximum time the lock should be held (fallback for dead nodes). Overrides `defaultLockAtMostFor` from `@EnableSchedulerLock`.

22

- **lockAtLeastFor**: Minimum time the lock should be held. Useful for preventing rapid re-execution.

23

24

### Usage Examples

25

26

#### Basic Usage

27

```java

28

@Component

29

public class ScheduledTasks {

30

31

@Scheduled(cron = "0 */5 * * * *")

32

@SchedulerLock(name = "processData", lockAtMostFor = "4m", lockAtLeastFor = "1m")

33

public void processData() {

34

// This method will only run on one node at a time

35

doHeavyProcessing();

36

}

37

}

38

```

39

40

#### Using SpEL Expressions

41

```java

42

@SchedulerLock(name = "processUser-#{#userId}", lockAtMostFor = "10m")

43

public void processUser(Long userId) {

44

// Lock name will be "processUser-123" for userId=123

45

processUserData(userId);

46

}

47

```

48

49

#### Using Default Durations

50

```java

51

@SchedulerLock(name = "simpleTask")

52

public void simpleTask() {

53

// Uses defaultLockAtMostFor and defaultLockAtLeastFor from @EnableSchedulerLock

54

doSimpleWork();

55

}

56

```

57

58

## @LockProviderToUse Annotation

59

60

Annotation for disambiguating between multiple lock providers when more than one `LockProvider` bean exists.

61

62

```java { .api }

63

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.PACKAGE})

64

@Retention(RetentionPolicy.RUNTIME)

65

public @interface LockProviderToUse {

66

String value(); // Bean name of the LockProvider to use

67

}

68

```

69

70

### Usage Examples

71

72

#### Method-Level Provider Selection

73

```java

74

@Component

75

public class ScheduledTasks {

76

77

@Scheduled(cron = "0 */5 * * * *")

78

@SchedulerLock(name = "criticalTask", lockAtMostFor = "10m")

79

@LockProviderToUse("redisPrimaryLockProvider")

80

public void criticalTask() {

81

// Uses the Redis lock provider for this critical task

82

performCriticalOperation();

83

}

84

85

@Scheduled(cron = "0 0 */1 * * *")

86

@SchedulerLock(name = "housekeeping", lockAtMostFor = "30m")

87

@LockProviderToUse("databaseLockProvider")

88

public void housekeeping() {

89

// Uses the database lock provider for housekeeping

90

performHousekeeping();

91

}

92

}

93

```

94

95

#### Class-Level Provider Selection

96

```java

97

@Component

98

@LockProviderToUse("databaseLockProvider")

99

public class DatabaseTasks {

100

101

@Scheduled(cron = "0 0 2 * * *")

102

@SchedulerLock(name = "dbCleanup", lockAtMostFor = "2h")

103

public void cleanupDatabase() {

104

// Inherits database lock provider from class level

105

cleanupOldRecords();

106

}

107

}

108

```

109

110

#### Package-Level Provider Selection

111

```java

112

// In package-info.java

113

@LockProviderToUse("defaultLockProvider")

114

package com.example.scheduled.tasks;

115

116

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

117

```

118

119

## Method Proxy Infrastructure

120

121

### MethodProxyScheduledLockAdvisor

122

123

AOP advisor that intercepts calls to methods annotated with `@SchedulerLock`.

124

125

```java { .api }

126

public class MethodProxyScheduledLockAdvisor extends AbstractPointcutAdvisor {

127

128

public MethodProxyScheduledLockAdvisor(

129

ExtendedLockConfigurationExtractor lockConfigurationExtractor,

130

LockProviderSupplier lockProviderSupplier

131

);

132

133

public Pointcut getPointcut(); // Matches @SchedulerLock methods

134

public Advice getAdvice(); // Returns LockingInterceptor

135

}

136

```

137

138

### Method Interception Logic

139

140

The advisor uses a `MethodInterceptor` that:

141

142

1. **Validates method compatibility**: Throws `LockingNotSupportedException` for methods returning primitives (except void)

143

2. **Extracts lock configuration**: Uses `ExtendedLockConfigurationExtractor` to get lock settings

144

3. **Obtains lock provider**: Uses `LockProviderSupplier` to get the appropriate lock provider

145

4. **Executes with lock**: Uses `DefaultLockingTaskExecutor` to execute the method with locking

146

5. **Handles return types**: Special handling for `Optional` return types

147

148

```java { .api }

149

// Internal interceptor logic (simplified)

150

public class LockingInterceptor implements MethodInterceptor {

151

public Object invoke(MethodInvocation invocation) throws Throwable;

152

}

153

```

154

155

### Supported Return Types

156

157

- **void**: Standard case, no special handling

158

- **Object types**: Returns the actual result or null if lock not acquired

159

- **Optional**: Returns `Optional.empty()` if lock not acquired, otherwise the method result

160

- **Primitives**: **Not supported** - throws `LockingNotSupportedException`

161

162

### Usage with Optional Return Types

163

164

```java

165

@SchedulerLock(name = "optionalTask", lockAtMostFor = "5m")

166

public Optional<String> optionalTask() {

167

// If lock is acquired: returns Optional.of(result)

168

// If lock is not acquired: returns Optional.empty()

169

return Optional.of("Task completed");

170

}

171

```

172

173

## Lock Provider Selection

174

175

### Single Lock Provider

176

When only one `LockProvider` bean exists, it's used automatically:

177

178

```java

179

@Configuration

180

public class LockConfig {

181

@Bean

182

public LockProvider lockProvider() {

183

return new JdbcTemplateLockProvider(dataSource);

184

}

185

}

186

```

187

188

### Multiple Lock Providers

189

When multiple `LockProvider` beans exist, use `@LockProviderToUse` to specify which one:

190

191

```java

192

@Configuration

193

public class LockConfig {

194

195

@Bean("primaryLockProvider")

196

public LockProvider primaryLockProvider() {

197

return new RedisLockProvider(redisTemplate);

198

}

199

200

@Bean("fallbackLockProvider")

201

public LockProvider fallbackLockProvider() {

202

return new JdbcTemplateLockProvider(dataSource);

203

}

204

}

205

206

// Then use @LockProviderToUse to select

207

@SchedulerLock(name = "task", lockAtMostFor = "10m")

208

@LockProviderToUse("primaryLockProvider")

209

public void scheduledTask() {

210

// Uses Redis lock provider

211

}

212

```

213

214

### Lock Provider Supplier

215

216

Internal interface that supplies the appropriate lock provider for method execution:

217

218

```java { .api }

219

@FunctionalInterface

220

interface LockProviderSupplier {

221

LockProvider supply(Object target, Method method, Object[] parameterValues);

222

223

static LockProviderSupplier create(ListableBeanFactory beanFactory);

224

}

225

```

226

227

Implementation automatically handles:

228

- Single provider scenarios (direct bean lookup)

229

- Multiple provider scenarios (uses `BeanNameSelectingLockProviderSupplier`)

230

231

## Error Handling

232

233

### LockingNotSupportedException

234

235

Thrown when trying to lock methods that return primitive types (except void):

236

237

```java { .api }

238

class LockingNotSupportedException extends LockException {

239

LockingNotSupportedException(String message);

240

}

241

```

242

243

Example that throws this exception:

244

```java

245

@SchedulerLock(name = "invalidMethod", lockAtMostFor = "5m")

246

public int invalidMethod() { // ❌ Primitive return type

247

return 42;

248

}

249

```

250

251

### NoUniqueBeanDefinitionException

252

253

Thrown when multiple `LockProvider` beans exist but no `@LockProviderToUse` annotation is present:

254

255

```java

256

// ❌ This will fail at runtime if multiple providers exist

257

@SchedulerLock(name = "ambiguousTask", lockAtMostFor = "5m")

258

public void ambiguousTask() {

259

doWork();

260

}

261

```

262

263

The error message includes all available provider bean names to help with debugging.