setProtocol method

  1. @override
Future<void> setProtocol(
  1. ProtocolID protocol
)
override

Implementation

@override
Future<void> setProtocol(ProtocolID protocol) async {
  if (_protocolScopeImpl != null) {
    _logger.severe('$name: stream scope already attached to a protocol: ${_protocolScopeImpl!.protocol}');
    throw Exception('$name: stream scope already attached to a protocol: ${_protocolScopeImpl!.protocol}');
  }
  _logger.fine('$name: Setting protocol to $protocol for peer ${_peerScopeImpl.peer}');

  // 1. Get necessary scopes from ResourceManager
  final newProtocolScope = _rcmgr.getProtocolScopeInternal(protocol);
  final systemScope = _rcmgr.systemScope;
  final limiter = _rcmgr.limiter;

  // Explicitly cast to the concrete PeerId type
  final newPeerProtoScope = newProtocolScope.getPeerSubScope(
    _peerScopeImpl.peer as concrete_peer_id.PeerId,
    limiter,
    systemScope
  );

  // 2. Identify original transient scope
  // Initial edges for a stream are [peerScope, transientScope, systemScope]
  ResourceScopeImpl? transientScope;
  for (final edge in edges) {
    if (edge.name == 'transient' && edge is TransientScopeImpl) {
      transientScope = edge;
      break;
    }
  }
  if (transientScope == null) {
    _logger.fine('$name: Edges: ${edges.map((e) => e.name).join(', ')}');
    throw StateError('$name: Transient scope not found in initial edges for juggling.');
  }

  // 3. Resource Juggling
  network_errors.ResourceLimitExceededException? reservationError;
  // bool reservedInProto = false; // Removed: Direct reservation on protocolScope is removed.
  bool reservedInPeerProto = false;

  try {
    // Reserve in PeerProtoScope (this will also reserve in its parent ProtocolScope and SystemScope)
    try {
      newPeerProtoScope.addStream(direction);
      reservedInPeerProto = true;
    } on network_errors.ResourceLimitExceededException catch (e) {
     _logger.severe('Failed to add stream - $direction - $e') ;
      reservationError = e;
    }
    // Other exceptions will propagate up and be caught by the outer try-catch

    if (reservationError == null) {
      // Reservation successful, release from original TransientScope
      // No need to check for errors here as removeStream is void and shouldn't fail in a way that needs rollback here
      (transientScope as TransientScopeImpl).removeStream(direction);

      // Update internal state
      _protocolScopeImpl = newProtocolScope; // Still useful to store this reference
      _peerProtoScope = newPeerProtoScope;

      // New edges for the stream scope: its peer and the specific peer-protocol scope.
      // Resource accounting will flow up from peerProtoScope to protocolScope and systemScope.

      // Manage ref counts for edge changes
      List<ResourceScopeImpl> oldEdges = List.from(this.edges);
      List<ResourceScopeImpl> newEdges = [_peerScopeImpl, newPeerProtoScope];

      for (var oldEdge in oldEdges) {
        if (!newEdges.contains(oldEdge)) {
          oldEdge.decRef();
        }
      }
      for (var newEdge in newEdges) {
        if (!oldEdges.contains(newEdge)) {
          newEdge.incRef();
        }
      }
      this.edges = newEdges;

      _logger.fine('$name: Successfully set protocol to $protocol. Resources transferred, edges updated.');
    }
  } on network_errors.ResourceLimitExceededException catch (e) {
    reservationError = e;
  } catch (e) { // Catch other exceptions during reservation attempts
    _logger.severe('$name: Unexpected error during setProtocol resource juggling: $e');
    // Ensure reservationError is set if it's a limit issue, otherwise rethrow or handle.
    if (e is Exception && reservationError == null) { // Avoid overwriting a specific limit error
       // Wrap it or handle as a generic failure
      throw Exception('$name: Failed to set protocol due to an unexpected error: $e');
    }
    // If it was a limit error, reservationError should already be set.
    // If it's another type of error that wasn't caught by the specific `else { throw err; }`
    // it will be caught here. If reservationError is still null, it means it's not a limit error.
    if (reservationError == null && e is! network_errors.ResourceLimitExceededException) {
        throw e; // Rethrow if not a limit error and not already handled
    }
  }


  if (reservationError != null) {
    // Rollback successful reservations
    if (reservedInPeerProto) {
      // This will also trigger release from its parents (protocolScope, systemScope)
      newPeerProtoScope.removeStream(direction);
    }
    // No direct reservation on newProtocolScope, so no direct rollback needed for it.
    _logger.fine('$name: Failed to reserve resources for protocol $protocol: $reservationError.');
    throw reservationError;
  }
}