3030import launch .logging
3131
3232from .opaque_function import OpaqueFunction
33+ from .pop_launch_configurations import PopLaunchConfigurations
34+ from .push_launch_configurations import PushLaunchConfigurations
35+ from .reset_launch_configurations import ResetLaunchConfigurations
3336
3437from ..action import Action
3538from ..event_handler import EventHandler
@@ -54,6 +57,11 @@ class TimerAction(Action):
5457 Action that defers other entities until a period of time has passed, unless canceled.
5558
5659 All timers are "one-shot", in that they only fire one time and never again.
60+
61+ Entities executed after the given period of time can access the launch configurations that
62+ exist at the time that the timer action executed, but changes made by them will not persist.
63+ This is similar to grouping the entities in a :class:`launch.actions.GroupAction` with
64+ ``scoped=True``.
5765 """
5866
5967 def __init__ (
@@ -84,6 +92,7 @@ def __init__(
8492 self .__period = type_utils .normalize_typed_substitution (period , float )
8593 self .__actions = actions
8694 self .__context_locals : Dict [Text , Any ] = {}
95+ self .__context_launch_configuration : Dict [Any , Any ] = {}
8796 self ._completed_future : Optional [asyncio .Future ] = None
8897 self .__canceled = False
8998 self ._canceled_future : Optional [asyncio .Future ] = None
@@ -139,7 +148,14 @@ def describe_conditional_sub_entities(self) -> List[Tuple[
139148 def handle (self , context : LaunchContext ) -> Optional [SomeEntitiesType ]:
140149 """Handle firing of timer."""
141150 context .extend_locals (self .__context_locals )
142- return self .__actions
151+ # Reset the launch configurations to the state they were in when the timer action was
152+ # executed, and make sure to push and pop them so that the changes don't persist and leak
153+ return [
154+ PushLaunchConfigurations (),
155+ ResetLaunchConfigurations (self .__context_launch_configuration ),
156+ * self .__actions ,
157+ PopLaunchConfigurations (),
158+ ]
143159
144160 def cancel (self ) -> None :
145161 """
@@ -189,8 +205,11 @@ def execute(self, context: LaunchContext) -> Optional[List[LaunchDescriptionEnti
189205 ))
190206 setattr (context , '_TimerAction__event_handler_has_been_installed' , True )
191207
192- # Capture the current context locals so the yielded actions can make use of them too.
193- self .__context_locals = dict (context .get_locals_as_dict ()) # Capture a copy
208+ # Capture the current context locals and launch configuration so the yielded actions can
209+ # make use of them too.
210+ # Make sure to capture copies
211+ self .__context_locals = dict (context .get_locals_as_dict ())
212+ self .__context_launch_configuration = context .launch_configurations .copy ()
194213 context .asyncio_loop .create_task (self ._wait_to_fire_event (context ))
195214
196215 # By default, the 'shutdown' event will cause timers to cancel so they don't hold up the
0 commit comments