diff --git a/R/SockJSAdapter.R b/R/SockJSAdapter.R index 53541df7..03d21aea 100644 --- a/R/SockJSAdapter.R +++ b/R/SockJSAdapter.R @@ -43,6 +43,7 @@ local({ disableProtocols <- paste('"', disableProtocols, '"', sep = '', collapse = ',') } reconnect <- if (identical("true", tolower(input[11]))) "true" else "false" + reconnectTimeout <- as.numeric(input[14]) * 1000 options(shiny.sanitize.errors = identical("true", tolower(input[12]))) # Top-level bookmarking directory (for all users) @@ -191,8 +192,8 @@ local({ tags$script(src='__assets__/sockjs-0.3.4.min.js'), tags$script(src='__assets__/shiny-server-client.min.js'), tags$script( - sprintf("preShinyInit({reconnect:%s,disableProtocols:[%s]});", - reconnect, disableProtocols + sprintf("preShinyInit({reconnect:%s,disableProtocols:[%s],reconnectTimeout:%s});", + reconnect, disableProtocols, reconnectTimeout ) ), tags$link(rel='stylesheet', type='text/css', href='__assets__/shiny-server.css'), diff --git a/config/shiny-server-rules.config b/config/shiny-server-rules.config index 1a874eaf..1b31707f 100644 --- a/config/shiny-server-rules.config +++ b/config/shiny-server-rules.config @@ -176,6 +176,14 @@ sockjs_disconnect_delay { maxcount 1; } +reconnect_timeout { + desc "The number of seconds to retain an abruptly closed connection + before passing along its closed and/or end events. Defaults to 15 seconds."; + param Float timeout "The number of seconds to retain an abruptly closed connection."; + at $; + maxcount 1; +} + simple_scheduler { desc "A basic scheduler which will spawn one single-threaded R worker for each application. If no scheduler is specified, this is the default scheduler."; param Integer [maxRequests] "The maximum number of requests to assign to this scheduler before it should start returning rejecting incoming traffic using a '503 - Service Unavailable' message. Once this threshold is hit, users attempting to initialize a new session will receive 503 errors." 100; diff --git a/lib/main.js b/lib/main.js index 4445e0ea..1ace4718 100755 --- a/lib/main.js +++ b/lib/main.js @@ -247,7 +247,8 @@ var loadConfig_p = qutil.serialized(function() { // Create SockJS server sockjsServer = proxy_sockjs.createServer(metarouter, schedulerRegistry, - configRouter.sockjsHeartbeatDelay, configRouter.sockjsDisconnectDelay); + configRouter.sockjsHeartbeatDelay, configRouter.sockjsDisconnectDelay, + configRouter.reconnectTimeout); sockjsHandler = sockjsServer.middleware(); socketTimeout = configRouter.httpKeepaliveTimeout; diff --git a/lib/proxy/sockjs.js b/lib/proxy/sockjs.js index 18ae1085..7395f341 100644 --- a/lib/proxy/sockjs.js +++ b/lib/proxy/sockjs.js @@ -22,7 +22,7 @@ var RobustSockJS = require('./robust-sockjs'); var errorcode = require("./errorcode"); exports.createServer = createServer; -function createServer(router, schedulerRegistry, heartbeatDelay, disconnectDelay) { +function createServer(router, schedulerRegistry, heartbeatDelay, disconnectDelay, reconnectTimeout) { if (!heartbeatDelay || heartbeatDelay < 0) { logger.warn("Ignoring invalid SockJS heartbeat delay: " + heartbeatDelay); heartbeatDelay = 25 * 1000; @@ -45,7 +45,7 @@ function createServer(router, schedulerRegistry, heartbeatDelay, disconnectDelay disconnect_delay: disconnectDelay }); - var robust = new RobustSockJS(); + var robust = new RobustSockJS(reconnectTimeout); sockjsServer.on('connection', function(conn) { var robustConn = robust.robustify(conn); if (!robustConn){ diff --git a/lib/router/config-router-util.js b/lib/router/config-router-util.js index 07c743af..b76a414e 100644 --- a/lib/router/config-router-util.js +++ b/lib/router/config-router-util.js @@ -81,6 +81,11 @@ function parseApplication(settings, locNode, provideDefaults){ appSettings.reconnect = false; } + if (locNode.getValues('reconnect_timeout') && + locNode.getValues('reconnect_timeout').timeout){ + appSettings.reconnectTimeout = locNode.getValues('reconnect_timeout').timeout; + } + if (locNode.getOne('sanitize_errors') && locNode.getValues('sanitize_errors').enabled === false) { appSettings.sanitizeErrors = false; diff --git a/lib/router/config-router.js b/lib/router/config-router.js index 320b0995..67e7a223 100644 --- a/lib/router/config-router.js +++ b/lib/router/config-router.js @@ -128,6 +128,11 @@ function ConfigRouter(conf, schedulerRegistry) { this.sockjsDisconnectDelay = conf.getValues('sockjs_disconnect_delay').delay * 1000; } + this.reconnectTimeout = 15; + if (conf.getOne('reconnect_timeout')) { + this.reconnectTimeout = conf.getValues('reconnect_timeout').timeout; + } + var apps = conf.search("application", true); if (apps && apps.length > 0){ logger.error("The `application` configuration has been deprecated. Please "+ diff --git a/lib/worker/app-worker.js b/lib/worker/app-worker.js index 97637127..09d9c8d9 100644 --- a/lib/worker/app-worker.js +++ b/lib/worker/app-worker.js @@ -347,7 +347,8 @@ var AppWorker = function(appSpec, endpoint, logStream, workerId, home) { appSpec.settings.appDefaults.disableProtocols.join(",") + '\n' + appSpec.settings.appDefaults.reconnect + '\n' + appSpec.settings.appDefaults.sanitizeErrors + '\n' + - appSpec.settings.appDefaults.bookmarkStateDir + '\n' + appSpec.settings.appDefaults.bookmarkStateDir + '\n' + + appSpec.settings.appDefaults.reconnectTimeout + '\n' ); self.$proc.stdout.pipe(split()).on('data', function(line){ var match = null;