Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More control over http keepalive and sockjs timeouts #264

Merged
merged 3 commits into from
Nov 18, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
shiny-server 1.5.2
--------------------------------------------------------------------------------

* Add additional configuration directives `http_keepalive_timeout`,
`sockjs_heartbeat_delay`, and `sockjs_disconnect_delay` to allow working
with very slow connections and large SockJS payloads.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth clarifying whether or not any of these default values changed from the previous release. (I'm curious)

shiny-server 1.5.1
--------------------------------------------------------------------------------

Expand Down
21 changes: 21 additions & 0 deletions config/shiny-server-rules.config
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,27 @@ app_idle_timeout {
maxcount 1;
}

http_keepalive_timeout {
desc "Defines how long a keepalive connection will sit between HTTP requests/responses before it is closed. Defaults to 45 seconds.";
param Float timeout "The number of seconds to keep a connection alive between requests/responses.";
at $;
maxcount 1;
}

sockjs_heartbeat_delay {
desc "How often the SockJS server should send heartbeat packets to the server. These are used to prevent proxies and load balancers from closing active SockJS connections. Defaults to 25 seconds.";
param Float delay "The number of seconds to wait between heartbeat packets.";
at $;
maxcount 1;
}

sockjs_disconnect_delay {
desc "How long the SockJS server should wait between HTTP requests before considering the client to be disconnected.";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you note the default?

param Float delay "The number of seconds to wait before giving up.";
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;
Expand Down
9 changes: 7 additions & 2 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ app.use(connect_util.filterByRegex(
));
app.use(shinyProxy.httpListener);

var socketTimeout = 45 * 1000;

// Now create a server and hook everything up.
var server = new Server();
server.on('connection', function(socket) {
Expand All @@ -186,7 +188,7 @@ server.on('connection', function(socket) {
// SockJS sends a heartbeat every 25s so as long as we wait significantly
// longer than that to timeout, we shouldn't need to worry about closing
// active connections.
socket.setTimeout(45 * 1000);
socket.setTimeout(socketTimeout);
});
server.on('request', _.bind(app.handle, app));
server.on('error', function(err) {
Expand Down Expand Up @@ -226,9 +228,12 @@ var loadConfig_p = qutil.serialized(function() {
transport.setSocketDir(configRouter.socketDir);

// Create SockJS server
sockjsServer = proxy_sockjs.createServer(metarouter, schedulerRegistry);
sockjsServer = proxy_sockjs.createServer(metarouter, schedulerRegistry,
configRouter.sockjsHeartbeatDelay, configRouter.sockjsDisconnectDelay);
sockjsHandler = sockjsServer.middleware();

socketTimeout = configRouter.httpKeepaliveTimeout;

return createLogger_p(configRouter.accessLogSpec)
.then(function(logfunc) {
requestLogger = logfunc;
Expand Down
15 changes: 13 additions & 2 deletions lib/proxy/sockjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,16 @@ var RobustSockJS = require('./robust-sockjs');
var errorcode = require("./errorcode");

exports.createServer = createServer;
function createServer(router, schedulerRegistry) {
function createServer(router, schedulerRegistry, heartbeatDelay, disconnectDelay) {
if (!heartbeatDelay || heartbeatDelay < 0) {
logger.warn("Ignoring invalid SockJS heartbeat delay: " + heartbeatDelay);
heartbeatDelay = 25 * 1000;
}
if (!disconnectDelay || disconnectDelay < 0) {
logger.warn("Ignoring invalid SockJS disconnect delay: " + disconnectDelay);
disconnectDelay = 5 * 1000;
}

// Create a single SockJS server that will serve all applications. We'll use
// the connection.url to dispatch among the different worker processes'
// websocket ports. Once a connection is established, we simply pipe IO
Expand All @@ -31,7 +40,9 @@ function createServer(router, schedulerRegistry) {
// TODO: make URL configurable
sockjs_url: '//d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.min.js',
prefix: '.*/__sockjs__(/[no]=\\w+)?',
log: function() {}
log: function() {},
heartbeat_delay: heartbeatDelay,
disconnect_delay: disconnectDelay
});

var robust = new RobustSockJS();
Expand Down
15 changes: 15 additions & 0 deletions lib/router/config-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,21 @@ function ConfigRouter(conf, schedulerRegistry) {
this.$allowAppOverride = conf.getValues('allow_app_override').enabled;
this.$templateDir = conf.getValues('template_dir').dir;

this.httpKeepaliveTimeout = 45 * 1000;
if (conf.getOne('http_keepalive_timeout')) {
this.httpKeepaliveTimeout = conf.getValues('http_keepalive_timeout').timeout * 1000;
}

this.sockjsHeartbeatDelay = 25 * 1000;
if (conf.getOne('sockjs_heartbeat_delay')) {
this.sockjsHeartbeatDelay = conf.getValues('sockjs_heartbeat_delay').delay * 1000;
}

this.sockjsDisconnectDelay = 5 * 1000;
if (conf.getOne('sockjs_disconnect_delay')) {
this.sockjsDisconnectDelay = conf.getValues('sockjs_disconnect_delay').delay * 1000;
}

var apps = conf.search("application", true);
if (apps && apps.length > 0){
logger.error("The `application` configuration has been deprecated. Please "+
Expand Down