Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/actioncable_connection_monitor.js')
-rw-r--r--app/assets/javascripts/actioncable_connection_monitor.js142
1 files changed, 142 insertions, 0 deletions
diff --git a/app/assets/javascripts/actioncable_connection_monitor.js b/app/assets/javascripts/actioncable_connection_monitor.js
new file mode 100644
index 00000000000..fc4e436c7fb
--- /dev/null
+++ b/app/assets/javascripts/actioncable_connection_monitor.js
@@ -0,0 +1,142 @@
+/* eslint-disable no-restricted-globals */
+
+import { logger } from '@rails/actioncable';
+
+// This is based on https://github.com/rails/rails/blob/5a477890c809d4a17dc0dede43c6b8cef81d8175/actioncable/app/javascript/action_cable/connection_monitor.js
+// so that we can take advantage of the improved reconnection logic. We can remove this once we upgrade @rails/actioncable to a version that includes this.
+
+// Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting
+// revival reconnections if things go astray. Internal class, not intended for direct user manipulation.
+
+const now = () => new Date().getTime();
+
+const secondsSince = (time) => (now() - time) / 1000;
+class ConnectionMonitor {
+ constructor(connection) {
+ this.visibilityDidChange = this.visibilityDidChange.bind(this);
+ this.connection = connection;
+ this.reconnectAttempts = 0;
+ }
+
+ start() {
+ if (!this.isRunning()) {
+ this.startedAt = now();
+ delete this.stoppedAt;
+ this.startPolling();
+ addEventListener('visibilitychange', this.visibilityDidChange);
+ logger.log(
+ `ConnectionMonitor started. stale threshold = ${this.constructor.staleThreshold} s`,
+ );
+ }
+ }
+
+ stop() {
+ if (this.isRunning()) {
+ this.stoppedAt = now();
+ this.stopPolling();
+ removeEventListener('visibilitychange', this.visibilityDidChange);
+ logger.log('ConnectionMonitor stopped');
+ }
+ }
+
+ isRunning() {
+ return this.startedAt && !this.stoppedAt;
+ }
+
+ recordPing() {
+ this.pingedAt = now();
+ }
+
+ recordConnect() {
+ this.reconnectAttempts = 0;
+ this.recordPing();
+ delete this.disconnectedAt;
+ logger.log('ConnectionMonitor recorded connect');
+ }
+
+ recordDisconnect() {
+ this.disconnectedAt = now();
+ logger.log('ConnectionMonitor recorded disconnect');
+ }
+
+ // Private
+
+ startPolling() {
+ this.stopPolling();
+ this.poll();
+ }
+
+ stopPolling() {
+ clearTimeout(this.pollTimeout);
+ }
+
+ poll() {
+ this.pollTimeout = setTimeout(() => {
+ this.reconnectIfStale();
+ this.poll();
+ }, this.getPollInterval());
+ }
+
+ getPollInterval() {
+ const { staleThreshold, reconnectionBackoffRate } = this.constructor;
+ const backoff = (1 + reconnectionBackoffRate) ** Math.min(this.reconnectAttempts, 10);
+ const jitterMax = this.reconnectAttempts === 0 ? 1.0 : reconnectionBackoffRate;
+ const jitter = jitterMax * Math.random();
+ return staleThreshold * 1000 * backoff * (1 + jitter);
+ }
+
+ reconnectIfStale() {
+ if (this.connectionIsStale()) {
+ logger.log(
+ `ConnectionMonitor detected stale connection. reconnectAttempts = ${
+ this.reconnectAttempts
+ }, time stale = ${secondsSince(this.refreshedAt)} s, stale threshold = ${
+ this.constructor.staleThreshold
+ } s`,
+ );
+ this.reconnectAttempts += 1;
+ if (this.disconnectedRecently()) {
+ logger.log(
+ `ConnectionMonitor skipping reopening recent disconnect. time disconnected = ${secondsSince(
+ this.disconnectedAt,
+ )} s`,
+ );
+ } else {
+ logger.log('ConnectionMonitor reopening');
+ this.connection.reopen();
+ }
+ }
+ }
+
+ get refreshedAt() {
+ return this.pingedAt ? this.pingedAt : this.startedAt;
+ }
+
+ connectionIsStale() {
+ return secondsSince(this.refreshedAt) > this.constructor.staleThreshold;
+ }
+
+ disconnectedRecently() {
+ return (
+ this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold
+ );
+ }
+
+ visibilityDidChange() {
+ if (document.visibilityState === 'visible') {
+ setTimeout(() => {
+ if (this.connectionIsStale() || !this.connection.isOpen()) {
+ logger.log(
+ `ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = ${document.visibilityState}`,
+ );
+ this.connection.reopen();
+ }
+ }, 200);
+ }
+ }
+}
+
+ConnectionMonitor.staleThreshold = 6; // Server::Connections::BEAT_INTERVAL * 2 (missed two pings)
+ConnectionMonitor.reconnectionBackoffRate = 0.15;
+
+export default ConnectionMonitor;