@google-cloud/spanner
Version:
Cloud Spanner Client Library for Node.js
200 lines • 8.08 kB
JavaScript
"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 = exports.MUX_SESSION_CREATE_ERROR = exports.MUX_SESSION_AVAILABLE = void 0;
const events_1 = require("events");
const instrument_1 = require("./instrument");
exports.MUX_SESSION_AVAILABLE = 'mux-session-available';
exports.MUX_SESSION_CREATE_ERROR = 'mux-session-create-error';
/**
* 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;
constructor(database) {
super();
this.database = database;
// default frequency is 7 days
this.refreshRate = 7;
this._multiplexedSession = null;
this._observabilityOptions = database._observabilityOptions;
}
/**
* 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.
*/
createSession() {
this._createSession()
.then(() => {
this._maintain();
})
.catch(err => {
this.emit('error', 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>} A Promise that resolves when the session has been successfully created and assigned, an event
* `mux-session-available` will be emitted to signal that the session is ready.
*
* In case of error, an error will get emitted along with the error event.
*
* @private
*/
async _createSession() {
const traceConfig = {
opts: this._observabilityOptions,
dbName: this.database.formattedName_,
};
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 multiplexed session ${this._multiplexedSession.id}`);
this.emit(exports.MUX_SESSION_AVAILABLE);
}
catch (e) {
(0, instrument_1.setSpanError)(span, e);
this.emit(exports.MUX_SESSION_CREATE_ERROR, e);
throw e;
}
finally {
span.end();
}
});
}
/**
* 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.
*
* @returns {void} This method does not return any value.
*
*/
_maintain() {
const refreshRate = this.refreshRate * 24 * 60 * 60000;
this._refreshHandle = setInterval(async () => {
try {
await this._createSession();
}
catch (err) {
return;
}
}, refreshRate);
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._acquire().then(session => callback(null, session, session?.txn), callback);
}
/**
* Acquires a session asynchronously, and prepares the transaction for the session.
*
* Once a session is successfully acquired, it returns the session object (which may be `null` if unsuccessful).
*
* @returns {Promise<Session | null>}
* A Promise that resolves with the acquired session (or `null` if no session is available after retries).
*
*/
async _acquire() {
const session = await this._getSession();
// Prepare a transaction for a session
session.txn = session.transaction(session.parent.queryOptions_);
return session;
}
/**
* Attempts to get a session, waiting for it to become available if necessary.
*
* Waits for the `MUX_SESSION_AVAILABLE` event or for the `MUX_SESSION_CREATE_ERROR`
* to be emitted if the multiplexed session is not yet available. The method listens
* for these events, and once `mux-session-available` is emitted, it resolves and returns
* the session.
*
* 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 current multiplexed session if available,
* or `null` if the session is not available.
*
* @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;
}
// Define event and promises to wait for the session to become available or for the error
span.addEvent('Waiting for a multiplexed session to become available');
let removeAvailableListener;
let removeErrorListener;
const promises = [
new Promise((_, reject) => {
this.once(exports.MUX_SESSION_CREATE_ERROR, reject);
removeErrorListener = this.removeListener.bind(this, exports.MUX_SESSION_CREATE_ERROR, reject);
}),
new Promise(resolve => {
this.once(exports.MUX_SESSION_AVAILABLE, resolve);
removeAvailableListener = this.removeListener.bind(this, exports.MUX_SESSION_AVAILABLE, resolve);
}),
];
try {
await Promise.race(promises);
}
finally {
removeAvailableListener();
removeErrorListener();
}
// Return the multiplexed session after it becomes available
return this._multiplexedSession;
}
}
exports.MultiplexedSession = MultiplexedSession;
//# sourceMappingURL=multiplexed-session.js.map