<NME> RelayOperationTracker.js
<BEF> /**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict-local
 * @format
 * @oncall relay
 */

'use strict';
import type {RequestIdentifier} from '../util/getRequestIdentifier';

import type {RequestDescriptor} from './RelayStoreTypes';

import type {RequestDescriptor} from './RelayStoreTypes';

class RelayOperationTracker {
  _ownersToPendingOperations: Map<RequestDescriptor, Set<RequestDescriptor>>;
  _pendingOperationsToOwners: Map<RequestDescriptor, Set<RequestDescriptor>>;
  _ownersToPromise: Map<
    RequestDescriptor,
    {|promise: Promise<void>, resolve: () => void|},
  >;

  constructor() {
    this._ownersToPendingOperations = new Map();
    this._pendingOperationsToOwners = new Map();
    this._ownersToPromise = new Map();
  }

  /**
    this._ownersToPendingPromise = new Map();
  }

  /**
   * Update the map of current processing operations with the set of
   * affected owners and notify subscribers
   */
    if (affectedOwners.size === 0) {
      return;
    }
    const newlyAffectedOwners = new Set();
    for (const owner of affectedOwners) {
      const pendingOperationsAffectingOwner = this._ownersToPendingOperations.get(
        owner,
      );
      if (pendingOperationsAffectingOwner != null) {
        // In this case the `owner` already affected by some operations
        // We just need to detect, is it the same operation that we already
        // have in the list, or it's a new operation
        if (!pendingOperationsAffectingOwner.has(pendingOperation)) {
          pendingOperationsAffectingOwner.add(pendingOperation);
          newlyAffectedOwners.add(owner);
        }
      } else {
        // This is a new `owner` that is affected by the operation
        this._ownersToPendingOperations.set(owner, new Set([pendingOperation]));
        newlyAffectedOwners.add(owner);
      }
    }

    // No new owners were affected by this operation, we may stop here
    if (newlyAffectedOwners.size === 0) {
      return;
    }

    // But, if some owners were affected we need to add them to
    // the `_pendingOperationsToOwners` set
    const ownersAffectedByOperation =
      this._pendingOperationsToOwners.get(pendingOperation) || new Set();

    for (const owner of newlyAffectedOwners) {
      this._resolveOwnerResolvers(owner);
      ownersAffectedByOperation.add(owner);
    }
    this._pendingOperationsToOwners.set(
      pendingOperation,
      ownersAffectedByOperation,
    );
  }

      pendingOperationIdentifier,
      ownersAffectedByPendingOperation,
   * from all tracking maps
   */
  complete(pendingOperation: RequestDescriptor): void {
    const affectedOwners = this._pendingOperationsToOwners.get(
      pendingOperation,
    );
    if (affectedOwners == null) {
      return;
    }
    // These were the owners affected only by `pendingOperation`
    const completedOwners = new Set();

    // These were the owners affected by `pendingOperation`
    // and some other operations
    const updatedOwners = new Set();
    for (const owner of affectedOwners) {
      const pendingOperationsAffectingOwner = this._ownersToPendingOperations.get(
        owner,
      );
      if (!pendingOperationsAffectingOwner) {
        continue;
      }
      pendingOperationsAffectingOwner.delete(pendingOperation);
      if (pendingOperationsAffectingOwner.size > 0) {
        updatedOwners.add(owner);
      } else {
        completedOwners.add(owner);
      }
    }

    // Complete subscriptions for all owners, affected by `pendingOperation`
    for (const owner of completedOwners) {
      this._resolveOwnerResolvers(owner);
      this._ownersToPendingOperations.delete(owner);
    }

    // Update all owner that were updated by `pendingOperation` but still
    // are affected by other operations
    for (const owner of updatedOwners) {
      this._resolveOwnerResolvers(owner);
    }

    // Finally, remove pending operation
    this._pendingOperationsToOwners.delete(pendingOperation);
  }

  _resolveOwnerResolvers(owner: RequestDescriptor): void {
    const promiseEntry = this._ownersToPromise.get(owner);
    if (promiseEntry != null) {
      promiseEntry.resolve();
    }
    this._ownersToPromise.delete(owner);
  }

  getPromiseForPendingOperationsAffectingOwner(
    owner: RequestDescriptor,
  ): Promise<void> | null {
    if (!this._ownersToPendingOperations.has(owner)) {
      return null;
    }
    const cachedPromiseEntry = this._ownersToPromise.get(owner);
    if (cachedPromiseEntry != null) {
      return cachedPromiseEntry.promise;
    }
      return null;
    }

    const cachedPromiseEntry =
      this._ownersToPendingPromise.get(ownerIdentifier);
    if (cachedPromiseEntry != null) {
      'RelayOperationTracker: Expected resolver to be defined. If you' +
        'are seeing this, it is likely a bug in Relay.',
    );
    this._ownersToPromise.set(owner, {promise, resolve});
    return promise;
  }
}
      resolve = r;
    });
    invariant(
      resolve != null,
      'RelayOperationTracker: Expected resolver to be defined. If you' +
        'are seeing this, it is likely a bug in Relay.',
    );
    const pendingOperations = Array.from(pendingOperationsForOwner.values());
    this._ownersToPendingPromise.set(ownerIdentifier, {
      promise,
      resolve,
      pendingOperations,
    });
    return {promise, pendingOperations};
  }
}

module.exports = RelayOperationTracker;

<MSG> refactor RelayOperationTracker & getPromiseForActiveRequest (#3348)

Summary:
This refactor optimizes memory management in order to store only the identifier of the owner and not the whole object.

Pull Request resolved: https://github.com/facebook/relay/pull/3348

Reviewed By: josephsavona

Differential Revision: D26313696

Pulled By: alunyov

fbshipit-source-id: 7ade32b63d935b926e0f747fdafd213052caa8f2

<DFF> @@ -17,17 +17,17 @@ const invariant = require('invariant');
 import type {RequestDescriptor} from './RelayStoreTypes';
 
 class RelayOperationTracker {
-  _ownersToPendingOperations: Map<RequestDescriptor, Set<RequestDescriptor>>;
-  _pendingOperationsToOwners: Map<RequestDescriptor, Set<RequestDescriptor>>;
-  _ownersToPromise: Map<
-    RequestDescriptor,
+  _ownersToPendingOperationsIdentifier: Map<string, Set<string>>;
+  _pendingOperationsToOwnersIdentifier: Map<string, Set<string>>;
+  _ownersIdentifierToPromise: Map<
+    string,
     {|promise: Promise<void>, resolve: () => void|},
   >;
 
   constructor() {
-    this._ownersToPendingOperations = new Map();
-    this._pendingOperationsToOwners = new Map();
-    this._ownersToPromise = new Map();
+    this._ownersToPendingOperationsIdentifier = new Map();
+    this._pendingOperationsToOwnersIdentifier = new Map();
+    this._ownersIdentifierToPromise = new Map();
   }
 
   /**
@@ -41,43 +41,50 @@ class RelayOperationTracker {
     if (affectedOwners.size === 0) {
       return;
     }
-    const newlyAffectedOwners = new Set();
+    const pendingOperationIdentifier = pendingOperation.identifier;
+    const newlyAffectedOwnersIdentifier = new Set();
     for (const owner of affectedOwners) {
-      const pendingOperationsAffectingOwner = this._ownersToPendingOperations.get(
-        owner,
+      const ownerIdentifier = owner.identifier;
+      const pendingOperationsAffectingOwner = this._ownersToPendingOperationsIdentifier.get(
+        ownerIdentifier,
       );
       if (pendingOperationsAffectingOwner != null) {
-        // In this case the `owner` already affected by some operations
+        // In this case the `ownerIdentifier` already affected by some operations
         // We just need to detect, is it the same operation that we already
         // have in the list, or it's a new operation
-        if (!pendingOperationsAffectingOwner.has(pendingOperation)) {
-          pendingOperationsAffectingOwner.add(pendingOperation);
-          newlyAffectedOwners.add(owner);
+        if (!pendingOperationsAffectingOwner.has(pendingOperationIdentifier)) {
+          pendingOperationsAffectingOwner.add(pendingOperationIdentifier);
+          newlyAffectedOwnersIdentifier.add(ownerIdentifier);
         }
       } else {
-        // This is a new `owner` that is affected by the operation
-        this._ownersToPendingOperations.set(owner, new Set([pendingOperation]));
-        newlyAffectedOwners.add(owner);
+        // This is a new `ownerIdentifier` that is affected by the operation
+        this._ownersToPendingOperationsIdentifier.set(
+          ownerIdentifier,
+          new Set([pendingOperationIdentifier]),
+        );
+        newlyAffectedOwnersIdentifier.add(ownerIdentifier);
       }
     }
 
     // No new owners were affected by this operation, we may stop here
-    if (newlyAffectedOwners.size === 0) {
+    if (newlyAffectedOwnersIdentifier.size === 0) {
       return;
     }
 
     // But, if some owners were affected we need to add them to
-    // the `_pendingOperationsToOwners` set
-    const ownersAffectedByOperation =
-      this._pendingOperationsToOwners.get(pendingOperation) || new Set();
-
-    for (const owner of newlyAffectedOwners) {
-      this._resolveOwnerResolvers(owner);
-      ownersAffectedByOperation.add(owner);
+    // the `_pendingOperationsToOwnersIdentifier` set
+    const ownersAffectedByOperationIdentifier =
+      this._pendingOperationsToOwnersIdentifier.get(
+        pendingOperationIdentifier,
+      ) || new Set();
+
+    for (const ownerIdentifier of newlyAffectedOwnersIdentifier) {
+      this._resolveOwnerResolvers(ownerIdentifier);
+      ownersAffectedByOperationIdentifier.add(ownerIdentifier);
     }
-    this._pendingOperationsToOwners.set(
-      pendingOperation,
-      ownersAffectedByOperation,
+    this._pendingOperationsToOwnersIdentifier.set(
+      pendingOperationIdentifier,
+      ownersAffectedByOperationIdentifier,
     );
   }
 
@@ -86,64 +93,70 @@ class RelayOperationTracker {
    * from all tracking maps
    */
   complete(pendingOperation: RequestDescriptor): void {
-    const affectedOwners = this._pendingOperationsToOwners.get(
-      pendingOperation,
+    const pendingOperationIdentifier = pendingOperation.identifier;
+    const affectedOwnersIdentifier = this._pendingOperationsToOwnersIdentifier.get(
+      pendingOperationIdentifier,
     );
-    if (affectedOwners == null) {
+    if (affectedOwnersIdentifier == null) {
       return;
     }
-    // These were the owners affected only by `pendingOperation`
-    const completedOwners = new Set();
+    // These were the owners affected only by `pendingOperationIdentifier`
+    const completedOwnersIdentifier = new Set();
 
-    // These were the owners affected by `pendingOperation`
+    // These were the owners affected by `pendingOperationIdentifier`
     // and some other operations
-    const updatedOwners = new Set();
-    for (const owner of affectedOwners) {
-      const pendingOperationsAffectingOwner = this._ownersToPendingOperations.get(
-        owner,
+    const updatedOwnersIdentifier = new Set();
+    for (const ownerIdentifier of affectedOwnersIdentifier) {
+      const pendingOperationsAffectingOwner = this._ownersToPendingOperationsIdentifier.get(
+        ownerIdentifier,
       );
       if (!pendingOperationsAffectingOwner) {
         continue;
       }
-      pendingOperationsAffectingOwner.delete(pendingOperation);
+      pendingOperationsAffectingOwner.delete(pendingOperationIdentifier);
       if (pendingOperationsAffectingOwner.size > 0) {
-        updatedOwners.add(owner);
+        updatedOwnersIdentifier.add(ownerIdentifier);
       } else {
-        completedOwners.add(owner);
+        completedOwnersIdentifier.add(ownerIdentifier);
       }
     }
 
-    // Complete subscriptions for all owners, affected by `pendingOperation`
-    for (const owner of completedOwners) {
-      this._resolveOwnerResolvers(owner);
-      this._ownersToPendingOperations.delete(owner);
+    // Complete subscriptions for all owners, affected by `pendingOperationIdentifier`
+    for (const ownerIdentifier of completedOwnersIdentifier) {
+      this._resolveOwnerResolvers(ownerIdentifier);
+      this._ownersToPendingOperationsIdentifier.delete(ownerIdentifier);
     }
 
-    // Update all owner that were updated by `pendingOperation` but still
+    // Update all ownerIdentifier that were updated by `pendingOperationIdentifier` but still
     // are affected by other operations
-    for (const owner of updatedOwners) {
-      this._resolveOwnerResolvers(owner);
+    for (const ownerIdentifier of updatedOwnersIdentifier) {
+      this._resolveOwnerResolvers(ownerIdentifier);
     }
 
-    // Finally, remove pending operation
-    this._pendingOperationsToOwners.delete(pendingOperation);
+    // Finally, remove pending operation identifier
+    this._pendingOperationsToOwnersIdentifier.delete(
+      pendingOperationIdentifier,
+    );
   }
 
-  _resolveOwnerResolvers(owner: RequestDescriptor): void {
-    const promiseEntry = this._ownersToPromise.get(owner);
+  _resolveOwnerResolvers(ownerIdentifier: string): void {
+    const promiseEntry = this._ownersIdentifierToPromise.get(ownerIdentifier);
     if (promiseEntry != null) {
       promiseEntry.resolve();
     }
-    this._ownersToPromise.delete(owner);
+    this._ownersIdentifierToPromise.delete(ownerIdentifier);
   }
 
   getPromiseForPendingOperationsAffectingOwner(
     owner: RequestDescriptor,
   ): Promise<void> | null {
-    if (!this._ownersToPendingOperations.has(owner)) {
+    const ownerIdentifier = owner.identifier;
+    if (!this._ownersToPendingOperationsIdentifier.has(ownerIdentifier)) {
       return null;
     }
-    const cachedPromiseEntry = this._ownersToPromise.get(owner);
+    const cachedPromiseEntry = this._ownersIdentifierToPromise.get(
+      ownerIdentifier,
+    );
     if (cachedPromiseEntry != null) {
       return cachedPromiseEntry.promise;
     }
@@ -156,7 +169,7 @@ class RelayOperationTracker {
       'RelayOperationTracker: Expected resolver to be defined. If you' +
         'are seeing this, it is likely a bug in Relay.',
     );
-    this._ownersToPromise.set(owner, {promise, resolve});
+    this._ownersIdentifierToPromise.set(ownerIdentifier, {promise, resolve});
     return promise;
   }
 }

{'addition_count': 69, 'commit_subject': 'refactor RelayOperationTracker & getPromiseForActiveRequest (#3348)', 'deletion_count': 56, 'file_extension': '.js', 'lang': 'js', 'license': 'mit', 'repo_name': 'facebook/relay'}