Skip to content
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

Session state yield points #7373

Merged

Conversation

AnOctopus
Copy link
Collaborator

@AnOctopus AnOctopus commented Sep 19, 2023

Describe your changes

Replace SafeSessionState disconnect with script runner yield points, to achieve the same safety with slightly less complexity, shorter runtimes, and fewer spurious errors logged.

Testing Plan

e2e test added, unit tests updated


Contribution License Agreement

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.

@AnOctopus AnOctopus marked this pull request as ready for review October 3, 2023 19:51
@AnOctopus AnOctopus changed the title Prototype: Session state yield points Session state yield points Oct 5, 2023
@kmcgrady kmcgrady self-requested a review October 5, 2023 18:31
@kmcgrady kmcgrady self-assigned this Oct 5, 2023
Copy link
Collaborator

@kmcgrady kmcgrady left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm understanding this correctly.

Currently, we are disconnecting the session state from the script runner when a stop request occurs. This is to avoid session state being populated with new information after the stop request comes in.

Instead, we are doing the following:

  1. Allow any changes to Session State to have the opportunity to consider changes in execution (stop or rerun). (I have a question on Read operations, but I imagine it's just to shutdown faster).
  2. There's a change on the script finish to not have session state handle a script finish event if the script finish had an intervention (error or Streamlit Stop/Rerun event). I'm having trouble understanding how this connects to the PR description. Can you elaborate more?

) -> None:
"""Called when our script finishes executing, even if it finished
early with an exception. We perform post-run cleanup here.
"""
# Tell session_state to update itself in response
self._session_state.on_script_finished(ctx.widget_ids_this_run)
if not premature_stop:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are handling this for StopException/Rerun Exception to indicate the use of st.stop/rerun, don't we want to update the session state and handle it?

I may be perhaps misunderstanding the value of ignoring the change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, if a stop/rerun comes in and fast reruns is enabled (which is ~always the case, very very few apps turn it off), on_script_finished will be a no-op for the disconnected session state. This prevents widgets that were going to be rendered, but didn't because it was interrupted, from losing their state. To retain this behavior, since it no longer happens via the disconnect mechanism, we have to conditionally avoid the session state cleanup step via the script runner instead.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me walk through an example and make sure I understand.

Let's look at the following script:

import streamlit as st

st.button("misdirection")

if st.button("belly"):
    st.write("Here's an issue")
    st.stop()

st.write("This may not get written if the button is pressed")

When we run it, no major issue. When we click the button, we do the following:

  1. The button's value should be set to true (trigger)
  2. The script executes. The button flow runs and st.stop() is called.
  3. We request to stop
  4. (This part may be a change from my original understanding) That eventually raises an Exception, specifically a StopException, right?
  5. The script finishes and calls on_script_finished.
  6. It should call session state on script finished because we want to reset the button trigger? Or to remove the stale widgets (the example doesn't provide this scenario)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1-4 is still correct.
5. For the code on develop, if fast reruns is enabled, on_script_finished runs but does nothing (immediate return if disconnected). For the PR code, on_script_finished is instead not called at all.
6. The button trigger gets reset at the beginning of the next script run, due to a change I made recently for a bug fix. Possibly we should remove the trigger reset call from on_script_finished, to simplify things.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. This makes more sense now. Would love if we can try to simplify the flow for understanding.

del self._state[key]

def __contains__(self, key: str) -> bool:
self._yield_callback()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need it in the read operations? Or are we just using this as an opportunity to end faster?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just using it to end faster

Copy link
Collaborator

@kmcgrady kmcgrady left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine to me. Thanks for the clarification comments! There may be value in a flow chart or something for Session State as it ties to a bunch of things.

) -> None:
"""Called when our script finishes executing, even if it finished
early with an exception. We perform post-run cleanup here.
"""
# Tell session_state to update itself in response
self._session_state.on_script_finished(ctx.widget_ids_this_run)
if not premature_stop:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. This makes more sense now. Would love if we can try to simplify the flow for understanding.

@AnOctopus AnOctopus merged commit 512742c into streamlit:develop Oct 6, 2023
49 checks passed
@AnOctopus AnOctopus deleted the feat/session-state-yield-points branch October 6, 2023 18:04
eric-skydio pushed a commit to eric-skydio/streamlit that referenced this pull request Dec 20, 2023
Replace SafeSessionState's disconnect mechanism with additional yield points, to potentially end script runs faster and avoid spurious errors in logs, and slightly simplify the implementation.
zyxue pushed a commit to zyxue/streamlit that referenced this pull request Mar 22, 2024
Replace SafeSessionState's disconnect mechanism with additional yield points, to potentially end script runs faster and avoid spurious errors in logs, and slightly simplify the implementation.
zyxue pushed a commit to zyxue/streamlit that referenced this pull request Apr 16, 2024
Replace SafeSessionState's disconnect mechanism with additional yield points, to potentially end script runs faster and avoid spurious errors in logs, and slightly simplify the implementation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants