Repository: bokeh/bokeh

Issue:
Difference between public and internal session lifetime units
The Bokeh server's session lifetime flag, `--unused-session-lifetime`, expects values in milliseconds. This value is first assigned to `unused_session_lifetime_milliseconds` and eventually assigned to the Tornado flag, `unused_session_linger_seconds`.

When checking for unused sessions, `unused_session_linger_seconds` (actual units in milliseconds) is compared with `seconds_since_last_unsubscribe` (actual units in seconds). Session cleanup doesn't occur when expected.

Before working on a fix, I wanted to gather thoughts from @havocp and @bryevdv.



Discussion:
I think we should make everything be milliseconds so we don't get confused (almost everything is already).

Agreed


Resolving patch:
diff --git a/bokeh/server/application_context.py b/bokeh/server/application_context.py
--- a/bokeh/server/application_context.py
+++ b/bokeh/server/application_context.py
@@ -186,7 +186,7 @@ def get_session(self, session_id):
     def _discard_session(self, session, should_discard):
         if session.connection_count > 0:
             raise RuntimeError("Should not be discarding a session with open connections")
-        log.debug("Discarding session %r last in use %r seconds ago", session.id, session.seconds_since_last_unsubscribe)
+        log.debug("Discarding session %r last in use %r milliseconds ago", session.id, session.milliseconds_since_last_unsubscribe)
 
         session_context = self._session_contexts[session.id]
 
@@ -218,10 +218,10 @@ def do_discard():
         raise gen.Return(None)
 
     @gen.coroutine
-    def cleanup_sessions(self, unused_session_linger_seconds):
+    def cleanup_sessions(self, unused_session_linger_milliseconds):
         def should_discard_ignoring_block(session):
             return session.connection_count == 0 and \
-                (session.seconds_since_last_unsubscribe > unused_session_linger_seconds or \
+                (session.milliseconds_since_last_unsubscribe > unused_session_linger_milliseconds or \
                  session.expiration_requested)
         # build a temp list to avoid trouble from self._sessions changes
         to_discard = []
diff --git a/bokeh/server/session.py b/bokeh/server/session.py
--- a/bokeh/server/session.py
+++ b/bokeh/server/session.py
@@ -12,12 +12,15 @@
 import time
 
 def current_time():
+    '''Return the time in milliseconds since the epoch as a floating
+       point number.
+    '''
     try:
         # python >=3.3 only
-        return time.monotonic()
+        return time.monotonic() * 1000
     except:
         # if your python is old, don't set your clock backward!
-        return time.time()
+        return time.time() * 1000
 
 def _needs_document_lock(func):
     '''Decorator that adds the necessary locking and post-processing
@@ -136,7 +139,7 @@ def connection_count(self):
         return len(self._subscribed_connections)
 
     @property
-    def seconds_since_last_unsubscribe(self):
+    def milliseconds_since_last_unsubscribe(self):
         return current_time() - self._last_unsubscribe_time
 
     @_needs_document_lock
diff --git a/bokeh/server/tornado.py b/bokeh/server/tornado.py
--- a/bokeh/server/tornado.py
+++ b/bokeh/server/tornado.py
@@ -175,7 +175,7 @@ def __init__(self, applications, prefix, hosts,
         self._stats_job = PeriodicCallback(self.log_stats,
                                            stats_log_frequency_milliseconds,
                                            io_loop=self._loop)
-        self._unused_session_linger_seconds = unused_session_lifetime_milliseconds
+        self._unused_session_linger_milliseconds = unused_session_lifetime_milliseconds
         self._cleanup_job = PeriodicCallback(self.cleanup_sessions,
                                              check_unused_sessions_milliseconds,
                                              io_loop=self._loop)
@@ -299,7 +299,7 @@ def get_sessions(self, app_path):
     @gen.coroutine
     def cleanup_sessions(self):
         for app in self._applications.values():
-            yield app.cleanup_sessions(self._unused_session_linger_seconds)
+            yield app.cleanup_sessions(self._unused_session_linger_milliseconds)
         raise gen.Return(None)
 
     def log_stats(self):


Test patch: