Timers and Cron Workflows
Lesson 14: Time-Based Workflow Patterns
Master time-based workflow patterns including delays, timeouts, conditional waiting, and recurring scheduled tasks. Learn how to build reliable timer workflows that handle scheduling, timezone considerations, and long-running recurring processes.
Objective
By the end of this lesson, you will understand:
- ✅ Timer fundamentals in Temporal with
Workflow.sleep()
- ✅ Timer patterns - delay, timeout, conditional waiting
- ✅ Cron workflow patterns for recurring scheduled tasks
- ✅ Time-based scheduling with timezone handling
- ✅
continueAsNew
for long-running recurring processes - ✅ Production considerations for timer workflows
1. Timer Fundamentals in Temporal
Workflow.sleep() - The Foundation
// Basic sleep - duration-based delay
Workflow.sleep(Duration.ofSeconds(30))
Workflow.sleep(Duration.ofMinutes(5))
Workflow.sleep(Duration.ofHours(1))
// Sleep until specific time
val targetTime = Instant.parse("2024-12-25T00:00:00Z")
val now = Workflow.currentTimeMillis()
val delay = Duration.ofMillis(targetTime.toEpochMilli() - now)
if (delay.isPositive) {
Workflow.sleep(delay)
}
Temporal timers are durable, accurate, efficient, and scalable
Key Properties of Temporal Timers
Timer Characteristics:
- ✅ Durable: Timers survive worker crashes and restarts
- ✅ Accurate: Precisely scheduled, not affected by clock drift
- ✅ Efficient: No polling - event-driven execution
- ✅ Scalable: Millions of timers can be scheduled simultaneously
Production Benefits:
- No resource consumption while waiting
- Automatic recovery after infrastructure failures
- Precise scheduling regardless of system load
2. Timer Patterns
Simple Delay Pattern
class DelayWorkflowImpl : DelayWorkflow {
override fun processWithDelay(delayMinutes: Int): String {
val logger = Workflow.getLogger(this::class.java)
logger.info("Starting process, will delay for $delayMinutes minutes")
// Perform initial processing
val initialResult = performInitialWork()
// Wait for specified duration
Workflow.sleep(Duration.ofMinutes(delayMinutes.toLong()))
// Continue with delayed processing
val finalResult = performDelayedWork(initialResult)
return finalResult
}
}
Timeout Pattern
class TimeoutWorkflowImpl : TimeoutWorkflow {
override fun processWithTimeout(timeoutSeconds: Long): ProcessResult {
val operation = Async.procedure {
// Long-running operation
performLongOperation()
}
return try {
// Wait for operation with timeout
operation.get(Duration.ofSeconds(timeoutSeconds))
ProcessResult.success("Operation completed within timeout")
} catch (e: TimeoutException) {
// Handle timeout
ProcessResult.timeout("Operation timed out after ${timeoutSeconds}s")
} catch (e: Exception) {
// Handle other failures
ProcessResult.error("Operation failed: ${e.message}")
}
}
}
Conditional Waiting Pattern
class ConditionalWaitWorkflowImpl : ConditionalWaitWorkflow {
override fun waitForCondition(maxWaitMinutes: Int): ConditionResult {
val logger = Workflow.getLogger(this::class.java)
var checkCount = 0
val conditionMet = Workflow.await(Duration.ofMinutes(maxWaitMinutes.toLong())) {
checkCount++
logger.info("Checking condition, attempt: $checkCount")
val conditionStatus = checkConditionActivity.checkCondition()
if (conditionStatus.isMet) {
logger.info("Condition met after $checkCount checks")
return@await true
}
// Wait between checks
Workflow.sleep(Duration.ofSeconds(30))
false
}
return ConditionResult(
conditionMet = conditionMet,
checkCount = checkCount,
timeoutReached = !conditionMet
)
}
}
Periodic Processing Pattern
class PeriodicWorkflowImpl : PeriodicWorkflow {
override fun runPeriodicProcess(intervalMinutes: Int, maxIterations: Int): PeriodicResult {
val results = mutableListOf<String>()
repeat(maxIterations) { iteration ->
val logger = Workflow.getLogger(this::class.java)
logger.info("Starting iteration ${iteration + 1} of $maxIterations")
// Perform periodic work
val result = performPeriodicWork(iteration)
results.add(result)
// Wait before next iteration (except last)
if (iteration < maxIterations - 1) {
Workflow.sleep(Duration.ofMinutes(intervalMinutes.toLong()))
}
}
return PeriodicResult(
completedIterations = maxIterations,
results = results
)
}
}
3. Cron Workflow Patterns
Basic Cron Implementation
class CronWorkflowImpl : CronWorkflow {
override fun runCronJob(config: CronConfig): CronResult {
val executionTime = Instant.now()
val logger = Workflow.getLogger(this::class.java)
try {
// Execute the job
val jobResult = executeScheduledJob(config)
// Calculate next execution time
val nextRun = calculateNextRunTime(config.cronExpression, executionTime, config.timezone)
if (nextRun != null) {
// Sleep until next execution
val sleepDuration = Duration.between(executionTime, nextRun)
if (sleepDuration.isPositive) {
Workflow.sleep(sleepDuration)
}
// Continue with next execution
Workflow.continueAsNew(config)
}
return CronResult.success(config.jobId, executionTime, jobResult, nextRun)
// Continued on next slide...
Cron Error Handling
} catch (e: Exception) {
logger.error("Cron job failed", e)
// Schedule next run even on failure
val nextRun = calculateNextRunTime(config.cronExpression, executionTime, config.timezone)
if (nextRun != null) {
val sleepDuration = Duration.between(executionTime, nextRun)
if (sleepDuration.isPositive) {
Workflow.sleep(sleepDuration)
}
Workflow.continueAsNew(config)
}
return CronResult.failure(config.jobId, executionTime, e.message, nextRun)
}
}
}
Cron workflows use continueAsNew
to prevent history growth in long-running schedules
Timer Pattern Use Cases
When to Use Each Pattern:
Pattern | Use Case | Example |
---|---|---|
Simple Delay | Fixed waiting period | Wait 30 minutes before retry |
Timeout | Maximum operation time | API calls with 2-minute limit |
Conditional Wait | Event-based waiting | Wait for file to appear |
Periodic | Regular intervals | Health checks every 5 minutes |
Cron | Complex scheduling | Daily reports at 9 AM |
💡 Key Takeaways
What You've Learned:
- ✅ Temporal timers are durable and survive infrastructure failures
- ✅ Timer patterns handle different time-based scenarios
- ✅ Cron workflows enable complex recurring schedules
- ✅
continueAsNew
prevents history growth in long-running processes - ✅ Production-ready timer implementations