Proxy of a state instance to control mutability of vars for a background task. Since a background task runs against a state instance without holding the state_manager lock for the token, the reference may become stale if the same state is modified by another event handler. The prox
| 1294 | |
| 1295 | |
| 1296 | class StateProxy(wrapt.ObjectProxy): |
| 1297 | """Proxy of a state instance to control mutability of vars for a background task. |
| 1298 | |
| 1299 | Since a background task runs against a state instance without holding the |
| 1300 | state_manager lock for the token, the reference may become stale if the same |
| 1301 | state is modified by another event handler. |
| 1302 | |
| 1303 | The proxy object ensures that writes to the state are blocked unless |
| 1304 | explicitly entering a context which refreshes the state from state_manager |
| 1305 | and holds the lock for the token until exiting the context. After exiting |
| 1306 | the context, a StateUpdate may be emitted to the frontend to notify the |
| 1307 | client of the state change. |
| 1308 | |
| 1309 | A background task will be passed the `StateProxy` as `self`, so mutability |
| 1310 | can be safely performed inside an `async with self` block. |
| 1311 | |
| 1312 | class State(xt.State): |
| 1313 | counter: int = 0 |
| 1314 | |
| 1315 | @xt.background |
| 1316 | async def bg_increment(self): |
| 1317 | await asyncio.sleep(1) |
| 1318 | async with self: |
| 1319 | self.counter += 1 |
| 1320 | """ |
| 1321 | |
| 1322 | def __init__(self, state_instance): |
| 1323 | """Create a proxy for a state instance. |
| 1324 | |
| 1325 | Args: |
| 1326 | state_instance: The state instance to proxy. |
| 1327 | """ |
| 1328 | super().__init__(state_instance) |
| 1329 | # compile is not relevant to backend logic |
| 1330 | #TODO: We're currently using this weirdo mechanism for installing initial packages |
| 1331 | # gets to load_module -> compile -> get_frontend_packages -> install_frontend_packages |
| 1332 | # We can improve this |
| 1333 | self._self_app = getattr(prerequisites.get_app(), constants.CompileVars.APP) |
| 1334 | self._self_substate_path = state_instance.get_full_name().split(".") |
| 1335 | self._self_actx = None |
| 1336 | self._self_mutable = False |
| 1337 | |
| 1338 | async def __aenter__(self) -> StateProxy: |
| 1339 | """Enter the async context manager protocol. |
| 1340 | |
| 1341 | Sets mutability to True and enters the `App.modify_state` async context, |
| 1342 | which refreshes the state from state_manager and holds the lock for the |
| 1343 | given state token until exiting the context. |
| 1344 | |
| 1345 | Background tasks should avoid blocking calls while inside the context. |
| 1346 | |
| 1347 | Returns: |
| 1348 | This StateProxy instance in mutable mode. |
| 1349 | """ |
| 1350 | self._self_actx = self._self_app.modify_state( |
| 1351 | self.__wrapped__.router.session.client_token |
| 1352 | ) |
| 1353 | mutable_state = await self._self_actx.__aenter__() |
no outgoing calls