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

pn.state.on_session_destroyed() never called #3601

Open
CmpCtrl opened this issue Jun 9, 2022 · 5 comments
Open

pn.state.on_session_destroyed() never called #3601

CmpCtrl opened this issue Jun 9, 2022 · 5 comments
Labels
type: docs Related to the Panel documentation and examples

Comments

@CmpCtrl
Copy link

CmpCtrl commented Jun 9, 2022

ALL software version info

python 3.10
panel 0.13.1

Description of expected behavior and the observed behavior

I havent been able to get the pn.state.on_session_destroyed() callback to work, pn.state.on_session_created() works as expected.

Perhaps you can suggest a better approach for my usage case. I'm building an app that will be served locally for one user, and i want to be able to stop the server when the session is destroyed.

Complete, minimal, self-contained example code that reproduces the issue

import logging

import panel as pn

logging.basicConfig(
    format="%(asctime)s.%(msecs)03d %(levelname)s {%(module)s} [%(funcName)s] %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    level=logging.DEBUG,
)


def destroyed(session_context):
    print("destroyed")


def created(session_context):
    print("created")


pn.state.on_session_destroyed(destroyed)
pn.state.on_session_created(created)


def main_app():
    return pn.Row("Test App")


app = pn.serve(
    {"Demo": main_app},
    port=8080,
    title="Demo",
    show=True,
    start=True,
    autoreload=False,
)

Stack traceback and/or browser JavaScript console output

You can see that "created" was printed out as expected when the session was created, but when the session is destroyed 28s after the connection was closed, it never prints "destroyed"

Launching server at http://localhost:8080
2022-06-09 16:09:33.315 INFO {web} [log_request] 302 GET / (::1) 4.20ms
created
2022-06-09 16:09:33.518 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.fenced_code.FencedCodeExtension".
2022-06-09 16:09:33.524 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.footnotes.FootnoteExtension".
2022-06-09 16:09:33.525 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.attr_list.AttrListExtension".
2022-06-09 16:09:33.530 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.def_list.DefListExtension".
2022-06-09 16:09:33.536 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.tables.TableExtension".
2022-06-09 16:09:33.542 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.abbr.AbbrExtension".
2022-06-09 16:09:33.545 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.md_in_html.MarkdownInHtmlExtension".
2022-06-09 16:09:33.564 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.smarty.SmartyExtension".
2022-06-09 16:09:33.565 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.codehilite.CodeHiliteExtension".
2022-06-09 16:09:33.669 INFO {web} [log_request] 200 GET /Demo (::1) 350.14ms
DEBUG:bokeh.server.views.ws:Subprotocol header received
2022-06-09 16:09:33.944 INFO {web} [log_request] 101 GET /Demo/ws (::1) 2.00ms
INFO:bokeh.server.views.ws:WebSocket connection opened
DEBUG:bokeh.server.views.ws:Receiver created for Protocol()
DEBUG:bokeh.server.views.ws:ProtocolHandler created for Protocol()
INFO:bokeh.server.views.ws:ServerConnection created
DEBUG:bokeh.server.session:Sending pull-doc-reply from session 'w3rkJ6jEiFBNrmB56cOS1bmFa34Ce5TBfnsXTvsvuXA0'
INFO:bokeh.server.views.ws:WebSocket connection closed: code=1001, reason=None
DEBUG:bokeh.server.tornado:[pid 32012] 0 clients connected
DEBUG:bokeh.server.tornado:[pid 32012]   /Demo has 1 sessions with 1 unused
DEBUG:bokeh.server.tornado:[pid 32012] 0 clients connected
DEBUG:bokeh.server.tornado:[pid 32012]   /Demo has 1 sessions with 1 unused
DEBUG:bokeh.server.contexts:Scheduling 1 sessions to discard
DEBUG:bokeh.server.contexts:Discarding session 'w3rkJ6jEiFBNrmB56cOS1bmFa34Ce5TBfnsXTvsvuXA0' last in use 28422.0 milliseconds ago
DEBUG:bokeh.document.modules:Deleting 0 modules for document <bokeh.document.document.Document object at 0x0000021E42D71B40>
DEBUG:bokeh.server.tornado:[pid 32012] 0 clients connected
DEBUG:bokeh.server.tornado:[pid 32012]   /Demo has 0 sessions with 0 unused
@philippjfr
Copy link
Member

philippjfr commented Jun 10, 2022

This needs to be documented better but pn.state reflects the current session being served. This means that if you access it outside of any session it will not correctly register things with a particular session. So you have to change it to:

def main_app():
    pn.state.on_session_destroyed(destroyed)
    return pn.Row("Test App")

because inside the main_app you are in the session creation context. Alternatively you could also do this:

def created(session_context):
    pn.state.on_session_destroyed(destroyed)
    print("created")

pn.state.on_session_created(created)

@philippjfr philippjfr added the type: docs Related to the Panel documentation and examples label Jun 10, 2022
@CmpCtrl
Copy link
Author

CmpCtrl commented Jun 10, 2022

Thanks for this answer, yes its confusing that on_session_created works differently.

Also, i didnt have luck with your second suggestion of putting pn.state.on_session_destroyed() in the created() function. Which i think makes sense since it does work differently.

Follow up question, how might i get access to the app in the context of the destroyed function? Again my goal is to be able to stop the server app.stop() when the session is destroyed, which i understand isn't a common usage.

@CmpCtrl
Copy link
Author

CmpCtrl commented Jun 10, 2022

Ok, yea i'm still confused.

First, the pn.state.on_session_created() must be outside of the main_app() to work, and on_session_destroyed() inside the main_app().

Next, pn.serve() doesnt return until the server has stopped, so i don't see how to access the app.stop() method.

This is what i came up with, which does work, but doesn't seem very elegant..

import logging

import panel as pn

logging.basicConfig(
    format="%(asctime)s.%(msecs)03d %(levelname)s {%(module)s} [%(funcName)s] %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    level=logging.DEBUG,
)


def destroyed(session_context):
    print("destroyed")
    raise KeyboardInterrupt


def created(session_context):
    print("created")


def main_app():
    pn.state.on_session_destroyed(destroyed)
    return pn.Row("Test App")


pn.state.on_session_created(created)

try:
    app = pn.serve(
        {"Demo": main_app},
        port=8080,
        title="Demo",
        show=True,
        start=True,
        autoreload=False,
    )
except KeyboardInterrupt:
    pass

print("complete")
Launching server at http://localhost:8080
2022-06-10 10:33:21.238 INFO {web} [log_request] 302 GET / (::1) 4.00ms
created
2022-06-10 10:33:21.412 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.fenced_code.FencedCodeExtension".
2022-06-10 10:33:21.416 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.footnotes.FootnoteExtension".
2022-06-10 10:33:21.417 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.attr_list.AttrListExtension".
2022-06-10 10:33:21.422 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.def_list.DefListExtension".
2022-06-10 10:33:21.426 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.tables.TableExtension".
2022-06-10 10:33:21.431 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.abbr.AbbrExtension".
2022-06-10 10:33:21.436 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.md_in_html.MarkdownInHtmlExtension".
2022-06-10 10:33:21.437 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.extra.ExtraExtension".
2022-06-10 10:33:21.450 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.smarty.SmartyExtension".
2022-06-10 10:33:21.450 DEBUG {core} [registerExtensions] Successfully loaded extension "markdown.extensions.codehilite.CodeHiliteExtension".
2022-06-10 10:33:21.530 INFO {web} [log_request] 200 GET /Demo (::1) 285.92ms
DEBUG:bokeh.server.views.ws:Subprotocol header received
2022-06-10 10:33:21.734 INFO {web} [log_request] 101 GET /Demo/ws (::1) 2.00ms
INFO:bokeh.server.views.ws:WebSocket connection opened
DEBUG:bokeh.server.views.ws:Receiver created for Protocol()
DEBUG:bokeh.server.views.ws:ProtocolHandler created for Protocol()
INFO:bokeh.server.views.ws:ServerConnection created
DEBUG:bokeh.server.session:Sending pull-doc-reply from session 'Uut5JUm4WsniHgh4bWzU3Y8pmVCwlOOTWxQuzRJ581KQ'
INFO:bokeh.server.views.ws:WebSocket connection closed: code=1001, reason=None
DEBUG:bokeh.server.tornado:[pid 33608] 0 clients connected
DEBUG:bokeh.server.tornado:[pid 33608]   /Demo has 1 sessions with 1 unused
DEBUG:bokeh.server.tornado:[pid 33608] 0 clients connected
DEBUG:bokeh.server.tornado:[pid 33608]   /Demo has 1 sessions with 1 unused
DEBUG:bokeh.server.contexts:Scheduling 1 sessions to discard
DEBUG:bokeh.server.contexts:Discarding session 'Uut5JUm4WsniHgh4bWzU3Y8pmVCwlOOTWxQuzRJ581KQ' last in use 29578.0 milliseconds ago
DEBUG:bokeh.document.modules:Deleting 0 modules for document <bokeh.document.document.Document object at 0x000001A9F49C5750>
destroyed
complete

@philippjfr
Copy link
Member

You can run the server in a separate thread by passing threaded=True. That way pn.serve will return right away.

@CmpCtrl
Copy link
Author

CmpCtrl commented Jun 10, 2022

Copy. This does seem nicer. Thanks for the help.

import logging

import panel as pn

logging.basicConfig(
    format="%(asctime)s.%(msecs)03d %(levelname)s {%(module)s} [%(funcName)s] %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    level=logging.DEBUG,
)


def destroyed(session_context):
    print("destroyed")
    app.stop()


def created(session_context):
    print("created")


def main_app():
    pn.state.on_session_destroyed(destroyed)
    return pn.Row("Test App")


pn.state.on_session_created(created)


app = pn.serve(
    {"Demo": main_app},
    port=8080,
    title="Demo",
    show=True,
    start=True,
    autoreload=False,
    threaded=True,
)


print("complete")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: docs Related to the Panel documentation and examples
Projects
None yet
Development

No branches or pull requests

2 participants