start method

Future<void> start({
  1. int? port,
  2. int? maxHeapMb,
})

Start the gRPC server

Returns when server is ready to accept connections. If already running, returns immediately.

port - gRPC server port (default: auto-detect free port) maxHeapMb - Maximum JVM heap size in MB (default: auto-detect, max 4096)

Implementation

Future<void> start({int? port, int? maxHeapMb}) async {
  if (_serverProcess != null) {
    debugPrint('[ServerProcessManager] Server already running on port $_currentPort');
    return;
  }

  if (_isStarting) {
    debugPrint('[ServerProcessManager] Server is starting, waiting...');
    return _startCompleter?.future;
  }

  _isStarting = true;
  _startCompleter = Completer<void>();
  _currentPort = port ?? await _findFreePort();

  try {
    final javaPath = await _findJava();
    final jarPath = await _getJarPath();
    final nativesPath = await _getNativesPath();

    debugPrint('[ServerProcessManager] Starting server...');
    debugPrint('[ServerProcessManager] Java: $javaPath');
    debugPrint('[ServerProcessManager] JAR: $jarPath');
    debugPrint('[ServerProcessManager] Natives: $nativesPath');

    // Verify JAR exists
    if (!await File(jarPath).exists()) {
      throw Exception('Server JAR not found at: $jarPath');
    }

    // Calculate heap size - use provided value or auto-detect
    final heapMb = maxHeapMb ?? _getRecommendedHeapSizeMb();
    debugPrint('[ServerProcessManager] Using heap size: ${heapMb}MB');

    _serverProcess = await Process.start(
      javaPath,
      [
        '-Djava.library.path=$nativesPath',
        '-Xmx${heapMb}m',
        '-jar',
        jarPath,
        _currentPort.toString(),
      ],
      environment: {
        if (Platform.isLinux) 'LD_LIBRARY_PATH': nativesPath,
        if (Platform.isMacOS) 'DYLD_LIBRARY_PATH': nativesPath,
      },
    );

    // Monitor stdout for startup message
    final startupCompleter = Completer<void>();
    Timer? timeoutTimer;

    _serverProcess!.stdout.transform(utf8.decoder).listen((line) {
      debugPrint('[LiteRT-LM Server] $line');
      if (line.contains('started on port') && !startupCompleter.isCompleted) {
        startupCompleter.complete();
      }
    });

    _serverProcess!.stderr.transform(utf8.decoder).listen((line) {
      debugPrint('[LiteRT-LM Server ERROR] $line');
      if (line.contains('Exception') && !startupCompleter.isCompleted) {
        startupCompleter.completeError(Exception('Server failed to start: $line'));
      }
    });

    // Handle process exit
    _serverProcess!.exitCode.then((code) {
      debugPrint('[ServerProcessManager] Server process exited with code $code');
      _serverProcess = null;
      if (!startupCompleter.isCompleted) {
        startupCompleter.completeError(Exception('Server process exited unexpectedly'));
      }
    });

    // Timeout for startup
    timeoutTimer = Timer(const Duration(seconds: 30), () {
      if (!startupCompleter.isCompleted) {
        startupCompleter.completeError(
          TimeoutException('Server startup timed out after 30 seconds'),
        );
      }
    });

    // Wait for startup
    await startupCompleter.future;
    timeoutTimer.cancel();

    debugPrint('[ServerProcessManager] Server started successfully on port $_currentPort');
    _startCompleter?.complete();
  } catch (e) {
    debugPrint('[ServerProcessManager] Failed to start server: $e');
    _serverProcess?.kill();
    _serverProcess = null;
    _startCompleter?.completeError(e);
    rethrow;
  } finally {
    _isStarting = false;
  }
}