-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[OTHER] Taipy unable to assign a state variable, apparent leak between clients? #2441
Comments
Hi, I've been able to isolate the error and produce a minimum example that (mostly) reproduces the error. Running this example causes warnings to appear in the terminal, but the dashboard still functions okay. These warnings are nearly identical to the issue above, and the current (temporary) workaround to resolve these warnings has also resolved the issue above. Minimum working exampleTaipy version: 4.0.2 import taipy.gui.builder as tgb
import taipy.gui as tpg
client_handle = None
ui_elements_data = {} # In the real application, this is a dictionary-like object with the workaround implemented in its __getitem__ method.
class ClientUiElementStruct:
"""Very simplified example"""
TOGGLE_LOV = ['item1', 'item2']
def __init__(self):
self.toggle_value = 'item1'
def __repr__(self):
return f"{self.__class__.__name__}(toggle_value={self.toggle_value!r})"
def refresh_partial(state):
"""Only create content for the client if a client name is set. This implies that the necessary datastructure is also available"""
if cname := state.client_handle:
with tgb.Page() as client_part:
with tgb.part("card"):
tgb.text("The client name is: " + cname)
tgb.toggle(
value="{" + f"ui_elements_data['{cname}']" + ".toggle_value}",
lov="{" + f"ui_elements_data['{cname}']" + ".TOGGLE_LOV}",
on_change=callback_toggle
)
tgb.text("The active selected toggle value is: {ui_elements_data['" + cname + "'].toggle_value}")
tgb.text("The complete content of state.ui_elements_data: {str(dict(ui_elements_data))}")
state.client_partial.update_content(state, client_part)
def callback_toggle(state, var_name, value):
state.ui_elements_data[state.client_handle].toggle_value = value
state.refresh('ui_elements_data')
# Initialize the application state
def on_init(state):
refresh_partial(state)
def on_navigate(state: tpg.State, page_name: str, params: dict = {}) -> str:
"""Extract the client name from the query parameters and update the state"""
# If a client parameter is provided, update client-specific settings
if handle := params.get("client", False):
if len(handle) > 0:
print(f"Client name query parameter provided: {handle}")
# Theoretically, state variable 'ui_elements_data' should be unique for each client (empty for a new client)
# (as per documentation: https://docs.taipy.io/en/latest/refmans/reference/pkg_taipy/pkg_gui/State/)
# However, since this (for some reason) is not the case, we manually set any previous client data to 'None'. This simulates the behavior of the actual application. (Alternative: remove the entries, but this causes issues)
# For context: a new client connecting to the dashboard will ALWAYS have the query parameter 'client' set
previous_handles = [k for k in state.ui_elements_data.keys() if k != handle]
if len(previous_handles) > 0:
for k in previous_handles:
state.ui_elements_data[k] = None
# state.ui_elements_data.pop(k) # Alternative: Removing the entries also causes issues
state.client_handle = handle
state.ui_elements_data[handle] = ClientUiElementStruct()
state.refresh('ui_elements_data') # Just to be sure that the ui_elements_data is updated
refresh_partial(state)
tpg.navigate(state, to="/", tab="_self") # force navigation to the root page without query parameters. Prevents endless loop
return page_name
if __name__ == "__main__":
# Define the main page layout
with tgb.Page() as main_page:
tgb.text("Multi-Client DEBUG Dashboard", class_name="h1")
tgb.part(partial="{client_partial}")
pages = {"/": main_page}
# Create and run the Taipy GUI
gui = tpg.Gui(main_page)
client_partial = gui.add_partial("")
gui.run() Steps to reproduceNote: ensure the query parameter
.venv/lib/python3.12/site-packages/taipy/gui/utils/_evaluator.py:372: TaipyGuiWarning: Exception raised evaluating dataset['first'].toggle_value:
Traceback (most recent call last):
File ".venv/lib/python3.12/site-packages/taipy/gui/utils/_evaluator.py", line 369, in re_evaluate_expr
expr_evaluated = eval(expr_string, ctx)
^^^^^^^^^^^^^^^^^^^^^^
File "<string>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'toggle_value'
_warn(f"Exception raised evaluating {expr_string}", e)
... Issue Descriptions:1) State variable shared across clientsEach time a new client connects to the dashboard and provides the query parameter In theory, this state variable should be unique to each client. To track this, its contents are displayed in the partial. The expected value is a dictonary with one key-value pair. For the second client, this should be In practice, once the second client has connected, the state variable used by the second client contains BOTH the first client's handle and the second client's handle: Maybe I misunderstand how this should work, but the way I read the documentation, each connection (client) should hold it's own independent set of state variables. 2) Taipy backend - trying to access attribute of NoneType object, for binding of different clientTo further simulate the behavior of the actual application, any unwanted 'shared' variables are set to The Taipy partial only updates content if the Two types of errors can be observed:
Suggested fixes
The cause is the same: the state variable which contains an attribute that is bound to a UI element is either missing or replaced. Thus, a similar try-except error handling should be applied here. This would also effectively 'unbind' the UI connection, without raising an execption that reaches the highest level and prevents any other bindings from being updated. Current workaroundFor now, I've ensured that the dict-like data structure Of course this only works for this case and is not scalable for use with other data structures. I hope this gives you more insights into the issue. Thanks! |
Thanks a lot for the really detailed issue and description |
The doc might want to be more precise on the variable state description:
def on_init(state):
state.ui_elements_data = {} |
In your case, would it be a good idea to instantiate the |
I tried the following modifications: ...
client_handle = None
ui_elements_data = None # Must be declared, otherwise error in on_init(): AttributeError: Variable 'ui_elements_data' is not accessible.
...
# Initialize the application state
def on_init(state):
state.ui_elements_data = {}
refresh_partial(state)
... The dashboard works fine for the first client, but then throws an error when loading the second client: .venv/lib/python3.12/site-packages/taipy/gui/utils/_evaluator.py:372: TaipyGuiWarning: Exception raised evaluating ui_elements_data['first'].toggle_value:
Traceback (most recent call last):
File ".venv/lib/python3.12/site-packages/taipy/gui/utils/_evaluator.py", line 369, in re_evaluate_expr
expr_evaluated = eval(expr_string, ctx)
^^^^^^^^^^^^^^^^^^^^^^
File "<string>", line 1, in <module>
File ".venv/lib/python3.12/site-packages/taipy/gui/utils/_map_dict.py", line 37, in __getitem__
value = self._dict.__getitem__(key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyError: 'first'
... I'm not sure what you mean by this exactly? |
The technical explanation is that:
|
do you really need your client_handle ? |
Yes, For example, based on the config table:
The database is dynamic, so all the possible combinations cannot be known before the client connects |
I think you should avoid this |
To further clarify: in the actual application, just as you describe, the state variable is only the ClientUIElementStruct. There is no separate dictionary in which the Referring back to the original issue: (where
NOTE: the specific
In the To clarify, the overall control logic of a new client connecting is as follows: graph TD
A[New client connects<br>with client_handle in query params] -->| Taipy calls | C[on_navigate]
C -->|Extract client_handle query parameter| D[Start client setup<br>to initialize pages]
D --> J[Setup database connection]
J --> L[Get client pages config from SQL]
L --> M[Initialize pages: <br>run on_init of each page]
M --> |Instantiate TreeRoot object| N[Save<br>state.risk_tree = TreeRoot]
N --> |Start Long-Running callback| X[Retrieve data + create figures]
N --> Y
X -- LR Callback complete -->V[Update state.risk_tree object with new data + figures]
V --> Y[Update partials]
Y --> O{Check for failed pages}
O -->|Success| E[Navigate user to dashboard]
O -->|Failures| P[Remove failed pages]
P --> E
Note: When the |
Thanks for the clarification. |
the naming could be indeed a problem as taipy looks for |
I think that your problem here is that with such an expression your creating a huge dependency network between your variables. for each toggle you create, the value property is bound to Can you share your definition of the |
If you use the dot notation for dict access your values will be changed without needing a callback. PS: I know this will not solve your issue but it might help |
An example that outlines how
The output below the code follows a sequence of events A, B and C. The app works as expected and both clients appear to have independent But, notice the print outs for |
|
Hi Fred,
I changed the naming to |
I'd like to clarify that there are two separate issues here. Just to make sure we're not confusing them with each other:
The replication code I provided was to demonstrate both issues. Issue 1 is something I observed during debugging and was inconsistent with my understanding of the state variable as described in the docs. Specifically, as demonstrated in the replication code:
If you say this behavior is expected, then please regard this issue as resolved. Issue 2 has been temporarily resolved via the workaround. The workaround ensures that if the key cannot be found, it returns an empty object instead of raising a Nonetheless, I believe a more robust solution would be if some kind of error-handling could be built into Taipy. That would enable the page to continue to load even if certain elements could not be found. The replication code only raises a warning, which demonstrates that the error is correctly captured in a try-except statement (see However, in the production code, the equivalent error is not caught by
Alternatively: would it make sense to add a feature/functionality that enables GUI bindings to be "disconnected" when no longer needed? Similar to how UI elements are created inside a partial, but then the opposite? Just thinking out loud... |
What went wrong? 🤔
I've been stuck on a really critical bug for the past week. It's preventing pages from loading, and the only current workaround is restarting the server. It occurs (seemingly) random, even with automated tests.
The issue arises a nested dict-like data structure object is saved to a state variable. This object is used to save datasets, figures, values, etc. The size of this data structure is dynamic and is different for each client session, defined by a query parameter in the URL called
client_handle
(important). The data structure is constructed duringon_init()
of the page without any issue, and then assigned to a state variable in a simple line:state.risk_tree = tree
which causes the (occasional) error.The first client to connect to the server (since server startup) always initializes without a problem. Subsequent clients can have the issue, if they use a different
client_handle
. For example, assuming each client connects from an isolated browser and creates its own Taipy session:client_handle = vm_groningen_dev
(initialization OK)client_handle = vm_gelderland_dev
(initialization OK)client_handle = vm_limburg_dev
(ERROR)The error raised by during the initialization of connection #3 (
vm_limburg_dev
) is:This long attribute id refers to the key which is used to retrieve an element from the data structure (in this case an LOV).
Note: in the middle of the attribute id is a part
vm_gelderland_dev
(theclient_handle
). The currently connected client isvm_limburg_dev
. This indicates Taipy is trying to bind a callback from another client session, which it obviously cannot find in this session.Expected Behavior
The state variable
state.risk_tree
should be set. 9/10 times this works without a problem.Steps to Reproduce Issue
The biggest difficulty is that this bug is not consistent. 9/10 times it works fine, even when I reproduce client page-visit combinations that previously caused an error. So the only way to debug this is by inspecting the logs. I realize this is very little to go off, but since I can't even reliably reproduce the error, creating a minimum example is pretty much impossible.
See isolated_error.log, which also contains the variables. The actions to produce this:
client_handle = vm_gelderland_dev
initializes without an issue.client_handle = vm_limburg_dev
fails to initialize.client_handle
of the previous clientThe issue occurs in the page
on_init()
. Constructing the object works without an issue. Saving the object to a state variable causes the issue.After the Long-Running Callback is complete, a Taipy Partial generates the content of the page. As seen below, the GUI elements reference attributes inside the data structure.
Runtime Environment
Docker Container: python:3.12-slim-bookworm
Browsers
Chrome, Firefox, Safari
Version of Taipy
4.0.2
Additional Context
Acceptance Criteria
Code of Conduct
The text was updated successfully, but these errors were encountered: