stop method
Stops the client and closes all sessions.
Sessions are destroyed with up to 3 retries and exponential backoff. Returns a list of errors encountered during cleanup.
Implementation
Future<List<Exception>> stop() async {
_forceStopping = true;
final errors = <Exception>[];
// Destroy all sessions with retry logic (direct RPC, bypassing
// session.destroy()'s idempotency guard to allow real retries).
final sessionsToDestroy = List<CopilotSession>.from(_sessions.values);
for (final session in sessionsToDestroy) {
Exception? lastError;
for (var attempt = 1; attempt <= 3; attempt++) {
try {
await _connection?.sendRequest(
'session.destroy',
{'sessionId': session.sessionId},
const Duration(seconds: 10),
);
lastError = null;
break;
} catch (e) {
lastError = e is Exception ? e : Exception(e.toString());
if (attempt < 3) {
// Exponential backoff: 100ms, 200ms
await Future<void>.delayed(
Duration(milliseconds: 100 * (1 << (attempt - 1))),
);
}
}
}
// Always clean up the session locally
session.handleConnectionClose();
if (lastError != null) {
errors.add(Exception(
'Failed to destroy session ${session.sessionId} after 3 attempts: '
'$lastError',
));
}
}
_sessions.clear();
_modelsCache = null;
// Close connection and transport
try {
await _connection?.close();
} catch (e) {
errors.add(
e is Exception ? e : Exception('Failed to close connection: $e'));
}
_connection = null;
try {
await _transport?.close();
} catch (e) {
errors
.add(e is Exception ? e : Exception('Failed to close transport: $e'));
}
_setConnectionState(ConnectionState.disconnected);
_forceStopping = false;
return errors;
}