You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by vi...@apache.org on 2012/06/06 20:25:03 UTC

svn commit: r1347030 - in /incubator/ambari/branches/ambari-186: CHANGES.txt hmc/js/txnUtils.js hmc/js/utils.js

Author: vikram
Date: Wed Jun  6 18:25:02 2012
New Revision: 1347030

URL: http://svn.apache.org/viewvc?rev=1347030&view=rev
Log:
AMBARI-286. Make TxnProgressWidget Immune To Re-Fetch Race Conditions (Contributed by Varun)

Modified:
    incubator/ambari/branches/ambari-186/CHANGES.txt
    incubator/ambari/branches/ambari-186/hmc/js/txnUtils.js
    incubator/ambari/branches/ambari-186/hmc/js/utils.js

Modified: incubator/ambari/branches/ambari-186/CHANGES.txt
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/ambari-186/CHANGES.txt?rev=1347030&r1=1347029&r2=1347030&view=diff
==============================================================================
--- incubator/ambari/branches/ambari-186/CHANGES.txt (original)
+++ incubator/ambari/branches/ambari-186/CHANGES.txt Wed Jun  6 18:25:02 2012
@@ -6,6 +6,8 @@ characters wide.
 
 Release 0.1.x - unreleased
 
+  AMBARI-286. Make TxnProgressWidget Immune To Re-Fetch Race Conditions (Varun via Vikram)
+
   AMBARI-526. Display client nodes as part of cluster topology display. (Varun via Vikram)
 
   AMBARI-359. invalid parameter java_needed during uninstall (Ramya via Vikram)

Modified: incubator/ambari/branches/ambari-186/hmc/js/txnUtils.js
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/ambari-186/hmc/js/txnUtils.js?rev=1347030&r1=1347029&r2=1347030&view=diff
==============================================================================
--- incubator/ambari/branches/ambari-186/hmc/js/txnUtils.js (original)
+++ incubator/ambari/branches/ambari-186/hmc/js/txnUtils.js Wed Jun  6 18:25:02 2012
@@ -60,12 +60,16 @@ function TxnProgressWidget( txnProgressC
   };
 
   var pdpResponseHandler = {
-    success: function (e,pdp) {
+    success: function (e, pdp) {
 
       /* What we're here to render. */
       var txnProgressMarkup = 
         '<img id=txnProgressLoadingImgId class=loadingImg src=../images/loading.gif />'; 
 
+      var noNeedForFurtherPolling = false;
+      var txnProgressStatusDivContent = '';
+      var txnProgressStatusDivCssClass = '';
+
       var txnProgress = e.response.meta.progress;
 
       /* Guard against race conditions where txnProgress is null because the 
@@ -73,6 +77,16 @@ function TxnProgressWidget( txnProgressC
        */
       if (txnProgress) {
 
+        /* The first time we get back meaningful progress data, pause the 
+         * automatic polling to avoid race conditions where response N+1 
+         * is made (and returns with fresh data) while request N hasn't 
+         * yet been fully processed.
+         *
+         * We'll unpause at the end, after we've performed the rendering
+         * of the updated states.
+         */
+        pdp.pause();
+
         var txnProgressStates = txnProgress.subTxns || [];
         globalYui.log(globalYui.Lang.dump(txnProgressStates));
 
@@ -181,9 +195,32 @@ function TxnProgressWidget( txnProgressC
             ( presentTxnProgressState.description, 'txnProgressStatePending' );
         }
 
-        var noNeedForFurtherPolling = false;
-        var txnProgressStatusDivContent = '';
-        var txnProgressStatusDivCssClass = '';
+        txnProgressMarkup += '</ul>';
+
+        /* Make sure we have some progress data to show - if not, 
+         * we'll just show a loading image until this is non-null.
+         *
+         * The additional check for txnProgress.processRunning is to account 
+         * for cases where there are no subTxns (because it's all a no-op at 
+         * the backend) - the loading image should only be shown as long as 
+         * the backend is still working; after that, we should break out of
+         * the loading image loop and let the user know that there was
+         * nothing to be done.
+         */
+        if( txnProgress.subTxns == null ) {
+          if( txnProgress.processRunning == 0 ) {
+            txnProgressMarkup = 
+              '<br/>' + 
+              '<div class=txnNoOpMsg>' + 
+                'Nothing to do for this transaction; enjoy the freebie!' +
+              '</div>' + 
+              '<br/>';
+          } 
+          else {
+            txnProgressMarkup = 
+              '<img id=txnProgressLoadingImgId class=loadingImg src=../images/loading.gif />';
+          }
+        }
 
         /* We can break this polling cycle in one of 2 ways: 
          * 
@@ -211,61 +248,44 @@ function TxnProgressWidget( txnProgressC
           txnProgressStatusDivContent = this.txnProgressStatusMessage.failure;
           txnProgressStatusDivCssClass = 'statusError';
         }
+      }
 
-        if( noNeedForFurtherPolling ) {
+      /* Render txnProgressMarkup before making any decisions about the
+       * future state of pdp. 
+       */
+      globalYui.log('About to generate markup: ' + txnProgressMarkup);
+      globalYui.one('#txnProgressDynamicRenderDivId').setContent( txnProgressMarkup );
+
+      /* And before checking out, decide whether we're done with this txn 
+       * or whether any more polling is required.
+       */
+      if (noNeedForFurtherPolling) {
 
-          /* We've made all the progress we could have, so stop polling. */
-          pdp.stop();
+        /* We've made all the progress we could have, so stop polling. */
+        pdp.stop();
 
-          var txnProgressStatusDiv = globalYui.one('#txnProgressStatusDivId');
-          
-          txnProgressStatusDiv.addClass(txnProgressStatusDivCssClass);
-          txnProgressStatusDiv.one('#txnProgressStatusMessageDivId').setContent(txnProgressStatusDivContent);
-          txnProgressStatusDiv.setStyle('display', 'block');
-
-          /* Run the post-completion fixups. */
-          if( txnProgressStatusDivCssClass == 'statusOk' ) {
-            if( this.txnProgressPostCompletionFixup.success ) {
-              this.txnProgressPostCompletionFixup.success(this);
-            }
-          }
-          else if( txnProgressStatusDivCssClass == 'statusError' ) {
-            if( this.txnProgressPostCompletionFixup.failure ) {
-              this.txnProgressPostCompletionFixup.failure(this);
-            }
+        var txnProgressStatusDiv = globalYui.one('#txnProgressStatusDivId');
+        
+        txnProgressStatusDiv.addClass(txnProgressStatusDivCssClass);
+        txnProgressStatusDiv.one('#txnProgressStatusMessageDivId').setContent(txnProgressStatusDivContent);
+        txnProgressStatusDiv.setStyle('display', 'block');
+
+        /* Run the post-completion fixups. */
+        if (txnProgressStatusDivCssClass == 'statusOk') {
+          if (this.txnProgressPostCompletionFixup.success) {
+            this.txnProgressPostCompletionFixup.success(this);
           }
         }
-
-        txnProgressMarkup += '</ul>';
-
-        /* Make sure we have some progress data to show - if not, 
-         * we'll just show a loading image until this is non-null.
-         *
-         * The additional check for txnProgress.processRunning is to account 
-         * for cases where there are no subTxns (because it's all a no-op at 
-         * the backend) - the loading image should only be shown as long as 
-         * the backend is still working; after that, we should break out of
-         * the loading image loop and let the user know that there was
-         * nothing to be done.
-         */
-        if( txnProgress.subTxns == null ) {
-          if( txnProgress.processRunning == 0 ) {
-            txnProgressMarkup = 
-              '<br/>' + 
-              '<div class=txnNoOpMsg>' + 
-                'Nothing to do for this transaction; enjoy the freebie!' +
-              '</div>' + 
-              '<br/>';
-          } 
-          else {
-            txnProgressMarkup = 
-              '<img id=txnProgressLoadingImgId class=loadingImg src=../images/loading.gif />';
+        else if (txnProgressStatusDivCssClass == 'statusError') {
+          if (this.txnProgressPostCompletionFixup.failure) {
+            this.txnProgressPostCompletionFixup.failure(this);
           }
         }
       }
-
-      globalYui.log('About to generate markup: ' + txnProgressMarkup);
-      globalYui.one('#txnProgressDynamicRenderDivId').setContent( txnProgressMarkup );
+      else {
+        /* There's still more progress to be made, so unpause. */
+        pdp.unPause();
+      }
 
     }.bind(this),
 

Modified: incubator/ambari/branches/ambari-186/hmc/js/utils.js
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/ambari-186/hmc/js/utils.js?rev=1347030&r1=1347029&r2=1347030&view=diff
==============================================================================
--- incubator/ambari/branches/ambari-186/hmc/js/utils.js (original)
+++ incubator/ambari/branches/ambari-186/hmc/js/utils.js Wed Jun  6 18:25:02 2012
@@ -350,6 +350,9 @@ function PeriodicDataPoller( dataSourceC
 
   this.responseHandler = responseHandler;
 
+  /* Of course, we're not paused when we start off. */
+  this.paused = false;
+
   this.dataSource = new globalYui.DataSource.IO ({
     source: this.dataSourceContext.source
   });
@@ -367,28 +370,37 @@ function PeriodicDataPoller( dataSourceC
     request: this.dataSourceContext.request,
     callback: {
       success: function (e) {
-        /* Reset our failure count every time we succeed. */
-        this.dataSourcePollFailureCount = 0;
 
-        /* Invoke user-pluggable code. */
-        if( this.responseHandler.success ) {
-          this.responseHandler.success( e, this );
-        }
+        /* Avoid race conditions in JS by not processing incoming responses 
+         * from the backend if the PDP is paused (which is our signal that
+         * a previous response is still in the middle of being processed). 
+         */
+        if( !(this.isPaused()) ) {
+          /* Reset our failure count every time we succeed. */
+          this.dataSourcePollFailureCount = 0;
 
+          /* Invoke user-pluggable code. */
+          if( this.responseHandler.success ) {
+            this.responseHandler.success( e, this );
+          }
+        }
       }.bind(this),
 
       failure: function (e) {
-        ++this.dataSourcePollFailureCount;
 
-        if( this.dataSourcePollFailureCount > this.dataSourceContext.maxFailedAttempts ) {
+        if( !(this.isPaused()) ) {
+          ++this.dataSourcePollFailureCount;
 
-          /* Invoke user-pluggable code. */
-          if( this.responseHandler.failure ) {
-            this.responseHandler.failure( e, this );
-          }
+          if( this.dataSourcePollFailureCount > this.dataSourceContext.maxFailedAttempts ) {
+
+            /* Invoke user-pluggable code. */
+            if( this.responseHandler.failure ) {
+              this.responseHandler.failure( e, this );
+            }
 
-          /* No point making any more attempts. */
-          this.stop();
+            /* No point making any more attempts. */
+            this.stop();
+          }
         }
       }.bind(this)
     }
@@ -398,15 +410,53 @@ function PeriodicDataPoller( dataSourceC
 /* Start polling. */
 PeriodicDataPoller.prototype.start = function() {
 
-  this.dataSourcePollHandle = this.dataSource.setInterval( this.dataSourceContext.pollInterval, this.dataSourcePollRequestContext );
+  this.dataSourcePollHandle = this.dataSource.setInterval
+    ( this.dataSourceContext.pollInterval, this.dataSourcePollRequestContext );
 }
 
 /* Stop polling. */
 PeriodicDataPoller.prototype.stop = function() {
 
+  /* Always unPause() during stop(), so the next start() won't be neutered. */
+  this.unPause();
   this.dataSource.clearInterval( this.dataSourcePollHandle );
 }
 
+/* When the PDP is paused, the polling continues on its regular fixed 
+ * interval, but this.responseHandler is not invoked, thus avoiding
+ * a race condition (at least) in JS.
+ *
+ * TODO XXX Improve upon this to not even make calls to the backend
+ * while not losing our periodicity.
+ */
+PeriodicDataPoller.prototype.pause = function() {
+
+  this.paused = true;
+}
+
+PeriodicDataPoller.prototype.unPause = function() {
+
+  this.paused = false;
+}
+
+PeriodicDataPoller.prototype.isPaused = function() {
+
+  return this.paused;
+}
+
+/* Perform a one-time poll. 
+ *
+ * Meant to be used when the polling is not at a set frequency (as with the 
+ * start()/stop() pair), and is instead meant to be under explicit 
+ * control of the application. 
+ */
+PeriodicDataPoller.prototype.pollOnce = function() {
+
+  globalYui.io(this.dataSourceContext.source + this.dataSourcePollRequestContext.request, {
+    on: this.dataSourcePollRequestContext.callback
+  });
+}
+
 function titleCase(word){
   return word.substr(0,1).toUpperCase() + word.substr(1).toLowerCase();
 }