Improved tunnel clean up, #4172

Ylian Saint-Hilaire committed Jun 29, 2022 at 14:44 UTC d2b39fef3e488f6f0cc01432895872e87a9f6109
1 file changed +25 -6
apprelays.js
+25 -6
@@ -82,6 +82,7 @@ module.exports.CreateWebRelaySession = function (parent, db, req, args, domain,
82 var pendingRequests = [];
83 var nextTunnelId = 1;
84 var tunnels = {};
85 + var errorCount = 0; // If we keep closing tunnels without processing requests, fail the requests
86
87 // Any HTTP cookie set by the device is going to be shared between all tunnels to that device.
88 obj.webCookies = {};
@@ -121,6 +122,12 @@ module.exports.CreateWebRelaySession = function (parent, db, req, args, domain,
122
123 // Handle request
124 function handleNextRequest() {
125 + // if there are not pending requests, do nothing
126 + if (pendingRequests.length == 0) return;
127 +
128 + // If the errorCount is high, something is really wrong, we are opening lots of tunnels and not processing any requests.
129 + if (errorCount > 5) { close(); return; }
130 +
131 // Check to see if any of the tunnels are free
132 var count = 0;
133 for (var i in tunnels) {
@@ -140,12 +147,10 @@ module.exports.CreateWebRelaySession = function (parent, db, req, args, domain,
147 function launchNewTunnel() {
148 // Launch a new tunnel
149 const tunnel = module.exports.CreateWebRelay(obj, db, args, domain);
143 - tunnel.onclose = function (tunnelId) {
150 + tunnel.onclose = function (tunnelId, processedCount) {
151 + if (processedCount == 0) { errorCount++; } // If this tunnel closed without processing any requests, mark this as an error
152 delete tunnels[tunnelId];
145 - // Count how many non-websocket tunnels are active
146 - var count = 0;
147 - for (var i in tunnels) { count += (tunnels[i].isWebSocket ? 0 : 1); }
148 - if (count == 0) { launchNewTunnel(); }
153 + handleNextRequest();
154 }
155 tunnel.onconnect = function (tunnelId) {
156 if (pendingRequests.length > 0) {
@@ -154,6 +159,7 @@ module.exports.CreateWebRelaySession = function (parent, db, req, args, domain,
159 }
160 }
161 tunnel.oncompleted = function (tunnelId) {
162 + errorCount = 0; // Something got completed, clear any error count
163 if (pendingRequests.length > 0) {
164 const x = pendingRequests.shift();
165 if (x[2] == true) { tunnels[tunnelId].processWebSocket(x[0], x[1]); } else { tunnels[tunnelId].processRequest(x[0], x[1]); }
@@ -166,11 +172,21 @@ module.exports.CreateWebRelaySession = function (parent, db, req, args, domain,
172
173 // Close all tunnels
174 function close() {
175 + // Set the session as closed
176 if (obj.closed == true) return;
177 obj.closed = true;
178 +
179 + // Close all tunnels
180 for (var i in tunnels) { tunnels[i].close(); }
181 tunnels = null;
182 +
183 + // Close any pending requests
184 + for (var i in pendingRequests) { if (pendingRequests[i][2] == true) { pendingRequests[i][1].end(); } else { pendingRequests[i][1].close(); } }
185 +
186 + // Notify of session closure
187 if (obj.onclose) { obj.onclose(obj.userid + '/' + obj.sessionId); }
188 +
189 + // Cleanup
190 delete obj.userid;
191 delete obj.lastOperation;
192 }
@@ -189,6 +205,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
205 obj.relayActive = false;
206 obj.closed = false;
207 obj.isWebSocket = false;
208 + obj.processedRequestCount = 0;
209 const constants = (require('crypto').constants ? require('crypto').constants : require('constants')); // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead.
210
211 // Events
@@ -341,7 +358,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
358 if (obj.ws) { obj.ws.close(); delete obj.ws; }
359
360 // Event disconnection
344 - if (obj.onclose) { obj.onclose(obj.tunnelId); }
361 + if (obj.onclose) { obj.onclose(obj.tunnelId, obj.processedRequestCount); }
362
363 obj.relayActive = false;
364 };
@@ -461,6 +478,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
478 if ((obj.socketXHeader['transfer-encoding'] != null) && (obj.socketXHeader['transfer-encoding'].toLowerCase() == 'chunked')) { obj.socketParseState = 1; }
479 if (obj.isWebSocket) {
480 if ((obj.socketXHeader['connection'] != null) && (obj.socketXHeader['connection'].toLowerCase() == 'upgrade')) {
481 + obj.processedRequestCount++;
482 obj.socketParseState = 2; // Switch to decoding websocket frames
483 obj.ws._socket.resume(); // Resume the browser's websocket
484 } else {
@@ -615,6 +633,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
633 delete obj.res;
634
635 // Event completion
636 + obj.processedRequestCount++;
637 if (obj.oncompleted) { obj.oncompleted(obj.tunnelId); }
638 }
639 } else {