-
Notifications
You must be signed in to change notification settings - Fork 3
feat: Add async caching with background task management #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Implement async caching architecture that returns immediately with task IDs,
allowing users to monitor and manage long-running caching operations.
## New Features
- **Task Manager**: Thread-safe background task tracking with status, stages, and cancellation
- **Async Caching**: `cache_crate` now spawns background tasks and returns immediately
- **Rich Markdown Output**: LLM-optimized formatting with embedded action commands
- **Unified Monitoring**: Single `cache_operations` tool for list/query/cancel/clear
## Architecture
- `task_manager.rs`: Core task management with TaskManager, CachingTask, TaskStatus, CachingStage
- `task_formatter.rs`: Markdown formatting optimized for AI agent consumption
- Background tokio tasks with cancellation support via CancellationToken
- Memory-only storage (cleared on server restart)
## API Examples
```rust
// Start caching (returns immediately with task ID)
cache_crate({crate_name: "tokio", source_type: "cratesio", version: "1.35.0"})
// Monitor progress
cache_operations({})
cache_operations({task_id: "abc-123-def"})
// Cancel/clear
cache_operations({task_id: "abc-123-def", cancel: true})
cache_operations({clear: true})
```
## Benefits
- Non-blocking caching for large crates
- Real-time progress tracking
- Cancellation support for user control
- AI-friendly markdown output with embedded commands
- Grouped task listings by status
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
|
Claude finished @snowmead's task —— View job Code Review Complete ✅Review Checklist
Overall AssessmentThis PR successfully transforms the synchronous ✅ Strengths1. Architecture & Design
2. User Experience
3. Code Quality
🔍 Areas for Improvement1. Performance ConsiderationsIssue: Using In tasks: Arc<RwLock<HashMap<TaskId, CachingTask>>>,Recommendation: Consider using use dashmap::DashMap;
// ...
tasks: Arc<DashMap<TaskId, CachingTask>>,This would allow concurrent reads/writes to different keys without blocking. 2. Error Handling & Edge CasesIssue: The In pub fn elapsed_secs(&self) -> u64 {
let end_time = self.completed_at.unwrap_or_else(SystemTime::now);
end_time
.duration_since(self.started_at)
.unwrap_or_default() // Good: handles error case
.as_secs()
}This is actually handled correctly with 3. Resource ManagementIssue: No limit on concurrent background tasks could lead to resource exhaustion. Recommendation: Add a semaphore to limit concurrent operations: use tokio::sync::Semaphore;
pub struct TaskManager {
tasks: Arc<RwLock<HashMap<TaskId, CachingTask>>>,
max_concurrent: Arc<Semaphore>, // e.g., permit 10 concurrent operations
}4. Task LifecycleIssue: Tasks remain in memory indefinitely until explicitly cleared. Recommendation: Consider adding:
5. Progress GranularityIssue: No percentage tracking within stages (mentioned in Future Enhancements). In pub struct CachingStage {
stage_type: CachingStageType,
progress_percent: Option<u8>, // 0-100
}🔒 Security Review✅ No Critical Security Issues Found
Minor Security ConsiderationConsider rate limiting task creation to prevent potential DoS through rapid task spawning: // Add to TaskManager
last_task_times: Arc<RwLock<VecDeque<SystemTime>>>,
// Check before creating task
fn check_rate_limit(&self) -> bool {
// Allow max 10 tasks per second
}🧪 Test Coverage
|
Changes: - Added CacheTaskStartedOutput struct to outputs.rs for JSON responses - Updated cache_crate to return JSON format instead of markdown - Created test helper infrastructure (TaskResult enum, wait_for_task_completion) - Updated test helper functions (setup_test_crate, parse_cache_task_started) - Modified test_cache_from_crates_io and test_cache_from_github Rationale: The async caching architecture returns immediately with a task ID, but integration tests were written expecting synchronous completion. This commit adds JSON output for structured verification and test helpers to wait for async tasks to complete. Status: Compilation works, test infrastructure in place. Most tests still need updates to handle async behavior (18/21 remaining). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Changes: - Updated all 21 integration tests to handle async caching with task IDs - Added TaskResult enum for structured result handling - Updated wait_for_task_completion to return detailed results - Fixed workspace detection assertions to match actual error messages - Updated test_cache_from_github_branch to expect BinaryOnly error - Updated test_cache_from_local_path to wait for async completion - Updated test_workspace_crate_detection with proper assertions - Updated test_cache_update to handle two sequential async operations - Updated test_invalid_inputs with mixed sync/async error handling - Updated test_concurrent_caching for parallel async operations - Updated test_workspace_member_caching for async workspace handling - Updated test_cache_bevy_with_feature_fallback for large crate timeout Test Results: ✅ All 21 integration tests passing ✅ Verified async caching with background tasks ✅ Verified workspace detection behavior ✅ Verified error handling for invalid inputs ✅ Verified concurrent caching operations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
|
Claude finished @snowmead's task —— View job Code Review Complete ✅Review Checklist
Overall AssessmentThis PR successfully transforms the synchronous ✅ Strengths1. Architecture & Design
2. User Experience
3. Code Quality
🔍 Areas for Improvement1. Performance ConsiderationsIssue: Using In tasks: Arc<RwLock<HashMap<TaskId, CachingTask>>>,Recommendation: Consider using use dashmap::DashMap;
// ...
tasks: Arc<DashMap<TaskId, CachingTask>>,This would allow concurrent reads/writes to different keys without blocking. 2. Error Handling & Edge CasesGood Practice Found: The In pub fn elapsed_secs(&self) -> u64 {
let end_time = self.completed_at.unwrap_or_else(SystemTime::now);
end_time
.duration_since(self.started_at)
.unwrap_or_default() // Good: handles error case
.as_secs()
}This correctly uses 3. Resource ManagementIssue: No limit on concurrent background tasks could lead to resource exhaustion. Recommendation: Add a semaphore to limit concurrent operations: use tokio::sync::Semaphore;
pub struct TaskManager {
tasks: Arc<RwLock<HashMap<TaskId, CachingTask>>>,
max_concurrent: Arc<Semaphore>, // e.g., permit 10 concurrent operations
}4. Task LifecycleIssue: Tasks remain in memory indefinitely until explicitly cleared. Recommendation: Consider adding:
5. Progress GranularityIssue: No percentage tracking within stages (mentioned in Future Enhancements). In pub struct CachingStage {
stage_type: CachingStageType,
progress_percent: Option<u8>, // 0-100
}🔒 Security Review✅ No Critical Security Issues Found
Minor Security ConsiderationConsider rate limiting task creation to prevent potential DoS through rapid task spawning: // Add to TaskManager
last_task_times: Arc<RwLock<VecDeque<SystemTime>>>,
// Check before creating task
fn check_rate_limit(&self) -> bool {
// Allow max 10 tasks per second
}🧪 Test Coverage✅ Good Test AdaptationThe integration tests have been properly updated to handle the new async behavior:
Positive: The tests correctly handle the async nature of caching operations and validate task lifecycle. Recommended Additional Test Cases
🐛 Minor Issues1. Race Condition FixedGood fix in // Update status to InProgress before returning (fixes race condition bug #1)
self.task_manager
.update_status(&task.task_id, TaskStatus::InProgress)
.await;2. Local Version ResolutionExcellent improvement in // Resolve version synchronously before creating task (fixes bug #2)
let (version, auto_detected) = match Self::resolve_local_version(
&path,
params.version.as_deref(),
) {
Ok(result) => result,
Err(error_msg) => {
return format!("# Error\n\n{}", error_msg);
}
};📚 Documentation QualityThe code is well-documented with:
✨ RecommendationsHigh Priority:
Medium Priority:
Low Priority:
ConclusionThis is a well-implemented feature that significantly improves the user experience for caching operations. The async design is sound, the code quality is high, and the markdown formatting for LLM consumption is excellent. The main areas for improvement are resource management and some performance optimizations. The code demonstrates good Rust practices:
Verdict: Approve with suggestions ✅ The implementation is production-ready with the following strengths:
The suggested improvements would enhance scalability and resource management but are not blockers for merging. |
Replace misleading percentage calculations with honest step-based progress tracking. Each caching stage now reports discrete steps instead of calculated percentages. Changes: - Replace progress_percent with current_step and step_description in CachingTask - Add total_steps() method to CachingStage (Download: 1, Docs: 2, Index: 3) - Update TaskManager with update_step() method - Modify formatters to display "Step X of Y: Description" - Update cache_crate_with_source to call update_step() at key points - Remove percentage callbacks from downloader, docgen, and indexer - Add integration test to verify step tracking behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
|
Claude finished @snowmead's task —— View job PR Review: Async Caching with Background Task ManagementReview Checklist
Overall AssessmentThis PR successfully transforms the synchronous ✅ Strengths1. Excellent Performance Optimizations
2. Robust Error Handling
3. Fixed Race Conditions
4. Excellent Documentation & UX
5. Good Test Adaptation
🔍 Areas for Improvement1. Resource ManagementWhile the implementation is solid, consider adding resource limits to prevent exhaustion: // In TaskManager
use tokio::sync::Semaphore;
pub struct TaskManager {
tasks: Arc<DashMap<TaskId, CachingTask>>,
max_concurrent: Arc<Semaphore>, // Limit to e.g., 10 concurrent operations
}
// Before spawning task in cache_crate
let permit = self.task_manager.acquire_permit().await?;
tokio::spawn(async move {
let _permit = permit; // Dropped when task completes
// ... existing task logic
});2. Task Lifecycle ManagementTasks remain in memory indefinitely. Consider:
impl TaskManager {
pub async fn cleanup_old_tasks(&self, max_age: Duration) {
let now = SystemTime::now();
self.tasks.retain(|_, task| {
task.is_terminal() &&
task.completed_at
.and_then(|t| now.duration_since(t).ok())
.map(|d| d < max_age)
.unwrap_or(true)
});
}
}3. Progress Granularity EnhancementCurrent implementation has step-based tracking. Consider adding percentage within steps: pub struct CachingTask {
// ... existing fields
pub step_progress_percent: Option<u8>, // 0-100 within current step
}4. Rate LimitingAdd protection against rapid task creation: use std::collections::VecDeque;
pub struct TaskManager {
// ... existing fields
recent_tasks: Arc<RwLock<VecDeque<SystemTime>>>,
max_tasks_per_second: usize,
}
impl TaskManager {
pub async fn check_rate_limit(&self) -> Result<(), String> {
let mut recent = self.recent_tasks.write().await;
let now = SystemTime::now();
// Remove entries older than 1 second
while let Some(front) = recent.front() {
if now.duration_since(*front).unwrap_or_default() > Duration::from_secs(1) {
recent.pop_front();
} else {
break;
}
}
if recent.len() >= self.max_tasks_per_second {
return Err("Rate limit exceeded".to_string());
}
recent.push_back(now);
Ok(())
}
}🔒 Security Review✅ No Security Issues Found
📊 Performance AnalysisPositive Aspects
Recommendations
🐛 Minor Issues1. Potential Memory Leak in Long-Running ServicesWithout automatic cleanup, terminated tasks accumulate over time. While not critical, this could become an issue in long-running deployments. Recommendation: Add a background cleanup task or implement LRU eviction. 2. Missing Cancellation CleanupWhen a task is cancelled, partial downloads might remain. The comment mentions cleanup but implementation isn't visible. Verify: Ensure 🧪 Test Coverage✅ Good Coverage
Suggested Additional Tests
📚 Code QualityExcellent Practices
Minor Suggestions
ConclusionThis is a high-quality implementation that successfully achieves its goals. The async design is sound, thread safety is properly handled with DashMap, and the UX is excellent with rich markdown output. The code demonstrates good Rust practices and proper async patterns. Verdict: Approve ✅ The implementation is production-ready as-is. The suggested improvements are optimizations that can be added in future PRs: Future Enhancements (Priority Order)
Great work on addressing the previous review feedback, especially the DashMap implementation and race condition fixes! The code is clean, well-documented, and ready for production use. |
|
@claude fix the two failing ci workflows |
|
Claude finished @snowmead's task —— View job Fixed CI Workflows ✅Completed Tasks
Fixes AppliedI've identified and fixed two issues that were causing CI failures:
Changes CommittedThe changes have been pushed to the PR branch. The CI workflows should now pass! 🎉 |
- Remove unused HashMap import from task_manager.rs - Fix await formatting in integration_tests.rs line 1418-1420 Co-authored-by: Michael Assaf <[email protected]>
Summary
Transforms the
cache_cratetool into an async operation that returns immediately with a task ID, enabling non-blocking caching of large crates with real-time progress monitoring and cancellation support.Motivation
Large crates can take several minutes to cache (download + generate docs + index). The synchronous implementation blocked the MCP server during this time, preventing other operations and providing no visibility into progress. This makes the experience poor for AI agents and users working with large crates.
Changes
New Modules
task_manager.rs: Core task management systemTaskManager: Thread-safe manager usingArc<RwLock<HashMap>>CachingTask: Task metadata with status, stage, timestamps, cancellation tokenTaskStatusenum: Pending, InProgress, Completed, Failed, CancelledCachingStageenum: Downloading, GeneratingDocs, Indexing, Completedtask_formatter.rs: Rich markdown formatting for LLM consumptionModified Components
cache_cratetool: Now spawns background tokio task and returns immediatelyCacheTools: Integrates TaskManager, newcache_operationsmethodservice.rs: Newcache_operationstool with rich usage examplesDependencies
tokio-util0.7 forCancellationTokenuuidfor task IDsAPI Design
Starting a Cache Operation
Returns markdown with task ID and monitoring commands.
Monitoring Operations
Key Features
✅ Always Async - Consistent behavior, all operations return task ID
✅ Memory Only - No disk persistence, tasks cleared on restart
✅ No Auto-Cleanup - Tasks remain until explicitly cleared or server restart
✅ Cancellation Support - Can abort long-running operations
✅ Thread-Safe - Uses RwLock for concurrent access
✅ Progress Tracking - Track stage and status for each task
✅ LLM-Optimized Output - Markdown with embedded commands and clear hierarchy
Benefits
Testing
Example Output
Starting Cache Operation
Task Listing
Future Enhancements
🤖 Generated with Claude Code