1# Unified Scheduler Architecture - Layered Design
2
3## Overview
4
5A clean layered architecture where:
6- **Core**: Basic scheduling interface (`Scheduler` trait) + test-enhanced concrete impl (`TestScheduler`)
7- **GPUI**: Uses trait objects for production safety, test features via `TestScheduler`
8- **Cloud**: Session wrapper uses `TestScheduler` for session coordination
9
10Key design principles:
11- Main `Scheduler` trait has only essential methods (no test pollution)
12- Test-specific features (deprioritization, task tracking) are `TestScheduler`-specific
13- Production schedulers implement minimal interface
14- Cloud requires `TestScheduler` for session features
15
16## Core Architecture
17
18```
19βββββββββββββββββββββββββββββββββββββββββββ
20β Shared Crate β
21βββββββββββββββββββββββββββββββββββββββββββ€
22β Scheduler trait: β
23β - spawn() - core Send futures β
24β - spawn_foreground() - non-Send β
25β - Platform integration (park, now) β
26β β
27β TestScheduler: β
28β - Implements Scheduler + test features β
29β - deprioritize() - test-only method β
30β - spawn_labeled() - labels for testing β
31β β
32β GcdScheduler/ThreadPoolScheduler: β
33β - Minimal Scheduler implementations β
34βββββββββββββββββββββββββββββββββββββββββββ
35 β²
36 βββββββββββΌββββββββββ
37 β β β
38βββββββββββ΄βββββ βββ΄ββββββββββ΄βββββ
39β GPUI β β Cloud β
40β Uses trait β β CloudSimulated β
41β objects β β uses Test- β
42β + TestSchedulerβ β Scheduler β
43ββββββββββββββββ βββββββββββββββββββ
44```
45
46## Scheduler Trait Definition
47
48```rust
49pub trait Scheduler: Send + Sync {
50 /// Schedule a runnable to be executed (object-safe core functionality)
51 fn schedule(&self, runnable: Runnable);
52
53 /// Schedule a runnable with label for test tracking
54 fn schedule_labeled(&self, runnable: Runnable, label: TaskLabel);
55
56 /// Schedule a runnable on the main thread (optional, defaults to panic)
57 fn schedule_foreground(&self, runnable: Runnable) {
58 panic!("schedule_foreground not supported by this scheduler");
59 }
60
61 /// Platform integration methods
62 fn park(&self, timeout: Option<Duration>) -> bool { false }
63 fn unparker(&self) -> Unparker { Arc::new(|_| {}).into() }
64 fn is_main_thread(&self) -> bool;
65 fn now(&self) -> Instant;
66}
67```
68
69**Explanation:**
70- Core trait methods are object-safe (no generic parameters)
71- `schedule` methods operate on `Runnable` for low-level execution control
72- Scheduler implementations manage task state internally when scheduling runnables
73- No task completion hooks needed on `Task` - scheduler tracks running tasks itself
74
75## Generic Spawn Helpers
76
77Generic spawn methods are implemented for `dyn Scheduler` to provide the high-level `Future` interface:
78
79```rust
80impl dyn Scheduler {
81 /// Spawn Send future (generic helper)
82 pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
83 where R: Send + 'static {
84 let task_id = self.assign_task_id();
85 let task_metadata = TaskMetadata {
86 id: task_id,
87 label: None,
88 session: None,
89 spawn_location: std::panic::Location::caller(),
90 };
91
92 let (runnable, inner_task) = async_task::spawn(future, move |runnable| {
93 // Scheduler manages task lifecycle: mark as started when scheduled
94 self.mark_task_started(task_id);
95 // When runnable completes, scheduler marks as finished
96 self.schedule_completion_callback(runnable, task_id);
97 });
98
99 // Schedule the runnable (this adds to running tasks)
100 self.schedule(runnable);
101
102 Task {
103 inner: TaskState::Spawned(inner_task),
104 metadata: task_metadata,
105 }
106 }
107
108 /// Spawn Send future with label (generic helper)
109 pub fn spawn_labeled<R>(
110 &self,
111 label: TaskLabel,
112 future: impl Future<Output = R> + Send + 'static
113 ) -> Task<R>
114 where R: Send + 'static {
115 let task_id = self.assign_task_id();
116 let task_metadata = TaskMetadata {
117 id: task_id,
118 label: Some(label),
119 session: None,
120 spawn_location: std::panic::Location::caller(),
121 };
122
123 let (runnable, inner_task) = async_task::spawn(future, move |runnable| {
124 self.mark_task_started(task_id);
125 self.schedule_completion_callback(runnable, task_id);
126 });
127
128 // Apply test-specific logic (e.g., deprioritization) in scheduler
129 self.schedule_labeled(runnable, label);
130
131 Task {
132 inner: TaskState::Spawned(inner_task),
133 metadata: task_metadata,
134 }
135 }
136
137 /// Spawn non-Send future on main thread (generic helper)
138 pub fn spawn_foreground<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
139 where R: 'static {
140 let task_id = self.assign_task_id();
141 let task_metadata = TaskMetadata {
142 id: task_id,
143 label: None,
144 session: None,
145 spawn_location: std::panic::Location::caller(),
146 };
147
148 let (runnable, inner_task) = async_task::spawn_local(future, move |runnable| {
149 self.mark_task_started(task_id);
150 self.schedule_completion_callback(runnable, task_id);
151 });
152
153 self.schedule_foreground(runnable);
154
155 Task {
156 inner: TaskState::Spawned(inner_task),
157 metadata: task_metadata,
158 }
159 }
160}
161```
162
163**Explanation:**
164- Core trait has only essential methods
165- No test-specific methods (deprioritize stays off main trait)
166- Production schedulers implement minimal interface
167- Test features are concrete `TestScheduler` methods
168
169## Task<T> Definition
170
171```rust
172#[derive(Debug)]
173pub struct Task<T> {
174 inner: TaskState<T>,
175 id: TaskId, // Mandatory for coordination
176 metadata: TaskMetadata,
177}
178
179#[derive(Debug)]
180pub struct TaskMetadata {
181 label: Option<TaskLabel>, // GPUI test identification
182 session: Option<SessionId>, // Cloud session association
183 spawn_location: Option<&'static std::panic::Location>,
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
187pub struct TaskId(pub usize);
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
190pub struct TaskLabel(NonZeroUsize);
191```
192
193**Explanation:**
194- Mandatory TaskId for session coordination
195- Optional metadata for GPUI labels and Cloud sessions
196- Task implements Future directly
197
198## TestScheduler (Concrete Implementation)
199
200```rust
201pub struct TestScheduler {
202 inner: RefCell<TestSchedulerInner>,
203}
204
205struct TestSchedulerInner {
206 tasks: HashMap<TaskId, TaskState>,
207 task_labels: HashMap<TaskId, TaskLabel>,
208 deprioritized_labels: HashSet<TaskLabel>, // Test-specific state
209 deprioritized_queue: VecDeque<(Runnable, TaskId)>,
210 main_thread_queue: VecDeque<Runnable>,
211 delayed: Vec<(Instant, Runnable)>,
212 parker: Parker,
213 is_main_thread: bool,
214 now: Instant,
215 next_task_id: AtomicUsize,
216}
217
218impl Scheduler for TestScheduler {
219 fn schedule(&self, runnable: Runnable) {
220 let task_id = self.next_task_id.fetch_add(1, Ordering::SeqCst);
221 self.inner.borrow_mut().tasks.insert(task_id, TaskState::Running);
222
223 // Schedule the runnable and setup completion callback
224 let scheduler = self.clone();
225 let completion_runnable = self.create_completion_runnable(runnable, task_id);
226 completion_runnable.schedule();
227 }
228
229 fn schedule_labeled(&self, runnable: Runnable, label: TaskLabel) {
230 let task_id = self.next_task_id.fetch_add(1, Ordering::SeqCst);
231
232 // Apply deprioritization if label is registered
233 if self.inner.borrow().deprioritized_labels.contains(&label) {
234 // Store label association and put in deprioritized queue
235 self.inner.borrow_mut().deprioritized_queue.push((runnable, task_id));
236 self.inner.borrow_mut().task_labels.insert(task_id, label);
237 } else {
238 self.inner.borrow_mut().tasks.insert(task_id, TaskState::Running);
239 let completion_runnable = self.create_completion_runnable(runnable, task_id);
240 completion_runnable.schedule();
241 }
242 }
243
244 fn schedule_foreground(&self, runnable: Runnable) {
245 assert!(self.is_main_thread(), "schedule_foreground called off main thread");
246 let task_id = self.next_task_id.fetch_add(1, Ordering::SeqCst);
247 self.inner.borrow_mut().tasks.insert(task_id, TaskState::Running);
248
249 let completion_runnable = self.create_completion_runnable(runnable, task_id);
250 // Schedule on main thread queue
251 self.inner.borrow_mut().main_thread_queue.push(completion_runnable);
252 }
253
254 fn is_main_thread(&self) -> bool { self.inner.borrow().is_main_thread }
255 fn now(&self) -> Instant { self.inner.borrow().now }
256 fn park(&self, timeout: Option<Duration>) -> bool {
257 self.inner.borrow().parker.park_timeout(timeout.unwrap_or(Duration::MAX))
258 }
259
260 fn unparker(&self) -> Unparker {
261 self.inner.borrow().parker.unparker()
262 }
263}
264
265impl TestScheduler {
266 fn assign_task_id(&self) -> TaskId {
267 TaskId(self.next_task_id.fetch_add(1, Ordering::SeqCst))
268 }
269
270 fn mark_task_started(&self, task_id: TaskId) {
271 // Task already marked running in schedule methods
272 }
273
274 fn schedule_completion_callback(&self, runnable: Runnable, task_id: TaskId) -> Runnable {
275 let scheduler = self.clone();
276 async_task::spawn(async move {
277 // Run the original runnable
278 runnable.schedule();
279 // Mark task as completed when done
280 scheduler.mark_task_completed(task_id);
281 }, |_| {}).0
282 }
283
284 fn mark_task_completed(&self, task_id: TaskId) {
285 self.inner.borrow_mut().tasks.remove(&task_id);
286 }
287
288 fn create_completion_runnable(&self, runnable: Runnable, task_id: TaskId) -> Runnable {
289 let scheduler = self.clone();
290 async_task::spawn(async move {
291 runnable.schedule();
292 scheduler.mark_task_completed(task_id);
293 }, |_| {}).0
294 }
295}
296
297// Test-specific methods (NOT on main trait)
298impl TestScheduler {
299 pub fn deprioritize(&self, label: TaskLabel) {
300 self.inner.borrow_mut().deprioritized_labels.insert(label);
301 }
302
303 pub fn is_task_running(&self, task_id: TaskId) -> bool {
304 self.inner.borrow().tasks.contains_key(&task_id)
305 }
306
307 // Additional internal methods for task lifecycle management
308 fn move_to_deprioritized_queue(&self, task_id: TaskId) {
309 // Move task to deprioritized queue for deterministic testing
310 // This is called from deprioritize to move already scheduled tasks
311 if let Some(runnable) = self.inner.borrow_mut().tasks.remove(&task_id) {
312 self.inner.borrow_mut().deprioritized_queue.push_back((runnable, task_id));
313 }
314 }
315}
316
317 // Task creation now handled by generic spawn helpers
318 // Runnable scheduling managed internally by schedule methods
319}
320```
321
322**Explanation:**
323- `deprioritize()` is a TestScheduler-specific method (not on main trait)
324- `spawn_labeled()` is TestScheduler-specific (not on main trait)
325- `is_task_running()` provides task status for Cloud session validation
326- Test-specific state stays in TestScheduler
327
328## Production Schedulers
329
330```rust
331pub struct GcdScheduler {
332 main_queue: dispatch_queue_t,
333 background_queue: dispatch_queue_t,
334}
335
336impl Scheduler for GcdScheduler {
337 fn schedule(&self, runnable: Runnable) {
338 unsafe {
339 dispatch_async_f(self.background_queue, runnable.into_raw().as_ptr() as *mut c_void, Some(trampoline));
340 }
341 }
342
343 fn schedule_labeled(&self, runnable: Runnable, _label: TaskLabel) {
344 // Production scheduler ignores labels
345 unsafe {
346 dispatch_async_f(self.background_queue, runnable.into_raw().as_ptr() as *mut c_void, Some(trampoline));
347 }
348 }
349
350 fn schedule_foreground(&self, runnable: Runnable) {
351 unsafe {
352 dispatch_async_f(self.main_queue, runnable.into_raw().as_ptr() as *mut c_void, Some(trampoline));
353 }
354 }
355
356 fn is_main_thread(&self) -> bool {
357 // macOS-specific main thread detection
358 unsafe { msg_send![class!(NSThread), isMainThread] }
359 }
360
361 fn now(&self) -> Instant { Instant::now() }
362}
363```
364
365**Explanation:**
366- Production schedulers implement object-safe `Scheduler` trait
367- No test-specific features or task state tracking
368- Minimal implementation with direct dispatch to GCD queues
369- Test features only available via `TestScheduler` wrapper in GPUI
370
371## GPUI Integration
372
373```rust
374// BackgroundExecutor uses trait objects (production-safe)
375pub struct BackgroundExecutor {
376 scheduler: Arc<dyn Scheduler>, // Any Scheduler implementation
377}
378
379impl BackgroundExecutor {
380 pub fn new(scheduler: Arc<dyn Scheduler>) -> Self {
381 Self { scheduler }
382 }
383
384 pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
385 where R: Send + 'static {
386 // Generic spawn helper implemented on dyn Scheduler
387 self.scheduler.spawn(future)
388 }
389
390 pub fn spawn_labeled<R>(
391 &self,
392 label: TaskLabel,
393 future: impl Future<Output = R> + Send + 'static
394 ) -> Task<R>
395 where R: Send + 'static {
396 // Generic spawn_labeled helper implemented on dyn Scheduler
397 self.scheduler.spawn_labeled(label, future)
398 }
399
400 // When GPUI needs test features, it downcasts to TestScheduler
401 pub fn deprioritize(&self, label: TaskLabel) {
402 // Downcast to access TestScheduler-specific features
403 if let Some(test_scheduler) = self.scheduler.downcast_ref::<TestScheduler>() {
404 test_scheduler.deprioritize(label);
405 } else {
406 // Production: do nothing (ignore test-only calls)
407 }
408 }
409}
410
411// ForegroundExecutor also uses trait objects
412pub struct ForegroundExecutor {
413 scheduler: Rc<dyn Scheduler>,
414}
415
416impl ForegroundExecutor {
417 pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
418 where R: 'static {
419 // Generic spawn_foreground helper implemented on dyn Scheduler
420 self.scheduler.spawn_foreground(future)
421 }
422}
423
424**Explanation:**
425- GPUI executors use trait objects for production safety and object-safe `Scheduler` trait
426- Generic spawn helpers provide the familiar Future-based API on `dyn Scheduler`
427- Object-safe schedule methods allow trait object usage without downcasting for basic operations
428- Test features still require downcasting to `TestScheduler` for deprioritization
429- Production deployments can use minimal schedulers via trait objects
430- Test deployments get full test features through TestScheduler wrapper
431
432## Cloud Integration
433
434```rust
435// Cloud wrapper requires TestScheduler for session features and task tracking
436pub struct CloudSimulatedScheduler {
437 scheduler: Arc<dyn Scheduler>, // Object-safe scheduler (usually TestScheduler)
438 inner: RefCell<CloudSimulatedSchedulerInner>,
439}
440
441struct CloudSimulatedSchedulerInner {
442 current_session: Option<SessionId>,
443 sessions: HashMap<SessionId, SessionData>,
444 task_to_session: HashMap<TaskId, SessionId>,
445}
446
447impl CloudSimulatedScheduler {
448 pub fn new(scheduler: Arc<dyn Scheduler>) -> Self {
449 Self {
450 scheduler,
451 inner: RefCell::new(CloudSimulatedSchedulerInner {
452 current_session: None,
453 sessions: HashMap::new(),
454 task_to_session: HashMap::new(),
455 }),
456 }
457 }
458
459 // Use generic spawn helpers with session tracking
460 pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
461 where R: Send + 'static {
462 // Get task from generic spawn helper (includes task_id assignment)
463 let task = self.scheduler.spawn(future);
464
465 // Auto-associate with current session
466 if let Some(session_id) = self.inner.borrow().current_session.clone() {
467 self.inner.borrow_mut().task_to_session.insert(task.metadata.id, session_id.clone());
468 // Track spawned task in session
469 if let Some(session) = self.inner.borrow_mut().sessions.get_mut(&session_id) {
470 session.spawned_tasks.push(task.metadata.id);
471 }
472 }
473
474 task
475 }
476
477 pub fn spawn_labeled<R>(
478 &self,
479 label: TaskLabel,
480 future: impl Future<Output = R> + Send + 'static
481 ) -> Task<R>
482 where R: Send + 'static {
483 // Use generic spawn_labeled helper
484 let task = self.scheduler.spawn_labeled(label, future);
485
486 // Auto-associate with current session
487 if let Some(session_id) = self.inner.borrow().current_session.clone() {
488 self.inner.borrow_mut().task_to_session.insert(task.metadata.id, session_id.clone());
489 // Track spawned task in session
490 if let Some(session) = self.inner.borrow_mut().sessions.get_mut(&session_id) {
491 session.spawned_tasks.push(task.metadata.id);
492 }
493 }
494
495 task
496 }
497
498 pub fn validate_session_cleanup(&self, session_id: SessionId) -> Result<()> {
499 // Use TestScheduler's internal task tracking for validation
500 if let Some(test_scheduler) = self.scheduler.downcast_ref::<TestScheduler>() {
501 let inner = self.inner.borrow();
502
503 if let Some(session) = inner.sessions.get(&session_id) {
504 let running_tasks: Vec<TaskId> = session
505 .spawned_tasks
506 .iter()
507 .filter(|&&task_id| test_scheduler.is_task_running(task_id))
508 .copied()
509 .collect();
510
511 // Check against explicit wait_until permissions
512 let unauthorized = running_tasks.difference(&session.wait_until_task_ids);
513
514 if unauthorized.next().is_some() {
515 return Err(anyhow!("Session cleanup failed: unauthorized tasks still running"));
516 }
517 }
518 } else {
519 // Production scheduler: no task tracking available
520 return Err(anyhow!("Session validation requires TestScheduler"));
521 }
522
523 Ok(())
524 }
525
526 // Session management methods
527 pub fn create_session(&self) -> SessionId {
528 let session_id = SessionId::new();
529 self.inner.borrow_mut().sessions.insert(session_id.clone(), SessionData {
530 spawned_tasks: Vec::new(),
531 wait_until_task_ids: HashSet::new(),
532 });
533 self.inner.borrow_mut().current_session = Some(session_id.clone());
534 session_id
535 }
536
537 pub fn add_wait_until_task(&self, session_id: SessionId, task_id: TaskId) {
538 if let Some(session) = self.inner.borrow_mut().sessions.get_mut(&session_id) {
539 session.wait_until_task_ids.insert(task_id);
540 }
541 }
542}
543```
544
545**Explanation:**
546- Cloud wrapper uses object-safe `Scheduler` trait with generic spawn helpers
547- Internal task management: scheduler tracks running tasks, Cloud wrapper associates with sessions
548- Session tracking enhanced: tasks automatically associated via spawn helpers and metadata
549- Task lifecycle: scheduler manages completion internally, Cloud validates against running tasks
550- Test features: downcast to TestScheduler for `is_task_running()` validation
551- Production safety: uses trait objects, but session features require TestScheduler
552
553## Migration Strategy
554
555### Phase 1: Core Infrastructure
5561. Define `Scheduler` trait (core methods only)
5572. Implement `TestScheduler` (with test features like `deprioritize()`)
5583. Implement production schedulers (GCD, ThreadPool)
5594. Define `Task<T>` with mandatory TaskId
560
561### Phase 2: GPUI Migration
5621. Update GPUI executors to use trait objects
5632. Add downcasting for test features
5643. Preserve all existing GPUI functionality
5654. Test deployments use TestScheduler, production uses minimal schedulers
566
567### Phase 3: Cloud Integration
5681. Cloud wrapper uses TestScheduler for session coordination
5692. Maintain automatic session association
5703. Preserve `wait_until` and validation behavior
5714. Application code unchanged
572
573### Phase 4: Validation
5741. GPUI tests work with new architecture
5752. Cloud session behavior preserved
5763. Production efficiency maintained
577
578## Benefits
579
580β
**Object-Safe Trait**: Scheduler trait is object-safe, enabling trait objects without downcasting for core operations
581β
**Internal Task Management**: Scheduler manages task lifecycle and completion state internally, providing unified task tracking
582β
**Clean Separation**: Test methods only on TestScheduler, generic spawn helpers on trait objects
583β
**Production Safety**: GPUI executors use trait objects with minimal dyn dispatch overhead
584β
**Session Intelligence**: Cloud gets full coordination features with automatic task-session association via spawn helpers
585β
**Flexible Architecture**: Production vs test deployments with scheduler implementations optimized for each context
586β
**Backward Compatibility**: All existing functionality preserved via generic spawn helpers on `dyn Scheduler`
587
588This design keeps test concerns in TestScheduler while maintaining production safety and session coordination capabilities through internal scheduler task management.
589
590### Benefits of This Approach
591
592β
**Interface Compatibility**: GPUI code continues using Future-based spawn, Cloud uses session-aware wrappers
593β
**Performance**: Trait objects have minimal overhead, direct Runnable scheduling in production
594β
**Separation of Concerns**: Low-level Runnable scheduling in trait, high-level Future API as helpers
595β
**Object Safety**: Enables `Arc<dyn Scheduler>` usage without runtime downcasting for basic operations
596
597### Migration Impact
598
599- GPUI executors: Simple switch from `Arc<dyn PlatformDispatcher>` to `Arc<dyn Scheduler>`
600- Cloud wrapper: Enhanced to automatically associate tasks with sessions via spawn helpers
601- Test infrastructure: TestScheduler provides both low-level scheduling and task tracking internally
602
603## Implementation Reference
604
605### GPUI File Paths & Types
606
607**Core Executor Files:**
608- `crates/gpui/src/executor.rs` - `BackgroundExecutor`, `ForegroundExecutor`, `Task<T>`
609- `crates/gpui/src/app/app.rs` - App-level executor access
610- `crates/gpui/src/platform.rs` - `PlatformDispatcher` trait (current system)
611
612**Types to Update:**
613- `BackgroundExecutor` - Switch from `PlatformDispatcher` to `Arc<dyn Scheduler>`
614- `ForegroundExecutor` - Switch from `PlatformDispatcher` to `Rc<dyn Scheduler>`
615- `Task<T>` - Ensure compatibility with new `Task<T>` design
616
617**Test Infrastructure:**
618- `crates/gpui/src/platform/test/dispatcher.rs` - `TestDispatcher` (current)
619- Will need new `TestScheduler` implementation
620- `TaskLabel` usage in tests
621
622### Cloud File Paths & Types
623
624**Core Runtime Files:**
625- `crates/platform_simulator/src/runtime.rs` - `SimulatorRuntime`, session management
626- `crates/platform_simulator/src/platform.rs` - `SimulatedExecutionContext`
627- `crates/platform_simulator/src/lib.rs` - Cloud worker setup
628
629**Key Types:**
630- `SessionId` - Session identification
631- `WorkerSession` - Session state tracking
632- `ExecutionContext::wait_until()` - Session coordination API
633- `SimulatorRuntime::validate_session_cleanup()` - Cleanup validation
634
635**Worker Files:**
636- `crates/client_api/src/client_api.rs` - API endpoints using sessions
637- `crates/cloud_worker/src/worker.rs` - Worker execution with sessions
638- `crates/cloudflare_platform/src/execution_context.rs` - Platform-specific execution context
639
640### Migration Points
641
642**GPUI Areas:**
643- Update GPUI executors to use `Arc<dyn Scheduler>` trait objects
644- Replace `PlatformDispatcher` usage with object-safe `Scheduler` methods and generic spawn helpers
645- Preserve `spawn_labeled()` and `deprioritize()` APIs via generic helpers and downcasting
646- Update `BackgroundExecutor` and `ForegroundExecutor` to call `dyn Scheduler` spawn helpers
647
648**Cloud Areas:**
649- Replace `SimulatorRuntime` with `CloudSimulatedScheduler` wrapper around `dyn Scheduler`
650- Implement session management using wrapper's spawn helpers with automatic task association
651- Preserve `wait_until()` and session validation via downcast to TestScheduler for task tracking
652- Update `ExecutionContext` implementation to use new wrapper
653
654### Test Files Impacted
655
656**GPUI Tests:**
657- `crates/gpui/src/app/test_context.rs` - Test setup
658- `crates/gpui_macros/src/test.rs` - Test macro generation
659- Project-specific test files using `deprioritize()`
660
661**Cloud Tests:**
662- `crates/platform_simulator/src/lib.rs` - Test setup
663- Worker test files using session features
664- Session validation test cases
665
666### Platform Backend Files
667
668**macOS:**
669- `crates/gpui/src/platform/mac/dispatcher.rs` - `MacDispatcher` β `GcdScheduler`
670
671**Linux:**
672- `crates/gpui/src/platform/linux/dispatcher.rs` - `LinuxDispatcher` β `ThreadPoolScheduler`
673
674**Windows:**
675- `crates/gpui/src/platform/windows/dispatcher.rs` - `WindowsDispatcher` β `ThreadPoolScheduler`
676
677**Test:**
678- `crates/gpui/src/platform/test/dispatcher.rs` - `TestDispatcher` β `TestScheduler`
679
680## Compatibility Checklist
681
682### GPUI Compatibility
683- β
`spawn()` β `dyn Scheduler::spawn()` (generic helper on trait object)
684- β
`spawn_labeled(label)` β `dyn Scheduler::spawn_labeled()` (generic helper on trait object)
685- β
`deprioritize()` β Downcast to TestScheduler, then `TestScheduler::deprioritize()`
686- β
`timer()` β `scheduler.timer()` (platform method on trait object)
687- β
`BackgroundExecutor` β Trait object wrapper using `dyn Scheduler`
688
689### Cloud Compatibility
690- β
`ExecutionContext::wait_until()` β Scheduler wrapper with generic spawn helpers
691- β
Session validation β `validate_session_cleanup()` with downcast to TestScheduler
692- β
Automatic session association β Via spawn helpers and task metadata
693- β
Task cleanup checking β Internal scheduler task tracking (downcast to TestScheduler for running status)
694- β
`spawn()` in sessions β `dyn Scheduler::spawn()` with auto-association in wrapper
695
696### Test Compatibility
697- β
Test determinism β TestScheduler deprioritization
698- β
Task labeling β TestScheduler spawn_labeled override
699- β
Session coordination β Cloud wrapper
700- β
Production efficiency β Minimal scheduler implementations
701
702## Next Steps
703
7041. **Create shared scheduler crate** with core types
7052. **Implement TestScheduler** with task tracking and test features
7063. **Update GPUI executors** to use trait objects
7074. **Create Cloud wrapper** with session coordination
7085. **Migrate platform backends** to new scheduler implementations
7096. **Update tests** to use new architecture
7107. **Validate performance** and backward compatibility