start method
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;
}
}