2025-08-31_15-47-17_unified-scheduler-architecture.md

  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