@@ -388,9 +388,19 @@ impl ActorContext {
388388 }
389389
390390 pub fn sleep ( & self ) -> Result < ( ) > {
391+ // `started` is cleared when the lifecycle state machine transitions
392+ // into SleepGrace / DestroyGrace, so `started=false` covers both
393+ // "never started" and "already shutting down". Distinguish with the
394+ // request flags for an accurate diagnostic.
391395 if !self . 0 . sleep . started . load ( Ordering :: SeqCst ) {
392- return Err ( ActorLifecycleError :: Starting . build ( ) )
393- . context ( "cannot request sleep before actor startup completes" ) ;
396+ let already_stopping = self . 0 . sleep_requested . load ( Ordering :: SeqCst )
397+ || self . 0 . destroy_requested . load ( Ordering :: SeqCst ) ;
398+ return if already_stopping {
399+ Err ( ActorLifecycleError :: Stopping . build ( ) ) . context ( "actor is already shutting down" )
400+ } else {
401+ Err ( ActorLifecycleError :: Starting . build ( ) )
402+ . context ( "cannot request sleep before actor startup completes" )
403+ } ;
394404 }
395405 if self . 0 . sleep_requested . swap ( true , Ordering :: SeqCst ) {
396406 return Err ( ActorLifecycleError :: Stopping . build ( ) )
@@ -415,7 +425,13 @@ impl ActorContext {
415425 }
416426
417427 pub fn destroy ( & self ) -> Result < ( ) > {
418- if !self . 0 . sleep . started . load ( Ordering :: SeqCst ) {
428+ // See `sleep` for why the request flags disambiguate `started=false`.
429+ // destroy() is allowed after sleep() has been requested because
430+ // destroy is a stronger signal that escalates an in-flight sleep.
431+ if !self . 0 . sleep . started . load ( Ordering :: SeqCst )
432+ && !self . 0 . sleep_requested . load ( Ordering :: SeqCst )
433+ && !self . 0 . destroy_requested . load ( Ordering :: SeqCst )
434+ {
419435 return Err ( ActorLifecycleError :: Starting . build ( ) )
420436 . context ( "cannot request destroy before actor startup completes" ) ;
421437 }
0 commit comments