Files
spring-async-demo/README.md
Ankur Mhatre eaa8171966 Add the scheduling module
Three replicas of one application against one PostgreSQL database, proving
duplicate @Scheduled execution and then removing it with ShedLock: 24
executions where 8 were due, then 7 for 7 ticks.

Also measured: @SchedulerLock without @EnableSchedulerLock does nothing and
warns about nothing; spring.task.scheduling.pool.size=1 does not starve a
fixedRate job but delays it and fires 35 of 40 executions in a burst; and a
node whose clock is 40 seconds fast takes a live lock unless the provider
uses usingDbTime().
2026-09-01 23:32:16 +05:30

2.3 KiB

spring-async-demo

Companion code for the asynchronous execution and scheduling series on ankurm.com. Each directory is a self-contained Maven project for one article, with its own pom.xml, its own numbered documentation chapters, and its own captured output under docs/output/ — regenerated by that module's scripts/run-all.sh, never typed by hand.

Module Article What it demonstrates
async/ @Async in Spring Boot 4: Executors, Virtual Threads and the Self-Invocation Trap Which thread a method actually ran on, in every case where the answer is not the one you expect
scheduling/ @Scheduled, ShedLock and Distributed Cron: Scheduling That Survives Three Replicas Three replicas against one database running the same job three times, then one row and one conditional UPDATE fixing it

Common ground

All modules target the same verified stack: JDK 25 (Temurin 25.0.4.1+1), Spring Boot 4.1.1, Spring Framework 7.0.9. Versions were read from maven-metadata.xml on Maven Central and from Boot's own spring-boot-dependencies POM, rather than from release announcements.

Everything is asserted by a test and captured to a file. The measurement is nearly always the same one: the name of the thread that ran the work, returned by the code itself. Timing cannot tell a fast synchronous call from an asynchronous one, which is why @Async failures survive so long in production.

The two modules share a mechanism, which is why they live together: both @Async and ShedLock's default PROXY_METHOD intercept mode are Spring AOP proxies. Every proxy limitation the async module measures — self-invocation, final methods — applies unchanged to a @SchedulerLock method, and silently produces an unlocked job rather than a synchronous one.

The scheduling module also needs a database. scheduling/scripts/postgres.sh unpacks a throwaway PostgreSQL 14 into target/ with no Docker and no root, which is how its transcripts were produced; docker-compose.yml is there for anyone who would rather use Docker.

Licence

MIT — see LICENSE.