CoolFace
Apppublic

opusdev/vector-similarity-api

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes
server.ts578 linesDownload Raw Back to sdam
1import type { Document } from '../bson';2import { type AutoEncrypter } from '../client-side-encryption/auto_encrypter';3import { type CommandOptions, Connection } from '../cmap/connection';4import {5  ConnectionPool,6  type ConnectionPoolEvents,7  type ConnectionPoolOptions8} from '../cmap/connection_pool';9import { PoolClearedError } from '../cmap/errors';10import {11  APM_EVENTS,12  CLOSED,13  CMAP_EVENTS,14  CONNECT,15  DESCRIPTION_RECEIVED,16  ENDED,17  HEARTBEAT_EVENTS,18  SERVER_HEARTBEAT_FAILED,19  SERVER_HEARTBEAT_STARTED,20  SERVER_HEARTBEAT_SUCCEEDED21} from '../constants';22import {23  type AnyError,24  isNodeShuttingDownError,25  isSDAMUnrecoverableError,26  MONGODB_ERROR_CODES,27  MongoError,28  MongoErrorLabel,29  MongoNetworkError,30  MongoNetworkTimeoutError,31  MongoRuntimeError,32  MongoServerClosedError,33  type MongoServerError,34  needsRetryableWriteLabel35} from '../error';36import type { ServerApi } from '../mongo_client';37import { type Abortable, TypedEventEmitter } from '../mongo_types';38import { AggregateOperation } from '../operations/aggregate';39import type { GetMoreOptions } from '../operations/get_more';40import { type AbstractOperation } from '../operations/operation';41import type { ClientSession } from '../sessions';42import { type TimeoutContext } from '../timeout';43import { isTransactionCommand } from '../transactions';44import {45  abortable,46  type EventEmitterWithState,47  makeStateMachine,48  maxWireVersion,49  noop,50  squashError,51  supportsRetryableWrites52} from '../utils';53import { throwIfWriteConcernError } from '../write_concern';54import {55  type ClusterTime,56  STATE_CLOSED,57  STATE_CLOSING,58  STATE_CONNECTED,59  STATE_CONNECTING,60  TopologyType61} from './common';62import type {63  ServerHeartbeatFailedEvent,64  ServerHeartbeatStartedEvent,65  ServerHeartbeatSucceededEvent66} from './events';67import { Monitor, type MonitorOptions } from './monitor';68import { compareTopologyVersion, ServerDescription } from './server_description';69import { MIN_SECONDARY_WRITE_WIRE_VERSION } from './server_selection';70import type { Topology } from './topology';71 72const stateTransition = makeStateMachine({73  [STATE_CLOSED]: [STATE_CLOSED, STATE_CONNECTING],74  [STATE_CONNECTING]: [STATE_CONNECTING, STATE_CLOSING, STATE_CONNECTED, STATE_CLOSED],75  [STATE_CONNECTED]: [STATE_CONNECTED, STATE_CLOSING, STATE_CLOSED],76  [STATE_CLOSING]: [STATE_CLOSING, STATE_CLOSED]77});78 79/** @internal */80export type ServerOptions = Omit<ConnectionPoolOptions, 'id' | 'generation' | 'hostAddress'> &81  MonitorOptions;82 83/** @internal */84export interface ServerPrivate {85  /** The server description for this server */86  description: ServerDescription;87  /** A copy of the options used to construct this instance */88  options: ServerOptions;89  /** The current state of the Server */90  state: string;91  /** MongoDB server API version */92  serverApi?: ServerApi;93  /** A count of the operations currently running against the server. */94  operationCount: number;95}96 97/** @public */98export type ServerEvents = {99  serverHeartbeatStarted(event: ServerHeartbeatStartedEvent): void;100  serverHeartbeatSucceeded(event: ServerHeartbeatSucceededEvent): void;101  serverHeartbeatFailed(event: ServerHeartbeatFailedEvent): void;102  /** Top level MongoClient doesn't emit this so it is marked: @internal */103  connect(server: Server): void;104  descriptionReceived(description: ServerDescription): void;105  closed(): void;106  ended(): void;107} & ConnectionPoolEvents &108  EventEmitterWithState;109 110/** @internal */111export type ServerCommandOptions = Omit<CommandOptions, 'timeoutContext' | 'socketTimeoutMS'> & {112  timeoutContext: TimeoutContext;113  returnFieldSelector?: Document | null;114} & Abortable;115 116/** @internal */117export class Server extends TypedEventEmitter<ServerEvents> {118  /** @internal */119  s: ServerPrivate;120  /** @internal */121  topology: Topology;122  /** @internal */123  pool: ConnectionPool;124  serverApi?: ServerApi;125  hello?: Document;126  monitor: Monitor | null;127 128  /** @event */129  static readonly SERVER_HEARTBEAT_STARTED = SERVER_HEARTBEAT_STARTED;130  /** @event */131  static readonly SERVER_HEARTBEAT_SUCCEEDED = SERVER_HEARTBEAT_SUCCEEDED;132  /** @event */133  static readonly SERVER_HEARTBEAT_FAILED = SERVER_HEARTBEAT_FAILED;134  /** @event */135  static readonly CONNECT = CONNECT;136  /** @event */137  static readonly DESCRIPTION_RECEIVED = DESCRIPTION_RECEIVED;138  /** @event */139  static readonly CLOSED = CLOSED;140  /** @event */141  static readonly ENDED = ENDED;142 143  /**144   * Create a server145   */146  constructor(topology: Topology, description: ServerDescription, options: ServerOptions) {147    super();148    this.on('error', noop);149 150    this.serverApi = options.serverApi;151 152    const poolOptions = { hostAddress: description.hostAddress, ...options };153 154    this.topology = topology;155    this.pool = new ConnectionPool(this, poolOptions);156 157    this.s = {158      description,159      options,160      state: STATE_CLOSED,161      operationCount: 0162    };163 164    for (const event of [...CMAP_EVENTS, ...APM_EVENTS]) {165      this.pool.on(event, (e: any) => this.emit(event, e));166    }167 168    this.pool.on(Connection.CLUSTER_TIME_RECEIVED, (clusterTime: ClusterTime) => {169      this.clusterTime = clusterTime;170    });171 172    if (this.loadBalanced) {173      this.monitor = null;174      // monitoring is disabled in load balancing mode175      return;176    }177 178    // create the monitor179    this.monitor = new Monitor(this, this.s.options);180 181    for (const event of HEARTBEAT_EVENTS) {182      this.monitor.on(event, (e: any) => this.emit(event, e));183    }184 185    this.monitor.on('resetServer', (error: MongoServerError) => markServerUnknown(this, error));186    this.monitor.on(Server.SERVER_HEARTBEAT_SUCCEEDED, (event: ServerHeartbeatSucceededEvent) => {187      this.emit(188        Server.DESCRIPTION_RECEIVED,189        new ServerDescription(this.description.hostAddress, event.reply, {190          roundTripTime: this.monitor?.roundTripTime,191          minRoundTripTime: this.monitor?.minRoundTripTime192        })193      );194 195      if (this.s.state === STATE_CONNECTING) {196        stateTransition(this, STATE_CONNECTED);197        this.emit(Server.CONNECT, this);198      }199    });200  }201 202  get clusterTime(): ClusterTime | undefined {203    return this.topology.clusterTime;204  }205 206  set clusterTime(clusterTime: ClusterTime | undefined) {207    this.topology.clusterTime = clusterTime;208  }209 210  get description(): ServerDescription {211    return this.s.description;212  }213 214  get name(): string {215    return this.s.description.address;216  }217 218  get autoEncrypter(): AutoEncrypter | undefined {219    if (this.s.options && this.s.options.autoEncrypter) {220      return this.s.options.autoEncrypter;221    }222    return;223  }224 225  get loadBalanced(): boolean {226    return this.topology.description.type === TopologyType.LoadBalanced;227  }228 229  /**230   * Initiate server connect231   */232  connect(): void {233    if (this.s.state !== STATE_CLOSED) {234      return;235    }236 237    stateTransition(this, STATE_CONNECTING);238 239    // If in load balancer mode we automatically set the server to240    // a load balancer. It never transitions out of this state and241    // has no monitor.242    if (!this.loadBalanced) {243      this.monitor?.connect();244    } else {245      stateTransition(this, STATE_CONNECTED);246      this.emit(Server.CONNECT, this);247    }248  }249 250  closeCheckedOutConnections() {251    return this.pool.closeCheckedOutConnections();252  }253 254  /** Destroy the server connection */255  close(): void {256    if (this.s.state === STATE_CLOSED) {257      return;258    }259 260    stateTransition(this, STATE_CLOSING);261 262    if (!this.loadBalanced) {263      this.monitor?.close();264    }265 266    this.pool.close();267    stateTransition(this, STATE_CLOSED);268    this.emit('closed');269  }270 271  /**272   * Immediately schedule monitoring of this server. If there already an attempt being made273   * this will be a no-op.274   */275  requestCheck(): void {276    if (!this.loadBalanced) {277      this.monitor?.requestCheck();278    }279  }280 281  public async command<TResult>(282    operation: AbstractOperation<TResult>,283    timeoutContext: TimeoutContext284  ): Promise<InstanceType<typeof operation.SERVER_COMMAND_RESPONSE_TYPE>> {285    if (this.s.state === STATE_CLOSING || this.s.state === STATE_CLOSED) {286      throw new MongoServerClosedError();287    }288    const session = operation.session;289 290    let conn = session?.pinnedConnection;291 292    this.incrementOperationCount();293    if (conn == null) {294      try {295        conn = await this.pool.checkOut({ timeoutContext, signal: operation.options.signal });296      } catch (checkoutError) {297        this.decrementOperationCount();298        if (!(checkoutError instanceof PoolClearedError)) this.handleError(checkoutError);299        throw checkoutError;300      }301    }302 303    let reauthPromise: Promise<void> | null = null;304    const cleanup = () => {305      this.decrementOperationCount();306      if (session?.pinnedConnection !== conn) {307        if (reauthPromise != null) {308          // The reauth promise only exists if it hasn't thrown.309          const checkBackIn = () => {310            this.pool.checkIn(conn);311          };312          void reauthPromise.then(checkBackIn, checkBackIn);313        } else {314          this.pool.checkIn(conn);315        }316      }317    };318 319    let cmd;320    try {321      cmd = operation.buildCommand(conn, session);322    } catch (e) {323      cleanup();324      throw e;325    }326 327    const options = operation.buildOptions(timeoutContext);328    const ns = operation.ns;329 330    if (this.loadBalanced && isPinnableCommand(cmd, session) && !session?.pinnedConnection) {331      session?.pin(conn);332    }333 334    options.directConnection = this.topology.s.options.directConnection;335 336    const omitReadPreference =337      operation instanceof AggregateOperation &&338      operation.hasWriteStage &&339      maxWireVersion(conn) < MIN_SECONDARY_WRITE_WIRE_VERSION;340    if (omitReadPreference) {341      delete options.readPreference;342    }343 344    if (this.description.iscryptd) {345      options.omitMaxTimeMS = true;346    }347 348    try {349      try {350        const res = await conn.command(ns, cmd, options, operation.SERVER_COMMAND_RESPONSE_TYPE);351        throwIfWriteConcernError(res);352        return res;353      } catch (commandError) {354        throw this.decorateCommandError(conn, cmd, options, commandError);355      }356    } catch (operationError) {357      if (358        operationError instanceof MongoError &&359        operationError.code === MONGODB_ERROR_CODES.Reauthenticate360      ) {361        reauthPromise = this.pool.reauthenticate(conn);362        reauthPromise.then(undefined, error => {363          reauthPromise = null;364          squashError(error);365        });366 367        await abortable(reauthPromise, options);368        reauthPromise = null; // only reachable if reauth succeeds369 370        try {371          const res = await conn.command(ns, cmd, options, operation.SERVER_COMMAND_RESPONSE_TYPE);372          throwIfWriteConcernError(res);373          return res;374        } catch (commandError) {375          throw this.decorateCommandError(conn, cmd, options, commandError);376        }377      } else {378        throw operationError;379      }380    } finally {381      cleanup();382    }383  }384 385  /**386   * Handle SDAM error387   * @internal388   */389  handleError(error: AnyError, connection?: Connection) {390    if (!(error instanceof MongoError)) {391      return;392    }393 394    const isStaleError =395      error.connectionGeneration && error.connectionGeneration < this.pool.generation;396    if (isStaleError) {397      return;398    }399 400    const isNetworkNonTimeoutError =401      error instanceof MongoNetworkError && !(error instanceof MongoNetworkTimeoutError);402    const isNetworkTimeoutBeforeHandshakeError =403      error instanceof MongoNetworkError && error.beforeHandshake;404    const isAuthHandshakeError = error.hasErrorLabel(MongoErrorLabel.HandshakeError);405    if (isNetworkNonTimeoutError || isNetworkTimeoutBeforeHandshakeError || isAuthHandshakeError) {406      // In load balanced mode we never mark the server as unknown and always407      // clear for the specific service id.408      if (!this.loadBalanced) {409        error.addErrorLabel(MongoErrorLabel.ResetPool);410        markServerUnknown(this, error);411      } else if (connection) {412        this.pool.clear({ serviceId: connection.serviceId });413      }414    } else {415      if (isSDAMUnrecoverableError(error)) {416        if (shouldHandleStateChangeError(this, error)) {417          const shouldClearPool = isNodeShuttingDownError(error);418          if (this.loadBalanced && connection && shouldClearPool) {419            this.pool.clear({ serviceId: connection.serviceId });420          }421 422          if (!this.loadBalanced) {423            if (shouldClearPool) {424              error.addErrorLabel(MongoErrorLabel.ResetPool);425            }426            markServerUnknown(this, error);427            process.nextTick(() => this.requestCheck());428          }429        }430      }431    }432  }433 434  /**435   * Ensure that error is properly decorated and internal state is updated before throwing436   * @internal437   */438  private decorateCommandError(439    connection: Connection,440    cmd: Document,441    options: CommandOptions | GetMoreOptions | undefined,442    error: unknown443  ): Error {444    if (typeof error !== 'object' || error == null || !('name' in error)) {445      throw new MongoRuntimeError('An unexpected error type: ' + typeof error);446    }447 448    if (error.name === 'AbortError' && 'cause' in error && error.cause instanceof MongoError) {449      error = error.cause;450    }451 452    if (!(error instanceof MongoError)) {453      // Node.js or some other error we have not special handling for454      return error as Error;455    }456 457    if (connectionIsStale(this.pool, connection)) {458      return error;459    }460 461    const session = options?.session;462    if (error instanceof MongoNetworkError) {463      if (session && !session.hasEnded && session.serverSession) {464        session.serverSession.isDirty = true;465      }466 467      // inActiveTransaction check handles commit and abort.468      if (469        inActiveTransaction(session, cmd) &&470        !error.hasErrorLabel(MongoErrorLabel.TransientTransactionError)471      ) {472        error.addErrorLabel(MongoErrorLabel.TransientTransactionError);473      }474 475      if (476        (isRetryableWritesEnabled(this.topology) || isTransactionCommand(cmd)) &&477        supportsRetryableWrites(this) &&478        !inActiveTransaction(session, cmd)479      ) {480        error.addErrorLabel(MongoErrorLabel.RetryableWriteError);481      }482    } else {483      if (484        (isRetryableWritesEnabled(this.topology) || isTransactionCommand(cmd)) &&485        needsRetryableWriteLabel(error, maxWireVersion(this), this.description.type) &&486        !inActiveTransaction(session, cmd)487      ) {488        error.addErrorLabel(MongoErrorLabel.RetryableWriteError);489      }490    }491 492    if (493      session &&494      session.isPinned &&495      error.hasErrorLabel(MongoErrorLabel.TransientTransactionError)496    ) {497      session.unpin({ force: true });498    }499 500    this.handleError(error, connection);501 502    return error;503  }504 505  /**506   * Decrement the operation count, returning the new count.507   */508  private decrementOperationCount(): number {509    return (this.s.operationCount -= 1);510  }511 512  /**513   * Increment the operation count, returning the new count.514   */515  private incrementOperationCount(): number {516    return (this.s.operationCount += 1);517  }518}519 520function markServerUnknown(server: Server, error?: MongoError) {521  // Load balancer servers can never be marked unknown.522  if (server.loadBalanced) {523    return;524  }525 526  if (error instanceof MongoNetworkError && !(error instanceof MongoNetworkTimeoutError)) {527    server.monitor?.reset();528  }529 530  server.emit(531    Server.DESCRIPTION_RECEIVED,532    new ServerDescription(server.description.hostAddress, undefined, { error })533  );534}535 536function isPinnableCommand(cmd: Document, session?: ClientSession): boolean {537  if (session) {538    return (539      session.inTransaction() ||540      (session.transaction.isCommitted && 'commitTransaction' in cmd) ||541      'aggregate' in cmd ||542      'find' in cmd ||543      'getMore' in cmd ||544      'listCollections' in cmd ||545      'listIndexes' in cmd ||546      'bulkWrite' in cmd547    );548  }549 550  return false;551}552 553function connectionIsStale(pool: ConnectionPool, connection: Connection) {554  if (connection.serviceId) {555    return (556      connection.generation !== pool.serviceGenerations.get(connection.serviceId.toHexString())557    );558  }559 560  return connection.generation !== pool.generation;561}562 563function shouldHandleStateChangeError(server: Server, err: MongoError) {564  const etv = err.topologyVersion;565  const stv = server.description.topologyVersion;566  return compareTopologyVersion(stv, etv) < 0;567}568 569function inActiveTransaction(session: ClientSession | undefined, cmd: Document) {570  return session && session.inTransaction() && !isTransactionCommand(cmd);571}572 573/** this checks the retryWrites option passed down from the client options, it574 * does not check if the server supports retryable writes */575function isRetryableWritesEnabled(topology: Topology) {576  return topology.s.options.retryWrites !== false;577}578