Throttle star requests.

This solution is not ideal, because ideally we simply do not send star
updates if multiple toggles follow each other.

Additionally, it's still possible to break if you click sufficiently
fast and more than twice. That being said, this is a good enough
stop-gap until this gets stored in the model and the model can ensure to
only save every so often.

Change-Id: Ic9b83b6979a62a840b0ef092f9bffd82b0a0b4ca
Release-Notes: n/a
Google-Bug-Id: b/141449307
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
index 746ecf3..0d0c88f 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
@@ -143,7 +143,7 @@
 import {ParsedChangeInfo} from '../../types/types';
 import {ErrorCallback} from '../../api/rest';
 import {addDraftProp, DraftInfo} from '../../utils/comment-util';
-import {BaseScheduler} from '../scheduler/scheduler';
+import {BaseScheduler, Scheduler} from '../scheduler/scheduler';
 import {MaxInFlightScheduler} from '../scheduler/max-in-flight-scheduler';
 import {escapeAndWrapSearchOperatorValue} from '../../utils/string-util';
 
@@ -270,6 +270,11 @@
 function createWriteScheduler() {
   return new MaxInFlightScheduler<Response>(new BaseScheduler<Response>(), 5);
 }
+
+function createSerializingScheduler() {
+  return new MaxInFlightScheduler<Response>(new BaseScheduler<Response>(), 1);
+}
+
 export class GrRestApiServiceImpl implements RestApiService, Finalizable {
   readonly _cache = siteBasedCache; // Shared across instances.
 
@@ -286,6 +291,9 @@
   // Private, but used in tests.
   readonly _restApiHelper: GrRestApiHelper;
 
+  // Used to serialize requests for certain RPCs
+  readonly _serialScheduler: Scheduler<Response>;
+
   constructor(private readonly authService: AuthService) {
     this._restApiHelper = new GrRestApiHelper(
       this._cache,
@@ -294,6 +302,7 @@
       createReadScheduler(),
       createWriteScheduler()
     );
+    this._serialScheduler = createSerializingScheduler();
   }
 
   finalize() {}
@@ -2232,11 +2241,13 @@
     return this.getFromProjectLookup(changeNum).then(project => {
       const encodedRepoName = project ? encodeURIComponent(project) + '~' : '';
       const url = `/accounts/self/starred.changes/${encodedRepoName}${changeNum}`;
-      return this._restApiHelper.send({
-        method: starred ? HttpMethod.PUT : HttpMethod.DELETE,
-        url,
-        anonymizedUrl: '/accounts/self/starred.changes/*',
-      });
+      return this._serialScheduler.schedule(() =>
+        this._restApiHelper.send({
+          method: starred ? HttpMethod.PUT : HttpMethod.DELETE,
+          url,
+          anonymizedUrl: '/accounts/self/starred.changes/*',
+        })
+      );
     });
   }