Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/compass-connections/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"compass-preferences-model": "^2.31.4",
"hadron-app-registry": "^9.2.11",
"lodash": "^4.17.21",
"mongodb": "^6.12.0",
"mongodb-build-info": "^1.7.2",
"mongodb-connection-string-url": "^3.0.1",
"mongodb-data-service": "^22.23.12",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Reducer, AnyAction, Action } from 'redux';
import { createStore, applyMiddleware } from 'redux';
import type { ThunkAction } from 'redux-thunk';
import thunk from 'redux-thunk';
import type { ServerHeartbeatFailedEvent } from 'mongodb';
import {
getConnectionTitle,
type ConnectionInfo,
Expand Down Expand Up @@ -1507,6 +1508,46 @@ function isAtlasStreamsInstance(
}
}

const NonRetryableErrorCodes = [3000, 3003, 4004, 1008] as const;
const NonRetryableErrorDescriptionFallbacks: {
[code in typeof NonRetryableErrorCodes[number]]: string;
} = {
3000: 'Unauthorized',
3003: 'Forbidden',
4004: 'Not Found',
1008: 'Violated policy',
};

function isNonRetryableHeartbeatFailure(evt: ServerHeartbeatFailedEvent) {
return NonRetryableErrorCodes.some((code) =>
evt.failure.message.includes(`code: ${code},`)
);
}

function getDescriptionForNonRetryableError(error: Error): string {
// Give a description from the error message when provided, otherwise fallback
// to the generic error description.
const reason = error.message.match(/code: \d+, reason: (.*)$/)?.[1];
return reason && reason.length > 0
? reason
: NonRetryableErrorDescriptionFallbacks[
Number(
error.message.match(/code: (\d+),/)?.[1]
) as typeof NonRetryableErrorCodes[number]
] ?? 'Unknown';
}

const openConnectionClosedWithNonRetryableErrorToast = (
connectionInfo: ConnectionInfo,
error: Error
) => {
openToast(`non-retryable-error-encountered--${connectionInfo.id}`, {
title: `Unable to connect to ${getConnectionTitle(connectionInfo)}`,
description: `Reason: ${getDescriptionForNonRetryableError(error)}`,
variant: 'warning',
});
};

export const connect = (
connectionInfo: ConnectionInfo
): ConnectionsThunkAction<
Expand Down Expand Up @@ -1701,6 +1742,36 @@ const connectWithOptions = (
return;
}

let showedNonRetryableErrorToast = false;
if (connectionInfo.atlasMetadata) {
// When we're on cloud we listen for non-retry-able errors on failed server
// heartbeats. These can happen when:
// - A user's session has ended.
// - The user's roles have changed.
// - The cluster / group they are trying to connect to has since been deleted.
// When we encounter one we disconnect. This is to avoid polluting logs/metrics
// and to avoid constantly retrying to connect when we know it'll fail.
dataService.on(
'serverHeartbeatFailed',
(evt: ServerHeartbeatFailedEvent) => {
if (!isNonRetryableHeartbeatFailure(evt)) {
return;
}

if (!dataService.isConnected() || showedNonRetryableErrorToast) {
return;
}

openConnectionClosedWithNonRetryableErrorToast(
connectionInfo,
evt.failure
);
showedNonRetryableErrorToast = true;
void dataService.disconnect();
}
);
}

dataService.on('oidcAuthFailed', (error) => {
openToast('oidc-auth-failed', {
title: `Failed to authenticate for ${getConnectionTitle(
Expand Down
2 changes: 2 additions & 0 deletions packages/data-service/src/data-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export type ExplainExecuteOptions = ExecutionOptions & {

export interface DataServiceEventMap {
topologyDescriptionChanged: (evt: TopologyDescriptionChangedEvent) => void;
serverHeartbeatFailed: (evt: ServerHeartbeatFailedEvent) => void;
connectionInfoSecretsChanged: () => void;
close: () => void;
oidcAuthFailed: (error: string) => void;
Expand Down Expand Up @@ -2414,6 +2415,7 @@ class DataServiceImpl extends WithLogContext implements DataService {
}
);
}
this._emitter.emit('serverHeartbeatFailed', evt);
});

client.on('commandSucceeded', (evt: CommandSucceededEvent) => {
Expand Down
Loading