@@ -517,112 +517,34 @@ private void CancelPendingOperations()
517517 }
518518
519519 public void Migrate ( Dictionary < string , object > ? requestContext , CancellationToken cancellationToken = default )
520- {
521- var cts = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
522- cts . CancelAfter ( _shared . InternalRuntime . CollectionOptions . Value . DeactivationTimeout ) ;
523-
524- if ( Equals ( RuntimeContext . Current ) && State is ActivationState . Deactivating )
525- {
526- // The grain is executing and is already deactivating, so just set the migration context and return.
527- StartMigratingCore ( requestContext , null ) ;
528- }
529- else
530- {
531- // We use a named work item since it is cheaper than allocating a Task and has the benefit of being named.
532- _workItemGroup . QueueWorkItem ( new MigrateWorkItem ( this , requestContext , cts ) ) ;
533- }
534- }
535-
536- private async Task StartMigratingAsync ( Dictionary < string , object > ? requestContext , CancellationTokenSource cts )
537520 {
538521 lock ( this )
539522 {
540523 if ( State is not ( ActivationState . Activating or ActivationState . Valid or ActivationState . Deactivating ) )
541524 {
542525 return ;
543526 }
544- }
545-
546- try
547- {
548- var newLocation = await PlaceMigratingGrainAsync ( requestContext , cts . Token ) ;
549- if ( newLocation is null )
550- {
551- // Will not deactivate/migrate.
552- return ;
553- }
554-
555- lock ( this )
556- {
557- if ( ! DeactivateCore ( new DeactivationReason ( DeactivationReasonCode . Migrating , "Migrating to a new location." ) , cts . Token ) )
558- {
559- // Grain is not able to start deactivating or has already completed.
560- return ;
561- }
562-
563- StartMigratingCore ( requestContext , newLocation ) ;
564- }
565-
566- LogDebugMigrating ( _shared . Logger , GrainId , newLocation ) ;
567- }
568- catch ( Exception exception )
569- {
570- LogErrorSelectingMigrationDestination ( _shared . Logger , exception , GrainId ) ;
571- return ;
572- }
573- }
574527
575- private void StartMigratingCore ( Dictionary < string , object > ? requestContext , SiloAddress ? newLocation )
576- {
577- if ( DehydrationContext is not null )
578- {
579- // Migration has already started.
580- return ;
581- }
528+ // If migration has not already been started, set a migration context to capture any state which should be transferred.
529+ // Doing this signals to the deactivation process that a migration is occurring, so it is important that this happens before we begin deactivation.
530+ DehydrationContext ??= new ( _shared . SerializerSessionPool , requestContext ) ;
582531
583- // Set a migration context to capture any state which should be transferred.
584- // Doing this signals to the deactivation process that a migration is occurring, so it is important that this happens before we begin deactivation.
585- DehydrationContext = new ( _shared . SerializerSessionPool , requestContext ) ;
586- ForwardingAddress = newLocation ;
587- }
588-
589- private async ValueTask < SiloAddress ? > PlaceMigratingGrainAsync ( Dictionary < string , object > ? requestContext , CancellationToken cancellationToken )
590- {
591- var placementService = _shared . Runtime . ServiceProvider . GetRequiredService < PlacementService > ( ) ;
592- var newLocation = await placementService . PlaceGrainAsync ( GrainId , requestContext , PlacementStrategy ) . WaitAsync ( cancellationToken ) ;
593-
594- // If a new (different) host is not selected, do not migrate.
595- if ( newLocation == Address . SiloAddress || newLocation is null )
596- {
597- // No more appropriate silo was selected for this grain. The migration attempt will be aborted.
598- // This could be because this is the only (compatible) silo for the grain or because the placement director chose this
599- // silo for some other reason.
600- if ( newLocation is null )
601- {
602- LogDebugPlacementStrategyFailedToSelectDestination ( _shared . Logger , PlacementStrategy , GrainId ) ;
603- }
604- else
532+ if ( State is not ActivationState . Deactivating )
605533 {
606- LogDebugPlacementStrategySelectedCurrentSilo ( _shared . Logger , PlacementStrategy , GrainId ) ;
534+ // Start deactivating the grain to prepare for migration.
535+ Deactivate ( new DeactivationReason ( DeactivationReasonCode . Migrating , "Migrating to a new location." ) , cancellationToken ) ;
607536 }
608-
609- // Will not migrate.
610- return null ;
611537 }
612-
613- return newLocation ;
614538 }
615539
616- public void Deactivate ( DeactivationReason reason , CancellationToken cancellationToken = default ) => DeactivateCore ( reason , cancellationToken ) ;
617-
618- public bool DeactivateCore ( DeactivationReason reason , CancellationToken cancellationToken )
540+ public void Deactivate ( DeactivationReason reason , CancellationToken cancellationToken = default )
619541 {
620542 lock ( this )
621543 {
622544 var state = State ;
623545 if ( state is ActivationState . Invalid )
624546 {
625- return false ;
547+ return ;
626548 }
627549
628550 if ( DeactivationReason . ReasonCode == DeactivationReasonCode . None )
@@ -646,8 +568,6 @@ public bool DeactivateCore(DeactivationReason reason, CancellationToken cancella
646568 ScheduleOperation ( new Command . Deactivate ( cts , state ) ) ;
647569 }
648570 }
649-
650- return true ;
651571 }
652572
653573 private void DeactivateStuckActivation ( )
@@ -1709,7 +1629,7 @@ private async Task ActivateAsync(Dictionary<string, object>? requestContextData,
17091629 /// </summary>
17101630 private async Task FinishDeactivating ( ActivationState previousState , CancellationToken cancellationToken )
17111631 {
1712- var migrated = false ;
1632+ var migrating = false ;
17131633 var encounteredError = false ;
17141634 try
17151635 {
@@ -1763,41 +1683,14 @@ private async Task FinishDeactivating(ActivationState previousState, Cancellatio
17631683 && _shared . MigrationManager is { } migrationManager
17641684 && ! cancellationToken . IsCancellationRequested )
17651685 {
1766- try
1767- {
1768- ForwardingAddress ??= await PlaceMigratingGrainAsync ( context . RequestContext , cancellationToken ) ;
1769-
1770- if ( ForwardingAddress is { } forwardingAddress )
1771- {
1772- // Populate the dehydration context.
1773- if ( context . RequestContext is { } requestContext )
1774- {
1775- RequestContextExtensions . Import ( requestContext ) ;
1776- }
1777-
1778- OnDehydrate ( context . MigrationContext ) ;
1779-
1780- // Send the dehydration context to the target host.
1781- await migrationManager . MigrateAsync ( forwardingAddress , GrainId , context . MigrationContext ) . AsTask ( ) . WaitAsync ( cancellationToken ) ;
1782- _shared . InternalRuntime . GrainLocator . UpdateCache ( GrainId , forwardingAddress ) ;
1783- migrated = true ;
1784- }
1785- }
1786- catch ( Exception exception )
1787- {
1788- LogFailedToMigrateActivation ( _shared . Logger , exception , this ) ;
1789- }
1790- finally
1791- {
1792- RequestContext . Clear ( ) ;
1793- }
1686+ migrating = await StartMigrationAsync ( context , migrationManager , cancellationToken ) ;
17941687 }
17951688
17961689 // If the instance is being deactivated due to a directory failure, we should not unregister it.
17971690 var isDirectoryFailure = DeactivationReason . ReasonCode is DeactivationReasonCode . DirectoryFailure ;
17981691 var isShuttingDown = DeactivationReason . ReasonCode is DeactivationReasonCode . ShuttingDown ;
17991692
1800- if ( ! migrated && IsUsingGrainDirectory && ! cancellationToken . IsCancellationRequested && ! isDirectoryFailure && ! isShuttingDown )
1693+ if ( ! migrating && IsUsingGrainDirectory && ! cancellationToken . IsCancellationRequested && ! isDirectoryFailure && ! isShuttingDown )
18011694 {
18021695 // Unregister from directory.
18031696 // If the grain was migrated, the new activation will perform a check-and-set on the registration itself.
@@ -1828,7 +1721,7 @@ private async Task FinishDeactivating(ActivationState previousState, Cancellatio
18281721 {
18291722 CatalogInstruments . ActivationShutdownViaDeactivateStuckActivation ( ) ;
18301723 }
1831- else if ( migrated )
1724+ else if ( migrating )
18321725 {
18331726 CatalogInstruments . ActivationShutdownViaMigration ( ) ;
18341727 }
@@ -1855,6 +1748,41 @@ private async Task FinishDeactivating(ActivationState previousState, Cancellatio
18551748 // Signal deactivation
18561749 GetDeactivationCompletionSource ( ) . TrySetResult ( true ) ;
18571750 _workSignal . Signal ( ) ;
1751+
1752+ async ValueTask < bool > StartMigrationAsync ( DehydrationContextHolder context , IActivationMigrationManager migrationManager , CancellationToken cancellationToken )
1753+ {
1754+ try
1755+ {
1756+ if ( ForwardingAddress is null )
1757+ {
1758+ var selectedAddress = await PlaceMigratingGrainAsync ( context . RequestContext , cancellationToken ) ;
1759+ if ( selectedAddress is null )
1760+ {
1761+ return false ;
1762+ }
1763+
1764+ ForwardingAddress = selectedAddress ;
1765+ }
1766+
1767+ // Populate the dehydration context.
1768+ if ( context . RequestContext is { } requestContext )
1769+ {
1770+ RequestContextExtensions . Import ( requestContext ) ;
1771+ }
1772+
1773+ OnDehydrate ( context . MigrationContext ) ;
1774+
1775+ // Send the dehydration context to the target host.
1776+ await migrationManager . MigrateAsync ( ForwardingAddress , GrainId , context . MigrationContext ) . AsTask ( ) . WaitAsync ( cancellationToken ) ;
1777+ _shared . InternalRuntime . GrainLocator . UpdateCache ( GrainId , ForwardingAddress ) ;
1778+ return true ;
1779+ }
1780+ catch ( Exception exception )
1781+ {
1782+ LogFailedToMigrateActivation ( _shared . Logger , exception , this ) ;
1783+ return false ;
1784+ }
1785+ }
18581786 }
18591787
18601788 private TaskCompletionSource < bool > GetDeactivationCompletionSource ( )
@@ -1872,10 +1800,49 @@ ValueTask IGrainManagementExtension.DeactivateOnIdle()
18721800 return default ;
18731801 }
18741802
1875- ValueTask IGrainManagementExtension . MigrateOnIdle ( )
1803+ async ValueTask IGrainManagementExtension . MigrateOnIdle ( )
18761804 {
1877- Migrate ( RequestContext . CallContextData ? . Value . Values , CancellationToken . None ) ;
1878- return default ;
1805+ var requestContextData = RequestContext . CallContextData ? . Value . Values ;
1806+ var selectedAddress = await PlaceMigratingGrainAsync ( requestContextData , CancellationToken . None ) ;
1807+ if ( selectedAddress is null )
1808+ {
1809+ return ;
1810+ }
1811+
1812+ // Only migrate if a different silo was selected.
1813+ ForwardingAddress = selectedAddress ;
1814+ LogDebugMigrating ( _shared . Logger , GrainId , ForwardingAddress ) ;
1815+ Migrate ( requestContextData , cancellationToken : CancellationToken . None ) ;
1816+ }
1817+
1818+ private async ValueTask < SiloAddress ? > PlaceMigratingGrainAsync ( Dictionary < string , object > ? requestContextData , CancellationToken cancellationToken )
1819+ {
1820+ try
1821+ {
1822+ var placementService = _shared . Runtime . ServiceProvider . GetRequiredService < PlacementService > ( ) ;
1823+ var selectedAddress = await placementService . PlaceGrainAsync ( GrainId , requestContextData , PlacementStrategy ) ;
1824+
1825+ if ( selectedAddress is null )
1826+ {
1827+ // No appropriate silo was selected for this grain.
1828+ LogDebugPlacementStrategyFailedToSelectDestination ( _shared . Logger , PlacementStrategy , GrainId ) ;
1829+ return null ;
1830+ }
1831+ else if ( selectedAddress == _shared . Runtime . SiloAddress )
1832+ {
1833+ // This could be because this is the only (compatible) silo for the grain or because the placement director chose this
1834+ // silo for some other reason.
1835+ LogDebugPlacementStrategySelectedCurrentSilo ( _shared . Logger , PlacementStrategy , GrainId ) ;
1836+ return null ;
1837+ }
1838+
1839+ return selectedAddress ;
1840+ }
1841+ catch ( Exception exception )
1842+ {
1843+ LogErrorSelectingMigrationDestination ( _shared . Logger , exception , GrainId ) ;
1844+ return null ;
1845+ }
18791846 }
18801847
18811848 private void UnregisterMessageTarget ( )
@@ -2202,15 +2169,6 @@ private class DehydrationContextHolder(SerializerSessionPool sessionPool, Dictio
22022169 public readonly Dictionary < string , object > ? RequestContext = requestContext ;
22032170 }
22042171
2205- private class MigrateWorkItem ( ActivationData activation , Dictionary < string , object > ? requestContext , CancellationTokenSource cts ) : WorkItemBase
2206- {
2207- public override string Name => "Migrate" ;
2208-
2209- public override IGrainContext GrainContext => activation ;
2210-
2211- public override void Execute ( ) => activation . StartMigratingAsync ( requestContext , cts ) . Ignore ( ) ;
2212- }
2213-
22142172 [ LoggerMessage (
22152173 EventId = ( int ) ErrorCode . Catalog_Reject_ActivationTooManyRequests ,
22162174 Level = LogLevel . Warning ,
0 commit comments