The @Timeout annotation allows you to specify a custom timeout policy for services, workflows, or individual methods using a WithTimeout class.
Package
io.infinitic.annotations.Timeout
Targets
- Classes (services and workflows)
- Methods (service methods and workflow methods)
Parameters
with
KClass<out WithTimeout>
required
The class implementing the timeout policy (must implement WithTimeout interface)
WithTimeout Interface
To define a timeout policy, implement the WithTimeout interface:
import io.infinitic.tasks.WithTimeout
class MyTimeoutPolicy : WithTimeout {
override fun getTimeoutSeconds(): Double {
// Return number of seconds before timeout
return 30.0 // 30 seconds
}
}
Usage
On Classes
Apply a timeout policy to all methods in a service or workflow:
import io.infinitic.annotations.Timeout
@Timeout(with = StandardTimeout::class)
interface PaymentService {
fun processPayment(amount: Double): PaymentResult
fun refundPayment(transactionId: String): RefundResult
}
@Timeout(with = LongRunningTimeout::class)
interface DataProcessingWorkflow {
fun processLargeDataset(datasetId: String): ProcessingResult
}
On Methods
Apply different timeout policies to specific methods:
import io.infinitic.annotations.Timeout
interface PaymentService {
@Timeout(with = QuickTimeout::class)
fun validateCard(cardInfo: CardInfo): ValidationResult
@Timeout(with = StandardTimeout::class)
fun processPayment(amount: Double): PaymentResult
@Timeout(with = LongTimeout::class)
fun processLargeTransaction(transactionData: TransactionData): PaymentResult
}
Timeout Policy Examples
Fixed Timeouts
import io.infinitic.tasks.WithTimeout
class QuickTimeout : WithTimeout {
override fun getTimeoutSeconds() = 5.0 // 5 seconds
}
class StandardTimeout : WithTimeout {
override fun getTimeoutSeconds() = 30.0 // 30 seconds
}
class LongTimeout : WithTimeout {
override fun getTimeoutSeconds() = 300.0 // 5 minutes
}
class VeryLongTimeout : WithTimeout {
override fun getTimeoutSeconds() = 3600.0 // 1 hour
}
Dynamic Timeout Based on Context
import io.infinitic.tasks.Task
import io.infinitic.tasks.WithTimeout
class DynamicTimeout : WithTimeout {
override fun getTimeoutSeconds(): Double {
// Access task metadata to determine timeout
val priority = Task.meta["priority"]?.toString() ?: "normal"
return when (priority) {
"high" -> 60.0 // 1 minute for high priority
"low" -> 300.0 // 5 minutes for low priority
else -> 120.0 // 2 minutes for normal priority
}
}
}
Retry-Aware Timeout
import io.infinitic.tasks.Task
import io.infinitic.tasks.WithTimeout
class RetryAwareTimeout : WithTimeout {
override fun getTimeoutSeconds(): Double {
// Increase timeout with each retry
val retryCount = Task.retry
val baseTimeout = 30.0
return baseTimeout * (retryCount + 1)
}
}
Environment-Based Timeout
import io.infinitic.tasks.WithTimeout
class EnvironmentTimeout : WithTimeout {
override fun getTimeoutSeconds(): Double {
val environment = System.getenv("ENVIRONMENT") ?: "production"
return when (environment) {
"development" -> 300.0 // 5 minutes in dev
"staging" -> 120.0 // 2 minutes in staging
"production" -> 60.0 // 1 minute in production
else -> 60.0
}
}
}
Method vs Class Priority
When both class and method have @Timeout annotations, the method annotation takes precedence:
@Timeout(with = StandardTimeout::class)
interface PaymentService {
// Uses StandardTimeout (30 seconds)
fun processPayment(amount: Double): PaymentResult
// Uses QuickTimeout (5 seconds) - overrides class-level
@Timeout(with = QuickTimeout::class)
fun validateCard(cardInfo: CardInfo): ValidationResult
}
Integration with Configuration
Timeout policies can also be configured in YAML (takes precedence over annotations):
services:
- name: PaymentService
class: com.example.PaymentServiceImpl
executor:
concurrency: 10
withTimeout: com.example.StandardTimeout
Timeout Behavior
When a timeout occurs:
- The task execution is interrupted
- A
TimeoutException is thrown
- The task can be retried based on the retry policy
- If no retry policy is defined, the task fails permanently
import io.infinitic.workflows.Workflow
class BookingWorkflowImpl : Workflow(), BookingWorkflow {
override fun processBooking(bookingId: String): BookingResult {
val paymentService = newService(PaymentService::class.java)
try {
val result = dispatch(paymentService::processPayment, 100.0).await()
return BookingResult.success(result)
} catch (e: Exception) {
// Handle timeout or other errors
when {
e.message?.contains("timeout") == true -> {
return BookingResult.timeout()
}
else -> throw e
}
}
}
}
Combining with Retry
import io.infinitic.annotations.Retry
import io.infinitic.annotations.Timeout
@Timeout(with = LongTimeout::class)
@Retry(with = StandardRetryPolicy::class)
interface ExternalApiService {
fun callExternalApi(request: ApiRequest): ApiResponse
}
In this example:
- Each attempt has a 5-minute timeout (LongTimeout)
- If timeout occurs, the retry policy determines if/when to retry
- Total possible duration = timeout × max retries
Best Practices
- Set realistic timeouts: Based on actual service performance, not arbitrary values
- Account for network latency: Add buffer for network delays
- Use shorter timeouts for validation: Quick operations should fail fast
- Consider downstream timeouts: Ensure timeouts align with dependent services
- Monitor timeout occurrences: Track timeouts to identify performance issues
- Test timeout behavior: Verify system handles timeouts gracefully
- Document timeout values: Make timeout expectations clear to consumers
Complete Example
import io.infinitic.annotations.Retry
import io.infinitic.annotations.Timeout
import io.infinitic.tasks.WithTimeout
import io.infinitic.tasks.WithRetry
import io.infinitic.exceptions.tasks.TaskFailedException
// Define timeout policies
class QuickTimeout : WithTimeout {
override fun getTimeoutSeconds() = 5.0
}
class StandardTimeout : WithTimeout {
override fun getTimeoutSeconds() = 30.0
}
class LongTimeout : WithTimeout {
override fun getTimeoutSeconds() = 300.0
}
// Define retry policy that handles timeouts
class TimeoutRetryPolicy : WithRetry {
override fun getSecondsBeforeRetry(
retry: Int,
exception: TaskFailedException
): Double? {
return when {
retry >= 3 -> null
exception.message?.contains("timeout") == true -> {
// Longer delay after timeout
(retry + 1) * 10.0
}
else -> (retry + 1) * 5.0
}
}
}
// Apply to service
@Timeout(with = StandardTimeout::class)
@Retry(with = TimeoutRetryPolicy::class)
interface PaymentService {
// Quick validation with short timeout
@Timeout(with = QuickTimeout::class)
fun validatePaymentInfo(info: PaymentInfo): ValidationResult
// Standard payment processing
fun processPayment(amount: Double): PaymentResult
// Batch processing with long timeout
@Timeout(with = LongTimeout::class)
fun processBatchPayments(payments: List<Payment>): BatchResult
}
// Implementation
class PaymentServiceImpl : PaymentService {
override fun validatePaymentInfo(info: PaymentInfo): ValidationResult {
// Has 5 seconds to complete (QuickTimeout)
return performQuickValidation(info)
}
override fun processPayment(amount: Double): PaymentResult {
// Has 30 seconds to complete (StandardTimeout)
return callPaymentGateway(amount)
}
override fun processBatchPayments(payments: List<Payment>): BatchResult {
// Has 5 minutes to complete (LongTimeout)
return processBatch(payments)
}
}
Monitoring Timeouts
You can track timeout information within your service:
import io.infinitic.tasks.Task
class PaymentServiceImpl : PaymentService {
override fun processPayment(amount: Double): PaymentResult {
val startTime = System.currentTimeMillis()
try {
val result = callPaymentGateway(amount)
val duration = System.currentTimeMillis() - startTime
logger.info("Payment processed in ${duration}ms")
return result
} catch (e: Exception) {
val duration = System.currentTimeMillis() - startTime
logger.error("Payment failed after ${duration}ms: ${e.message}")
throw e
}
}
}
See Also