diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ 'conpherence.pkg.css' => 'a34d59bd', 'conpherence.pkg.js' => '5f86c17d', 'core.pkg.css' => '959330a2', - 'core.pkg.js' => '349e50d5', + 'core.pkg.js' => '4a83713e', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', @@ -361,7 +361,7 @@ 'rsrc/image/texture/table_header.png' => '5c433037', 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', - 'rsrc/js/application/aphlict/Aphlict.js' => '6304947a', + 'rsrc/js/application/aphlict/Aphlict.js' => '674e335f', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'caade6f2', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '06e7f30e', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', @@ -583,7 +583,7 @@ 'herald-rule-editor' => 'd6a7e717', 'herald-test-css' => 'a52e323e', 'inline-comment-summary-css' => '51efda3a', - 'javelin-aphlict' => '6304947a', + 'javelin-aphlict' => '674e335f', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => 'caade6f2', 'javelin-behavior-aphlict-listen' => '06e7f30e', @@ -1391,19 +1391,19 @@ 'javelin-install', 'javelin-util', ), - '6304947a' => array( - 'javelin-install', - 'javelin-util', - 'javelin-websocket', - 'javelin-leader', - 'javelin-json', - ), '635de1ec' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-workflow', 'javelin-dom', ), + '674e335f' => array( + 'javelin-install', + 'javelin-util', + 'javelin-websocket', + 'javelin-leader', + 'javelin-json', + ), '680ea2c8' => array( 'javelin-install', 'javelin-dom', diff --git a/support/aphlict/server/lib/AphlictClientServer.js b/support/aphlict/server/lib/AphlictClientServer.js --- a/support/aphlict/server/lib/AphlictClientServer.js +++ b/support/aphlict/server/lib/AphlictClientServer.js @@ -153,6 +153,18 @@ } break; + case 'ping': + var pong = { + type: 'pong' + }; + + try { + listener.writeMessage(pong); + } catch (error) { + // Ignore any issues here, we'll clean up elsewhere. + } + break; + default: log( 'Unrecognized command "%s".', diff --git a/webroot/rsrc/js/application/aphlict/Aphlict.js b/webroot/rsrc/js/application/aphlict/Aphlict.js --- a/webroot/rsrc/js/application/aphlict/Aphlict.js +++ b/webroot/rsrc/js/application/aphlict/Aphlict.js @@ -41,6 +41,7 @@ _subscriptions: null, _status: null, _isReconnect: false, + _keepaliveInterval: false, start: function() { JX.Leader.listen('onBecomeLeader', JX.bind(this, this._lead)); @@ -104,6 +105,14 @@ this._broadcastStatus('open'); JX.Leader.broadcast(null, {type: 'aphlict.getsubscribers'}); + + // By default, ELBs terminate connections after 60 seconds with no + // traffic. Other load balancers may have similar configuration. Send + // a keepalive message every 15 seconds to prevent load balancers from + // deciding they can reap this connection. + + var keepalive = JX.bind(this, this._keepalive); + this._keepaliveInterval = setInterval(keepalive, 15000); }, _didReconnect: function() { @@ -124,6 +133,11 @@ }, _close: function() { + if (this._keepaliveInterval) { + clearInterval(this._keepaliveInterval); + this._keepaliveInterval = null; + } + this._broadcastStatus('closed'); }, @@ -135,6 +149,11 @@ var message = JX.JSON.parse(raw); var id = message.uniqueID || null; + // If this is just a keepalive response, don't bother broadcasting it. + if (message.type == 'pong') { + return; + } + JX.Leader.broadcast(id, {type: 'aphlict.server', data: message}); }, @@ -198,6 +217,10 @@ }; return this._write(frame); + }, + + _keepalive: function() { + this._writeCommand('ping', null); } },