@@ -349,68 +349,124 @@ public override Task SendUsersAsync(IReadOnlyList<string> userIds, string method
349349
350350 public override async Task AddToGroupAsync ( string connectionId , string groupName ,
351351 CancellationToken cancellationToken = new ( ) )
352+ {
353+ await AddToGroupsAsync ( connectionId , [ groupName ] , cancellationToken ) ;
354+ }
355+
356+ public async Task AddToGroupsAsync ( string connectionId , IReadOnlyList < string > groupNames ,
357+ CancellationToken cancellationToken = default )
352358 {
353359 var subscription = GetSubscription ( connectionId ) ;
354360 if ( subscription is null )
355361 {
356362 return ;
357363 }
358364
365+ var uniqueGroupNames = GetUniqueGroupNames ( groupNames ) ;
366+ if ( uniqueGroupNames . Length == 0 )
367+ {
368+ return ;
369+ }
370+
359371 if ( _orleansSignalOptions . Value . GroupPartitionCount > 1 )
360372 {
361373 var coordinatorGrain = NameHelperGenerator . GetGroupCoordinatorGrain < THub > ( _clusterClient ) ;
362- var partitionId = await Task . Run ( ( ) => coordinatorGrain . GetPartitionForGroup ( groupName ) , cancellationToken ) ;
363- var partitionGrain = NameHelperGenerator . GetGroupPartitionGrain < THub > ( _clusterClient , partitionId ) ;
364- var hubKey = NameHelperGenerator . CleanString ( typeof ( THub ) . FullName ! ) ;
374+ var partitionIds = await Task . Run (
375+ ( ) => coordinatorGrain . AddConnectionToGroups ( uniqueGroupNames , connectionId , subscription . Reference ) ,
376+ cancellationToken ) ;
365377
366- await Task . Run ( ( ) => partitionGrain . EnsureInitialized ( hubKey ) , cancellationToken ) ;
367-
368- subscription . AddGrain ( partitionGrain ) ;
369- await Task . Run ( ( ) => partitionGrain . AddConnection ( connectionId , subscription . Reference ) , cancellationToken ) ;
370- await Task . Run ( ( ) => coordinatorGrain . AddConnectionToGroup ( groupName , connectionId , subscription . Reference ) , cancellationToken ) ;
378+ foreach ( var partitionId in partitionIds )
379+ {
380+ var partitionGrain = NameHelperGenerator . GetGroupPartitionGrain < THub > ( _clusterClient , partitionId ) ;
381+ subscription . AddGrain ( partitionGrain ) ;
382+ }
371383 }
372384 else
373385 {
374- var groupGrain = NameHelperGenerator . GetSignalRGroupGrain < THub > ( _clusterClient , groupName ) ;
375- await Task . Run ( ( ) => groupGrain . AddConnection ( connectionId , subscription . Reference ) , cancellationToken ) ;
376- subscription . AddGrain ( groupGrain ) ;
386+ var groupGrains = uniqueGroupNames
387+ . Select ( groupName => NameHelperGenerator . GetSignalRGroupGrain < THub > ( _clusterClient , groupName ) )
388+ . Distinct ( )
389+ . ToArray ( ) ;
390+
391+ foreach ( var groupGrain in groupGrains )
392+ {
393+ subscription . AddGrain ( groupGrain ) ;
394+ }
395+
396+ var tasks = groupGrains
397+ . Select ( groupGrain => Task . Run ( ( ) => groupGrain . AddConnection ( connectionId , subscription . Reference ) , cancellationToken ) )
398+ . ToArray ( ) ;
399+
400+ if ( tasks . Length > 0 )
401+ {
402+ await Task . WhenAll ( tasks ) ;
403+ }
377404 }
378405
379406 await UpdateConnectionHeartbeatAsync ( connectionId , subscription ) ;
380407 }
381408
382409 public override async Task RemoveFromGroupAsync ( string connectionId , string groupName ,
383410 CancellationToken cancellationToken = new ( ) )
411+ {
412+ await RemoveFromGroupsAsync ( connectionId , [ groupName ] , cancellationToken ) ;
413+ }
414+
415+ public async Task RemoveFromGroupsAsync ( string connectionId , IReadOnlyList < string > groupNames ,
416+ CancellationToken cancellationToken = default )
384417 {
385418 var subscription = GetSubscription ( connectionId ) ;
386419 if ( subscription is null )
387420 {
388421 return ;
389422 }
390423
424+ var uniqueGroupNames = GetUniqueGroupNames ( groupNames ) ;
425+ if ( uniqueGroupNames . Length == 0 )
426+ {
427+ return ;
428+ }
429+
391430 if ( _orleansSignalOptions . Value . GroupPartitionCount > 1 )
392431 {
393432 var coordinatorGrain = NameHelperGenerator . GetGroupCoordinatorGrain < THub > ( _clusterClient ) ;
394- var partitionId = await Task . Run ( ( ) => coordinatorGrain . GetPartitionForGroup ( groupName ) , cancellationToken ) ;
395- var partitionGrain = NameHelperGenerator . GetGroupPartitionGrain < THub > ( _clusterClient , partitionId ) ;
396- var hubKey = NameHelperGenerator . CleanString ( typeof ( THub ) . FullName ! ) ;
397-
398- await Task . Run ( ( ) => partitionGrain . EnsureInitialized ( hubKey ) , cancellationToken ) ;
399-
400- await Task . Run ( ( ) => coordinatorGrain . RemoveConnectionFromGroup ( groupName , connectionId , subscription . Reference ) , cancellationToken ) ;
433+ var partitionIds = await Task . Run (
434+ ( ) => coordinatorGrain . RemoveConnectionFromGroups ( uniqueGroupNames , connectionId , subscription . Reference ) ,
435+ cancellationToken ) ;
401436
402- var stillTracked = await Task . Run ( ( ) => partitionGrain . HasConnection ( connectionId ) , cancellationToken ) ;
403- if ( ! stillTracked )
437+ foreach ( var partitionId in partitionIds )
404438 {
405- subscription . RemoveGrain ( partitionGrain ) ;
406- await UpdateConnectionHeartbeatAsync ( connectionId , subscription ) ;
439+ var partitionGrain = NameHelperGenerator . GetGroupPartitionGrain < THub > ( _clusterClient , partitionId ) ;
440+ var stillTracked = await Task . Run ( ( ) => partitionGrain . HasConnection ( connectionId ) , cancellationToken ) ;
441+ if ( ! stillTracked )
442+ {
443+ subscription . RemoveGrain ( partitionGrain ) ;
444+ }
407445 }
446+
447+ await UpdateConnectionHeartbeatAsync ( connectionId , subscription ) ;
408448 }
409449 else
410450 {
411- var groupGrain = NameHelperGenerator . GetSignalRGroupGrain < THub > ( _clusterClient , groupName ) ;
412- await Task . Run ( ( ) => groupGrain . RemoveConnection ( connectionId , subscription . Reference ) , cancellationToken ) ;
413- subscription . RemoveGrain ( groupGrain ) ;
451+ var groupGrains = uniqueGroupNames
452+ . Select ( groupName => NameHelperGenerator . GetSignalRGroupGrain < THub > ( _clusterClient , groupName ) )
453+ . Distinct ( )
454+ . ToArray ( ) ;
455+
456+ var tasks = groupGrains
457+ . Select ( groupGrain => Task . Run ( ( ) => groupGrain . RemoveConnection ( connectionId , subscription . Reference ) , cancellationToken ) )
458+ . ToArray ( ) ;
459+
460+ if ( tasks . Length > 0 )
461+ {
462+ await Task . WhenAll ( tasks ) ;
463+ }
464+
465+ foreach ( var groupGrain in groupGrains )
466+ {
467+ subscription . RemoveGrain ( groupGrain ) ;
468+ }
469+
414470 await UpdateConnectionHeartbeatAsync ( connectionId , subscription ) ;
415471 }
416472 }
@@ -643,6 +699,29 @@ private static string GenerateInvocationId()
643699 return connection ? . Features . Get < Subscription > ( ) ;
644700 }
645701
702+ private static string [ ] GetUniqueGroupNames ( IReadOnlyList < string > groupNames )
703+ {
704+ ArgumentNullException . ThrowIfNull ( groupNames ) ;
705+
706+ if ( groupNames . Count == 0 )
707+ {
708+ return [ ] ;
709+ }
710+
711+ var unique = new HashSet < string > ( StringComparer . Ordinal ) ;
712+ var ordered = new List < string > ( groupNames . Count ) ;
713+
714+ foreach ( var groupName in groupNames )
715+ {
716+ if ( unique . Add ( groupName ) )
717+ {
718+ ordered . Add ( groupName ) ;
719+ }
720+ }
721+
722+ return ordered . ToArray ( ) ;
723+ }
724+
646725 private Task UpdateConnectionHeartbeatAsync ( string connectionId , Subscription subscription )
647726 {
648727 if ( ! _orleansSignalOptions . Value . KeepEachConnectionAlive || string . IsNullOrEmpty ( subscription . HubKey ) )
0 commit comments