move daemon services to backend isolate
The merged UI/platform thread on macOS (Flutter 3.41) freezes on any synchronous work — file I/O, Process.run, even isolate spawning during early frames. No timing workaround (Timer, addPostFrameCallback, Future.delayed) was reliable. Fix: spawn a backend isolate that owns the DaemonDispatcher, GitClient, PqlClient, FilesService, EditorRegistry, and Toolchain resolution. The main isolate stays free for rendering. Communication uses SendPort with the existing IPC message protocol (IpcRequest/IpcResponse/IpcEvent) — zero new serialization. New files: backend.dart — spawns isolate, manages SendPort/ReceivePort backend_entry.dart — isolate entry point, boots all services isolate_client.dart — replaces InProcessClient for production Toolchain now exposes resolveToolchainPaths() as a top-level function with self-contained PATH expansion (no module-level state that would prevent isolate message passing). ClideTestApp gains boot-sequence tests: compute(), Isolate.run(), sequential Process.run, and the full resolve+exec chain. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
9fff157c9e
commit
a89910dc36
@@ -0,0 +1,69 @@
|
||||
/// IPC client that sends requests to a backend isolate via SendPort.
|
||||
///
|
||||
/// Replaces [InProcessClient] for production use. The backend isolate
|
||||
/// owns the [DaemonDispatcher] and all subprocess/file-I/O services.
|
||||
/// Requests and responses travel as serialized Maps over SendPort,
|
||||
/// reusing the existing IPC protocol (IpcRequest/IpcResponse/IpcEvent).
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:clide/clide.dart';
|
||||
import 'package:clide/kernel/src/events/bus.dart';
|
||||
import 'package:clide/kernel/src/events/types.dart';
|
||||
import 'package:clide/kernel/src/ipc/client.dart';
|
||||
import 'package:clide/kernel/src/log.dart';
|
||||
|
||||
class IsolateClient extends DaemonClient {
|
||||
IsolateClient({
|
||||
required Logger log,
|
||||
required DaemonBus events,
|
||||
required SendPort backendPort,
|
||||
}) : _backendPort = backendPort,
|
||||
_events = events,
|
||||
super(socketPath: '', log: log, events: events);
|
||||
|
||||
final SendPort _backendPort;
|
||||
final DaemonBus _events;
|
||||
final Map<String, Completer<IpcResponse>> _pending = {};
|
||||
int _nextId = 0;
|
||||
|
||||
/// Called by [Backend] to feed incoming messages from the backend isolate.
|
||||
void handleMessage(Map<String, Object?> msg) {
|
||||
final type = msg['type'] as String?;
|
||||
switch (type) {
|
||||
case 'response':
|
||||
final resp = IpcResponse.fromJson(msg);
|
||||
final c = _pending.remove(resp.id);
|
||||
if (c != null && !c.isCompleted) c.complete(resp);
|
||||
case 'event':
|
||||
final evt = IpcEvent.fromJson(msg);
|
||||
_events.emit(DaemonEvent(
|
||||
subsystem: evt.subsystem,
|
||||
kind: evt.kind,
|
||||
data: evt.data,
|
||||
ts: evt.timestamp,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isConnected => true;
|
||||
|
||||
@override
|
||||
Future<void> start() async {}
|
||||
|
||||
@override
|
||||
Future<void> stop() async {}
|
||||
|
||||
@override
|
||||
Future<IpcResponse> request(String cmd, {Map<String, Object?> args = const {}}) {
|
||||
final id = '${_nextId++}';
|
||||
final req = IpcRequest(id: id, cmd: cmd, args: args);
|
||||
final c = Completer<IpcResponse>();
|
||||
_pending[id] = c;
|
||||
_backendPort.send(req.toJson());
|
||||
return c.future;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user