Repository: bokeh/bokeh

Issue:
Add session expiration time options to `bokeh serve`
The Server class has 'check_unused_sessions_milliseconds', 'unused_session_lifetime_milliseconds', 'stats_log_frequency_milliseconds' but we don't have command line options to set these.



Discussion:

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
@@ -112,6 +112,16 @@
 The value is specified in milliseconds. The default keep-alive interval
 is 37 seconds. Give a value of 0 to disable keep-alive pings.
 
+To control how often statistic logs are written, set the
+--stats-log-frequency option:
+
+.. code-block:: sh
+
+    bokeh serve app_script.py --stats-log-frequency 30000
+
+The value is specified in milliseconds. The default interval for
+logging stats is 15 seconds. Only positive integer values are accepted.
+
 To have the Bokeh server override the remote IP and URI scheme/protocol for
 all requests with ``X-Real-Ip``, ``X-Forwarded-For``, ``X-Scheme``,
 ``X-Forwarded-Proto``  headers (if they are provided), set the
@@ -194,6 +204,31 @@
 least 256 bits (32 bytes) of entropy.  You can generate a new
 secret key with the ``bokeh secret`` command.
 
+Session Expiration Options
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To configure how often to check for unused sessions. set the
+--check-unused-sessions option:
+
+.. code-block:: sh
+
+    bokeh serve app_script.py --check-unused-sessions 10000
+
+The value is specified in milliseconds. The default interval for
+checking for unused sessions is 17 seconds. Only positive integer
+values are accepted.
+
+To configure how often unused sessions last. set the
+--unused-session-lifetime option:
+
+.. code-block:: sh
+
+    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
+accepted.
+
 Development Options
 ~~~~~~~~~~~~~~~~~~~
 
@@ -299,6 +334,27 @@ class Serve(Subcommand):
             default=None,
         )),
 
+        ('--check-unused-sessions', dict(
+            metavar='MILLISECONDS',
+            type=int,
+            help="How often to check for unused sessions",
+            default=None,
+        )),
+
+        ('--unused-session-lifetime', dict(
+            metavar='MILLISECONDS',
+            type=int,
+            help="How long unused sessions last",
+            default=None,
+        )),
+
+        ('--stats-log-frequency', dict(
+            metavar='MILLISECONDS',
+            type=int,
+            help="How often to log stats",
+            default=None,
+        )),
+
         ('--use-xheaders', dict(
             action='store_true',
             help="Prefer X-headers for IP/protocol information",
@@ -340,6 +396,21 @@ def invoke(self, args):
             # rename to be compatible with Server
             args.keep_alive_milliseconds = args.keep_alive
 
+        if args.check_unused_sessions is not None:
+            log.info("Check for unused sessions every %d milliseconds", args.check_unused_sessions)
+            # rename to be compatible with Server
+            args.check_unused_sessions_milliseconds = args.check_unused_sessions
+
+        if args.unused_session_lifetime is not None:
+            log.info("Unused sessions last for %d milliseconds", args.unused_session_lifetime)
+            # rename to be compatible with Server
+            args.unused_session_lifetime_milliseconds = args.unused_session_lifetime
+
+        if args.stats_log_frequency is not None:
+            log.info("Log statistics every %d milliseconds", args.stats_log_frequency)
+            # rename to be compatible with Server
+            args.stats_log_frequency_milliseconds = args.stats_log_frequency
+
         server_kwargs = { key: getattr(args, key) for key in ['port',
                                                               'address',
                                                               'allow_websocket_origin',
@@ -347,6 +418,9 @@ def invoke(self, args):
                                                               'prefix',
                                                               'develop',
                                                               'keep_alive_milliseconds',
+                                                              'check_unused_sessions_milliseconds',
+                                                              'unused_session_lifetime_milliseconds',
+                                                              'stats_log_frequency_milliseconds',
                                                               'use_xheaders',
                                                             ]
                           if getattr(args, key, None) is not None }
diff --git a/bokeh/server/tornado.py b/bokeh/server/tornado.py
--- a/bokeh/server/tornado.py
+++ b/bokeh/server/tornado.py
@@ -76,6 +76,9 @@ class BokehTornado(TornadoApplication):
             These are in addition to ``hosts``.
         keep_alive_milliseconds (int) : number of milliseconds between keep-alive pings
             Set to 0 to disable pings. Pings keep the websocket open.
+        check_unused_sessions_milliseconds (int) : number of milliseconds between check for unused sessions
+        unused_session_lifetime_milliseconds (int) : number of milliseconds for unused session lifetime
+        stats_log_frequency_milliseconds (int) : number of milliseconds between logging stats
         develop (boolean) : True for develop mode
 
     '''
@@ -107,6 +110,15 @@ def __init__(self, applications, prefix, hosts,
             # 0 means "disable"
             raise ValueError("keep_alive_milliseconds must be >= 0")
 
+        if check_unused_sessions_milliseconds <= 0:
+            raise ValueError("check_unused_sessions_milliseconds must be > 0")
+
+        if unused_session_lifetime_milliseconds <= 0:
+            raise ValueError("check_unused_sessions_milliseconds must be > 0")
+
+        if stats_log_frequency_milliseconds <= 0:
+            raise ValueError("stats_log_frequency_milliseconds must be > 0")
+
         self._hosts = set(hosts)
         self._websocket_origins = self._hosts | set(extra_websocket_origins)
         self._resources = {}


Test patch:
diff --git a/bokeh/command/subcommands/tests/test_serve.py b/bokeh/command/subcommands/tests/test_serve.py
--- a/bokeh/command/subcommands/tests/test_serve.py
+++ b/bokeh/command/subcommands/tests/test_serve.py
@@ -82,6 +82,27 @@ def test_args():
             default=None,
         )),
 
+        ('--check-unused-sessions', dict(
+            metavar='MILLISECONDS',
+            type=int,
+            help="How often to check for unused sessions",
+            default=None,
+        )),
+
+        ('--unused-session-lifetime', dict(
+            metavar='MILLISECONDS',
+            type=int,
+            help="How long unused sessions last",
+            default=None,
+        )),
+
+        ('--stats-log-frequency', dict(
+            metavar='MILLISECONDS',
+            type=int,
+            help="How often to log stats",
+            default=None,
+        )),
+
         ('--use-xheaders', dict(
             action='store_true',
             help="Prefer X-headers for IP/protocol information",