UNPKG

@google-cloud/spanner

Version:
196 lines 8.58 kB
"use strict"; /*! * Copyright 2024 Google LLC. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MultiplexedSession = void 0; const events_1 = require("events"); const api_1 = require("@opentelemetry/api"); const instrument_1 = require("./instrument"); /** * Class used to manage connections to Spanner using multiplexed session. * * **You don't need to use this class directly, connections will be handled for * you.** * * @class * @extends {EventEmitter} */ class MultiplexedSession extends events_1.EventEmitter { database; // frequency to create new mux session refreshRate; _multiplexedSession; _refreshHandle; _observabilityOptions; // shared promise lock to handle concurrent session creation requests _sharedMuxSessionWaitPromise; constructor(database) { super(); this.database = database; // default frequency is 7 days this.refreshRate = 7; this._multiplexedSession = null; this._observabilityOptions = database._observabilityOptions; this._sharedMuxSessionWaitPromise = null; } /** * Creates a new multiplexed session and manages its maintenance. * * This method initiates the session creation process by calling the `_createSession` method, which returns a Promise. * * Errors are silently swallowed here to prevent unhandled promise rejections or application crashes during background operations. */ createSession() { this._createSession() .then(() => { this._maintain(); }) // Ignore errors here. If this fails, the next user request will // automatically trigger a retry via `_getSession`. .catch(err => { }); } /** * Creates a new multiplexed session. * * This method sends a request to the database to create a new session with multiplexing enabled. * The response from the database would be an array, the first value of the array will be containing the multiplexed session. * * @returns {Promise<void>} Resolves when the session is successfully created. * @throws {Error} If the request fails, the error is thrown to the caller. * * @private */ async _createSession() { // If a session is already being created, just wait for it to complete. if (this._sharedMuxSessionWaitPromise) { return this._sharedMuxSessionWaitPromise; } // Define the async task that performs the actual session creation and tracing. const task = async () => { const traceConfig = { opts: this._observabilityOptions, dbName: this.database.formattedName_, }; return api_1.context.with(api_1.ROOT_CONTEXT, () => { return (0, instrument_1.startTrace)('MultiplexedSession.createSession', traceConfig, async (span) => { span.addEvent('Requesting a multiplexed session'); try { const [createSessionResponse] = await this.database.createSession({ multiplexed: true, }); this._multiplexedSession = createSessionResponse; span.addEvent('Created a multiplexed session'); } catch (e) { (0, instrument_1.setSpanError)(span, e); throw e; } finally { span.end(); } }); }); }; // Assign the running task to the shared promise variable, and ensure // the lock is released when it completes. this._sharedMuxSessionWaitPromise = task().finally(() => { this._sharedMuxSessionWaitPromise = null; }); return this._sharedMuxSessionWaitPromise; } /** * Maintains the multiplexed session by periodically refreshing it. * * This method sets up a periodic refresh interval for maintaining the session. The interval duration * is determined by the @param refreshRate option, which is provided in days. * The default value is 7 days. * * @throws {Error} If the multiplexed session creation fails in `_createSession`, the error is caught * and ignored. This is because the currently active multiplexed session has a 30-day expiry, providing * the maintainer with four opportunities (one every 7 days) to refresh the active session. * * Hence, if the `_createSession` fails here, the system will either simply retry at the next interval or * upon the next user request if the session expires. * * @returns {void} This method does not return any value. * */ _maintain() { // If a maintenance loop is already running, stop it first. // This prevents creating duplicate intervals if _maintain is called multiple times. if (this._refreshHandle) { clearInterval(this._refreshHandle); } const refreshRate = this.refreshRate * 24 * 60 * 60000; this._refreshHandle = api_1.context.with(api_1.ROOT_CONTEXT, () => setInterval(() => { this._createSession().catch(() => { }); }, refreshRate)); // Unreference the timer so it does not prevent the Node.js process from exiting. // If the application has finished all other work, this background timer shouldn't // force the process to stay open. this._refreshHandle.unref(); } /** * Retrieves a session asynchronously and invokes a callback with the session details. * * @param {GetSessionCallback} callback - The callback to be invoked once the session is acquired or an error occurs. * * @returns {void} This method does not return any value, as it operates asynchronously and relies on the callback. * */ getSession(callback) { this._getSession().then(session => callback(null, session, session.transaction(session.parent.queryOptions_)), callback); } /** * Attempts to get a session, waiting for it to become available if necessary. * * Logic Flow: * 1. Cache Hit: If a session exists, return it immediately. * 2. Join Wait: If another request is currently creating the session (`_sharedMuxSessionWaitPromise` exists), await it. * 3. Create: If neither, initiate a new creation request (`_createSession`). * * @throws {Error} In case of an error, the promise will get rejected and the error will get bubble up to the parent method. * * @returns {Promise<Session | null>} A promise that resolves with the active multiplexed session. * * @private * */ async _getSession() { const span = (0, instrument_1.getActiveOrNoopSpan)(); // Check if the multiplexed session is already available if (this._multiplexedSession !== null) { span.addEvent('Cache hit: has usable multiplexed session'); return this._multiplexedSession; } span.addEvent('Waiting for a multiplexed session to become available'); // If initialization is ALREADY in progress, join the existing line! if (this._sharedMuxSessionWaitPromise) { await this._sharedMuxSessionWaitPromise; } else { // If the session is null, and nobody is currently initializing it // It means a previous attempt failed and we are in a "Dead" state // We must kickstart the process again await this._createSession(); } span.addEvent('Acquired multiplexed session'); // Return the multiplexed session after it becomes available return this._multiplexedSession; } } exports.MultiplexedSession = MultiplexedSession; //# sourceMappingURL=multiplexed-session.js.map