State-Aware Scheduling
Overview
State-aware scheduling extends Brobot's scheduling capabilities to automatically validate and manage active states before executing scheduled tasks. This ensures that automation runs with the correct GUI context and can recover from unexpected state changes.
Core Concepts
The Challenge
Traditional scheduled tasks in GUI automation face several challenges:
- GUI state may change unexpectedly between task executions
- Applications may crash or navigate to unexpected screens
- Background processes may alter the interface
- User interactions may interfere with automation
The Solution
State-aware scheduling addresses these challenges by:
- Validating required states before each task execution
- Automatically rebuilding states when necessary
- Providing configurable behavior for different scenarios
- Maintaining separation between scheduling and state management concerns
Implementation
StateAwareScheduler Component
The StateAwareScheduler
wraps standard Java scheduling with state validation:
@Component
@RequiredArgsConstructor
public class StateAwareScheduler {
private final StateDetector stateDetector;
private final StateMemory stateMemory;
private final StateService stateService;
public void scheduleWithStateCheck(
ScheduledExecutorService scheduler,
Runnable task,
StateCheckConfiguration config,
long initialDelay,
long period,
TimeUnit unit) {
scheduler.scheduleAtFixedRate(() -> {
try {
performStateCheck(config);
task.run();
} catch (Exception e) {
log.error("Error in state-aware scheduled task", e);
}
}, initialDelay, period, unit);
}
private void performStateCheck(StateCheckConfiguration config) {
List<String> activeStateNames = stateMemory.getActiveStateNames();
boolean allRequiredStatesActive = config.requiredStates.stream()
.allMatch(activeStateNames::contains);
if (!allRequiredStatesActive && config.rebuildOnMismatch) {
stateDetector.rebuildActiveStates();
}
}
}
Configuration Options
Configure state checking behavior with the builder pattern:
StateCheckConfiguration config = new StateCheckConfiguration.Builder()
.withRequiredStates(List.of("MainMenu", "Dashboard"))
.withRebuildOnMismatch(true)
.withSkipIfStatesMissing(false)
.build();
Configuration Parameters
Parameter | Type | Default | Description |
---|---|---|---|
requiredStates | List<String> | Empty | States that must be active before task execution |
rebuildOnMismatch | boolean | true | Whether to rebuild states if requirements not met |
skipIfStatesMissing | boolean | false | Skip task execution if states cannot be validated |
Usage Examples
Basic Monitoring Task
@Service
public class ApplicationMonitor {
private final StateAwareScheduler stateAwareScheduler;
private final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1);
@PostConstruct
public void startMonitoring() {
StateCheckConfiguration config = new StateCheckConfiguration.Builder()
.withRequiredStates(List.of("Application"))
.withRebuildOnMismatch(true)
.build();
stateAwareScheduler.scheduleWithStateCheck(
scheduler,
this::checkApplicationHealth,
config,
5, // initial delay
10, // period
TimeUnit.SECONDS
);
}
private void checkApplicationHealth() {
// This runs only after state validation
// Application state is guaranteed to be checked
}
}
Complex Workflow Automation
@Component
public class DataSyncAutomation {
public void setupPeriodicSync() {
// Different configurations for different stages
StateCheckConfiguration loginConfig = new StateCheckConfiguration.Builder()
.withRequiredStates(List.of("LoginScreen"))
.withRebuildOnMismatch(true)
.withSkipIfStatesMissing(true) // Skip if can't find login
.build();
StateCheckConfiguration dataConfig = new StateCheckConfiguration.Builder()
.withRequiredStates(List.of("DataDashboard", "SyncPanel"))
.withRebuildOnMismatch(false) // Don't rebuild, just skip
.withSkipIfStatesMissing(true)
.build();
// Chain multiple state-aware tasks
CompletableFuture.runAsync(() -> {
performStateAwareTask(loginConfig, this::performLogin);
}).thenRun(() -> {
performStateAwareTask(dataConfig, this::syncData);
});
}
}
Error Recovery Automation
@Service
public class ErrorRecoveryService {
public void setupErrorMonitoring() {
StateCheckConfiguration config = new StateCheckConfiguration.Builder()
.withRequiredStates(List.of("NormalOperation"))
.withRebuildOnMismatch(true)
.build();
// Check every 30 seconds for error states
stateAwareScheduler.scheduleWithStateCheck(
scheduler,
this::checkAndRecoverFromErrors,
config,
0, 30, TimeUnit.SECONDS
);
}
private void checkAndRecoverFromErrors() {
// If we're here, normal operation state was validated
// Now check for any error dialogs
if (stateDetector.findState("ErrorDialog").isPresent()) {
handleErrorDialog();
// State will be rebuilt on next cycle
}
}
}
Integration with MonitoringService
State-aware scheduling complements the existing MonitoringService
:
@Component
public class EnhancedMonitoring {
private final MonitoringService monitoringService;
private final StateAwareScheduler stateAwareScheduler;
public void startStateAwareMonitoring() {
// Create state validation hook
Runnable stateCheck = stateAwareScheduler.createStateCheckHook(
new StateCheckConfiguration.Builder()
.withRequiredStates(List.of("Application"))
.build()
);
// Combine with monitoring service
monitoringService.startContinuousTask(
() -> {
stateCheck.run(); // Validate states first
performMonitoringTask();
},
() -> isApplicationRunning(),
5 // delay seconds
);
}
}
Best Practices
1. Choose Appropriate Required States
// Too specific - might fail unnecessarily
.withRequiredStates(List.of("MainMenu", "SubMenu", "SpecificDialog"))
// Better - focus on essential states
.withRequiredStates(List.of("Application", "MainWorkflow"))
2. Configure Rebuild Behavior Carefully
// For critical workflows - always rebuild
.withRebuildOnMismatch(true)
.withSkipIfStatesMissing(false)
// For optional tasks - skip if states missing
.withRebuildOnMismatch(false)
.withSkipIfStatesMissing(true)
3. Handle State Check Failures
public void scheduleWithFallback() {
try {
stateAwareScheduler.scheduleWithStateCheck(
scheduler, mainTask, mainConfig, 0, 10, TimeUnit.SECONDS
);
} catch (IllegalStateException e) {
// Fallback to simpler state requirements
stateAwareScheduler.scheduleWithStateCheck(
scheduler, fallbackTask, fallbackConfig, 0, 10, TimeUnit.SECONDS
);
}
}
4. Use Property Configuration
# application.properties
app.monitoring.required-states=Dashboard,Navigation
app.monitoring.rebuild-on-mismatch=true
app.monitoring.check-interval=5
@Value("${app.monitoring.required-states}")
private List<String> requiredStates;
@Value("${app.monitoring.rebuild-on-mismatch}")
private boolean rebuildOnMismatch;
Performance Considerations
- State Detection Overhead: State checking adds overhead to each task execution
- Rebuild Cost: Full state rebuilding can be expensive - use judiciously
- Frequency Balance: Balance checking frequency with performance impact
// High-frequency task with minimal state checking
StateCheckConfiguration lightConfig = new StateCheckConfiguration.Builder()
.withRequiredStates(List.of("MainApp")) // Single state check
.withRebuildOnMismatch(false) // No rebuild
.build();
// Low-frequency task with thorough checking
StateCheckConfiguration thoroughConfig = new StateCheckConfiguration.Builder()
.withRequiredStates(List.of("App", "Module", "Feature"))
.withRebuildOnMismatch(true)
.build();
Troubleshooting
Common Issues
- States Not Found: Ensure state names match exactly
- Excessive Rebuilding: Consider reducing rebuild frequency
- Task Skipping: Check logs for state validation failures
Debug Logging
Enable debug logging to troubleshoot state checking:
logging.level.com.yourapp.scheduling=DEBUG
logging.level.io.github.jspinak.brobot.statemanagement=DEBUG
Future Enhancements
Potential improvements to state-aware scheduling:
- Machine Learning Integration: Predict state changes and pre-validate
- State Change Events: React to state changes rather than polling
- Hierarchical State Checking: Check parent states before children
- Performance Optimization: Cache state detection results
- Advanced Recovery: Custom recovery strategies per state
Summary
State-aware scheduling provides a robust foundation for reliable GUI automation by:
- Ensuring correct state context before task execution
- Automatically recovering from state mismatches
- Maintaining clean separation of concerns
- Providing flexible configuration options
This pattern is especially valuable for long-running automation, background monitoring, and complex workflows where GUI state integrity is critical.