Commit 500bd88a authored by Muhammad Umair Khan's avatar Muhammad Umair Khan
Browse files

Resolves #1: Fix UI race condition causing network dropdown to revert during termination

When you selected "Select a network", the configuration-pane.js component told the app-container.js "Hey, we are terminating!"
But the app-container.js left the global Redux scenario state completely intact while it waited for the meep-virt-engine pods to finish terminating in the background.
Because the global Redux state wasn't cleared out immediately, configuration-pane.js was looking at the global state and saying, "Wait, there's still an active scenario in global state, but my dropdown is set to 'Select a network'. Let me sync myself with the global state!"
And BAM—it forcefully snapped the dropdown back to the simulated network.
Then, seconds later, the backend finally finished deleting pods, returned a status: inactive payload, and app-container.js finally cleared the global state.
Fix:
- **Immediate State Clearing:** Modified `app-container.js` to instantly nullify the global scenario state in Redux the exact millisecond a termination is triggered. This provides immediate visual feedback (clearing API tables) and removes the old data that was causing the auto-sync.
- **Polling Suppression:** Added a guard in `app-container.js` to completely ignore polled active scenario data as long as `terminationInProgressCount` is active.
- **Dropdown Sync Guard:** Added a check in `configuration-pane.js` to prevent the dropdown from attempting to auto-restore itself during an active termination.
- **Timeout Extension:** Increased the fallback `terminationInProgressCount` timeout from 5 seconds to 30 seconds to safely accommodate large scenario teardowns.
parent 5cbc38d4
Loading
Loading
Loading
Loading
+1531 −0

File added.

Preview size limit exceeded, changes collapsed.

+1 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import log from './util/logger';
var loadedState = loadState();

// Redux logger middleware
// eslint-disable-next-line no-unused-vars
const logger = store => next => action => {
  log.debug(`logger - action: ${action.type}. payload: `, action.payload);
  // log.trace('state: ', store.getState()); // Uncomment for detailed state logging
+14 −0
Original line number Diff line number Diff line
@@ -270,6 +270,15 @@ class AppContainer extends Component {
    if (this.props.page === PAGE_SANDBOX && prevProps.page !== PAGE_SANDBOX) {
      this.refreshApiConsole = true;
    }

    // Clear scenario immediately when termination starts to prevent UI from temporarily restoring the old scenario
    if (this.props.terminationInProgressCount > 0 && prevProps.terminationInProgressCount === -1) {
      this.updateScenario(null);
      this.props.changeApiTable(null);
      this.props.changeApiDetailedData(null);
      this.props.changeAppInstanceTable([]);
      this.props.changeSvcTable([]);
    }
  }

  // Timers
@@ -801,6 +810,11 @@ class AppContainer extends Component {
    }

    // Store & Process deployed scenario
    if (this.props.terminationInProgressCount !== -1) {
      // Ignore active scenario polling while termination is in progress
      return;
    }

    if (this.props.activationInProgressCount === -1) {
      this.updateScenario(data);
    } else {
+3 −4
Original line number Diff line number Diff line
@@ -12,7 +12,6 @@ import { Typography } from '@rmwc/typography';
import AppInstanceTable from './app-instance-table';

import log from '../../util/logger';
import { LOG_TYPES } from '../../util/log-types';

import {
  deepCopy
@@ -88,7 +87,7 @@ class ConfigPane extends Component {

  componentDidUpdate(prevProps) {
    // Get configured scenario if activated by another browser
    if (this.props.activationInProgressCount === -1) {
    if (this.props.activationInProgressCount === -1 && this.props.terminationInProgressCount === -1) {
      if (this.props.scenario !== null && this.props.scenario.name !== NO_SCENARIO_NAME) {
        if (this.props.networkFileSelected !== this.props.scenario.name) {
          this.getScenario(this.props.scenario.name, false);
@@ -206,7 +205,7 @@ class ConfigPane extends Component {
        this.props.changeActivationInProgressCount(10);
        // Terminate running scenario before acivating new scenario
        // NOTE: new scenario will be activated when old one has been fully removed
        this.props.changeTerminationInProgressCount(5);
        this.props.changeTerminationInProgressCount(30);
        this.props.activateApi.terminateScenario((error, data, response) => {
          this.terminateScenarioCb(error, data, response);
        });
@@ -219,7 +218,7 @@ class ConfigPane extends Component {
      }
    } else {
      // Terminate scenario
      this.props.changeTerminationInProgressCount(5);
      this.props.changeTerminationInProgressCount(30);
      this.props.activateApi.terminateScenario((error, data, response) => {
        this.terminateScenarioCb(error, data, response);
      });