Python/Shiny Long Running Dashboards - Memory Leaks In Server and Client

Sorry, I somehow missed your latest message, thanks for your patience.

I just merged a fix for this in shinywidgets. Feel free to try again with

git+https://github.com/posit-dev/py-shinywidgets.git

In your requirements.txt

Carson,

I conducted some additional tests on the newly deployed update.

After about 14 hours,

Chrome Browser - 3,155,228 K (steadily growing)
Python Process - 1,397,280 K (steadily growing)

It appears that your updates have slowed down the memory growth in the Chrome process to some degree. Previously, it was growing to over 4GB and crashing within about 2 hours.

Still looks like there is memory leakage on both server and client that are pretty significant.

ShinyWidgets reports v0.3.4.9002

Matt

I tried running tracemalloc in the server process and this is what it reported. Looks like many memory allocations in shiny reactive core, but I don't really have a lot of experience interpreting these traces.

INFO:     Shutting down
INFO:     connection closed
INFO:     Waiting for application shutdown.
Cleaning up...
[ Top 10 differences ]
C:\git\pyshiny.kiosk.qalab\venv\Lib\site-packages\shiny\reactive\_core.py:105: size=20.1 MiB (+20.1 MiB), count=194829 (+194829), average=108 B
<frozen importlib._bootstrap_external>:753: size=14.8 MiB (+14.8 MiB), count=59506 (+59506), average=260 B
C:\git\pyshiny.kiosk.qalab\venv\Lib\site-packages\shiny\reactive\_core.py:192: size=4567 KiB (+4567 KiB), count=97406 (+97406), average=48 B
C:\git\pyshiny.kiosk.qalab\venv\Lib\site-packages\shiny\reactive\_reactives.py:146: size=3805 KiB (+3805 KiB), count=97408 (+97408), average=40 B
C:\git\pyshiny.kiosk.qalab\venv\Lib\site-packages\shiny\reactive\_reactives.py:212: size=3805 KiB (+3805 KiB), count=97404 (+97404), average=40 B
C:\git\pyshiny.kiosk.qalab\venv\Lib\site-packages\shiny\reactive\_core.py:103: size=3459 KiB (+3459 KiB), count=33 (+33), average=105 KiB
C:\git\pyshiny.kiosk.qalab\venv\Lib\site-packages\shiny\reactive\_core.py:49: size=2664 KiB (+2664 KiB), count=48710 (+48710), average=56 B
C:\git\pyshiny.kiosk.qalab\venv\Lib\site-packages\shiny\reactive\_core.py:48: size=2664 KiB (+2664 KiB), count=48710 (+48710), average=56 B
C:\Users\Witzmanm\AppData\Local\Programs\Python\Python312\Lib\copy.py:253: size=1582 KiB (+1582 KiB), count=33750 (+33750), average=48 B
C:\git\pyshiny.kiosk.qalab\venv\Lib\site-packages\shiny\reactive\_core.py:73: size=1522 KiB (+1522 KiB), count=48710 (+48710), average=32 B
INFO:     Application shutdown complete.
INFO:     Finished server process [29588]
INFO:     Stopping reloader process [21808]
PS C:\git\pyshiny.kiosk.qalab>

`

If you must support user sessions that last this long for an application like this, I would recommend forcing a refresh of the page after a certain period of time, which should cap the memory usage.

Here a snippet of JavaScript to do that

// After 10 minutes of inactivity, refresh the page
const INACTIVITY_TIMEOUT = 10 * 60 * 1000;

function setupAutoReload() {

    const reloadPage = () => {
        console.log('No activity detected for 10 minutes. Reloading...');
        window.location.reload();
    };

    // Track the inactivity timeout
    let inactivityTimer = null;

    // When the user interacts with the page, reset the inactivity timer
    const handleActivity = () => {
        if (inactivityTimer) {
            clearTimeout(inactivityTimer);
        }
        inactivityTimer = setTimeout(reloadPage, INACTIVITY_TIMEOUT);
    };

    // A list of events that should reset the inactivity timer
    const events = ['mousemove', 'mousedown', 'keypress', 'scroll', 'touchstart', 'resize', 'click', 'keydown', 'wheel'];

    events.forEach(eventType => {
        window.addEventListener(eventType, handleActivity, { passive: true });
    });

    // Set initial timer
    inactivityTimer = setTimeout(reloadPage, INACTIVITY_TIMEOUT);
}

// Initialize the auto-reload functionality
// Important: only call this once
setupAutoReload();

To include the JavaScript in your app, you can include it in a ui.tags.script() (like you've have in the original example app).