LP1912834 OpenSRF JS Max Parallel Requests user/berick/lp1912834-max-parallel-net
authorBill Erickson <berickxx@gmail.com>
Fri, 22 Jan 2021 20:53:40 +0000 (15:53 -0500)
committerBill Erickson <berickxx@gmail.com>
Fri, 22 Jan 2021 22:01:14 +0000 (17:01 -0500)
Limit the number of active network requests coming from OpenSRF
(WebSocket, XHR) to avoid flooding servers with excessive numbers of
active requests.

The value is currently hardcoded to 5 in opensrf.js.

When the max number of requests limit is reached and new requests
continue to arrive, a warning message is logged to notify the developer
to consider refactoring the client code to use batched calls / API's.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
src/javascript/opensrf.js

index 03c79a9..fab7486 100644 (file)
@@ -64,6 +64,8 @@ var OSRF_STATUS_VERSIONNOTSUPPORTED = 505;
 // TODO: get path from ./configure prefix
 var SHARED_WORKER_LIB = '/js/dojo/opensrf/opensrf_ws_shared.js'; 
 
+var MAX_PARALLEL_REQUESTS = 5;
+
 /* The following classes map directly to network-serializable opensrf objects */
 
 function osrfMessage(hash) {
@@ -272,6 +274,60 @@ OpenSRF.set_subclass = function(cls, pcls) {
 };
 
 
+// Some static request queueing functions.
+OpenSRF.pendingRequests = [];
+OpenSRF.activeRequests = [];
+OpenSRF.throttleAlerted = false;
+
+OpenSRF.sendFromQueue = function() {
+    var pending = OpenSRF.pendingRequests;
+    var active = OpenSRF.activeRequests;
+
+    while (pending.length > 0 && active.length < MAX_PARALLEL_REQUESTS) {
+        var request = pending.shift();
+        active.push(request);
+
+        var ses = request.session;
+        var osrf_msg = request.osrf_msg;
+        var req_args = request.req_args;
+
+        ses.sendToNet(osrf_msg, req_args);
+    }
+
+    if (pending.length > 0) {
+        if (!OpenSRF.throttleAlerted) {
+
+            OpenSRF.throttleAlerted = true;
+            // Seeing this log in the console means the coder
+            // should consider refactoring to use batch calls.
+            console.warn('Net throttling activated on max requests');
+        }
+
+    } else {
+        // Reset now the backlog has been cleared.
+        OpenSRF.throttleAlerted = false;
+    }
+}
+
+OpenSRF.removeFromQueue = function(session, osrf_msg) {
+    var sesThread = session.thread;
+    var msgThread = osrf_msg.threadTrace();
+
+    // Start at the front of the list as those requests are
+    // more likely to be complete just by FIFO logic.
+    for (var idx = OpenSRF.activeRequests.length - 1; idx > 0; idx--) {
+        var req = OpenSRF.activeRequests[idx];
+        if (req.session.thread == sesThread &&
+            req.osrf_msg.threadTrace() == msgThread) {
+            OpenSRF.activeRequests.splice(idx, 1);
+        }
+    }
+
+    // Every completed request is a chance for a pending request
+    // to make it to the big leagues.
+    OpenSRF.sendFromQueue();
+}
+
 /* general session superclass */
 OpenSRF.Session = function() {
     this.remote_id = null;
@@ -289,6 +345,15 @@ OpenSRF.Session.prototype.cleanup = function() {
 };
 
 OpenSRF.Session.prototype.send = function(osrf_msg, args) {
+    OpenSRF.pendingRequests.push({
+        session: this,
+        osrf_msg: osrf_msg,
+        req_args: args
+    });
+    OpenSRF.sendFromQueue();
+}
+
+OpenSRF.Session.prototype.sendToNet = function(osrf_msg, args) {
     args = (args) ? args : {};
     switch(OpenSRF.Session.transport) {
         case OSRF_TRANSPORT_TYPE_WS:
@@ -706,6 +771,7 @@ OpenSRF.Stack.handle_message = function(ses, osrf_msg) {
 
 
         if(status == OSRF_STATUS_COMPLETE) {
+            OpenSRF.removeFromQueue(ses, osrf_msg);
             if(req) {
                 req.complete = true;
                 if(req.oncomplete && !req.oncomplete_called) {
@@ -727,6 +793,7 @@ OpenSRF.Stack.handle_message = function(ses, osrf_msg) {
 
         // capture all 400's and 500's as method errors
         if ((status+'').match(/^4/) || (status+'').match(/^5/)) {
+            OpenSRF.removeFromQueue(ses, osrf_msg);
             if(req && req.onmethoderror) 
                 return req.onmethoderror(req, status, status_text);
         }