Repository: bokeh/bokeh

Issue:
Periodic callbacks continue after tabs are closed
Calling `Session.destroy` when the connection count hits zero seems to fix the problem:

```
    def unsubscribe(self, connection):
        self._subscribed_connections.discard(connection)
        if len(self._subscribed_connections) == 0:
            self.destroy()
        self._last_unsubscribe_time = current_time()

```

@havocp is this the right approach? 



Discussion:
Changing to `0.12`, this is a very serious bug that needs to be addressed. 

I think this is expected to happen until the session expires. However, we have a very long session expiration by default. Do the callbacks continue past expiration? If so that would need debugging, but if shortening expiration time fixes this, then it doesn't need debugging just changing.

If we need closing a tab to shut things down, some options I can think of:
- make the session expiration time MUCH shorter (an issue here is when you're using a fixed session name locally with a notebook or something, but I think I may remember that we already special-cased that by only expiring uuid names?)
- introduce some sort of "paused" state, in which we remove periodic callbacks because there are no active websockets, but we auto-re-enable them when a websocket connects to the session. This is different from "destroyed" because we would not discard the document, just suspend its callbacks temporarily
- add some way for apps to ask whether there are any live websockets; periodic callbacks could then avoid doing work if there are no clients.

The thing I'd go for with session expiration would be to try to keep the session across a page reload, maybe. Or in the future if we implement auto-reconnect of the websocket, across a disconnect/reconnect.
So something like 15-30 seconds perhaps?

This appears to be currently working on master, with an invocation like:

```
bokeh serve --unused-session-lifetime 1000 --check-unused-sessions 1000 --show app.py
```

so I think the best short-term solution is some docs support, and lowering the expiration time to ~15 seconds. I think the "paused" state might be useful, but only as an optional thing for more complex use-cases, so it could be added on later, separately. 

Thoughts? 

to me a paused state is probably needless complexity until proven otherwise 

That's what I was saying in a roundabout way. :) 


Resolving patch:
diff --git a/bokeh/command/subcommands/serve.py b/bokeh/command/subcommands/serve.py
--- a/bokeh/command/subcommands/serve.py
+++ b/bokeh/command/subcommands/serve.py
@@ -258,7 +258,7 @@
     bokeh serve app_script.py --unused-session-lifetime 60000
 
 The value is specified in milliseconds. The default lifetime interval
-for unused sessions is 30 minutes. Only positive integer values are
+for unused sessions is 15 seconds. Only positive integer values are
 accepted.
 
 Logging Options
diff --git a/bokeh/server/tornado.py b/bokeh/server/tornado.py
--- a/bokeh/server/tornado.py
+++ b/bokeh/server/tornado.py
@@ -125,7 +125,7 @@ def __init__(self, applications, prefix, hosts,
                  # how often to check for unused sessions
                  check_unused_sessions_milliseconds=17000,
                  # how long unused sessions last
-                 unused_session_lifetime_milliseconds=60*30*1000,
+                 unused_session_lifetime_milliseconds=15000,
                  # how often to log stats
                  stats_log_frequency_milliseconds=15000,
                  develop=False):


Test patch: