Skip to content

Commit b467380

Browse files
authored
chore: support for common interface to get a session to support multiplexed session (#2204)
This PR contains the common interface class SessionFactory which will be responsible for the creation of the Session Pool and the Multiplexed Session(if the env variable would be set to true) upon client initialization. This PR also contains the getSession method which will return the session(multiplexed/regular) based upon the env variable value.
1 parent 559031d commit b467380

File tree

9 files changed

+518
-50
lines changed

9 files changed

+518
-50
lines changed

observability-test/database.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {Instance, MutationGroup, Spanner} from '../src';
4747
import * as pfy from '@google-cloud/promisify';
4848
import {grpc} from 'google-gax';
4949
import {MockError} from '../test/mockserver/mockspanner';
50+
import {FakeSessionFactory} from '../test/database';
5051
const {generateWithAllSpansHaveDBName} = require('./helper');
5152

5253
const fakePfy = extend({}, pfy, {
@@ -234,6 +235,7 @@ describe('Database', () => {
234235
'./codec': {codec: fakeCodec},
235236
'./partial-result-stream': {partialResultStream: fakePartialResultStream},
236237
'./session-pool': {SessionPool: FakeSessionPool},
238+
'./session-factory': {SessionFactory: FakeSessionFactory},
237239
'./session': {Session: FakeSession},
238240
'./table': {Table: FakeTable},
239241
'./transaction-runner': {

src/database.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
} from 'google-gax';
3737
import {Backup} from './backup';
3838
import {BatchTransaction, TransactionIdentifier} from './batch-transaction';
39+
import {SessionFactory, SessionFactoryInterface} from './session-factory';
3940
import {
4041
google as databaseAdmin,
4142
google,
@@ -111,7 +112,6 @@ import {
111112
setSpanErrorAndException,
112113
traceConfig,
113114
} from './instrument';
114-
115115
export type GetDatabaseRolesCallback = RequestCallback<
116116
IDatabaseRole,
117117
databaseAdmin.spanner.admin.database.v1.IListDatabaseRolesResponse
@@ -339,6 +339,7 @@ class Database extends common.GrpcServiceObject {
339339
private instance: Instance;
340340
formattedName_: string;
341341
pool_: SessionPoolInterface;
342+
sessionFactory_: SessionFactoryInterface;
342343
queryOptions_?: spannerClient.spanner.v1.ExecuteSqlRequest.IQueryOptions;
343344
commonHeaders_: {[k: string]: string};
344345
request: DatabaseRequest;
@@ -450,15 +451,6 @@ class Database extends common.GrpcServiceObject {
450451
},
451452
} as {} as ServiceObjectConfig);
452453

453-
this.pool_ =
454-
typeof poolOptions === 'function'
455-
? new (poolOptions as SessionPoolConstructor)(this, null)
456-
: new SessionPool(this, poolOptions);
457-
const sessionPoolInstance = this.pool_ as SessionPool;
458-
if (sessionPoolInstance) {
459-
sessionPoolInstance._observabilityOptions =
460-
instance._observabilityOptions;
461-
}
462454
if (typeof poolOptions === 'object') {
463455
this.databaseRole = poolOptions.databaseRole || null;
464456
this.labels = poolOptions.labels || null;
@@ -480,8 +472,13 @@ class Database extends common.GrpcServiceObject {
480472

481473
// eslint-disable-next-line @typescript-eslint/no-explicit-any
482474
this.requestStream = instance.requestStream as any;
483-
this.pool_.on('error', this.emit.bind(this, 'error'));
484-
this.pool_.open();
475+
this.sessionFactory_ = new SessionFactory(this, name, poolOptions);
476+
this.pool_ = this.sessionFactory_.getPool();
477+
const sessionPoolInstance = this.pool_ as SessionPool;
478+
if (sessionPoolInstance) {
479+
sessionPoolInstance._observabilityOptions =
480+
instance._observabilityOptions;
481+
}
485482
this.queryOptions_ = Object.assign(
486483
Object.assign({}, queryOptions),
487484
Database.getEnvironmentQueryOptions()

src/multiplexed-session.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import {EventEmitter} from 'events';
1818
import {Database} from './database';
1919
import {Session} from './session';
20-
import {GetSessionCallback} from './session-pool';
20+
import {GetSessionCallback} from './session-factory';
2121
import {
2222
ObservabilityOptions,
2323
getActiveOrNoopSpan,
@@ -38,7 +38,7 @@ export const MUX_SESSION_CREATE_ERROR = 'mux-session-create-error';
3838
* @constructs MultiplexedSessionInterface
3939
* @param {Database} database The database to create a multiplexed session for.
4040
*/
41-
export interface MultiplexedSessionInterface {
41+
export interface MultiplexedSessionInterface extends EventEmitter {
4242
/**
4343
* When called creates a multiplexed session.
4444
*
@@ -71,6 +71,7 @@ export class MultiplexedSession
7171
database: Database;
7272
// frequency to create new mux session
7373
refreshRate: number;
74+
isMultiplexedEnabled: boolean;
7475
_multiplexedSession: Session | null;
7576
_refreshHandle!: NodeJS.Timer;
7677
_observabilityOptions?: ObservabilityOptions;
@@ -81,6 +82,9 @@ export class MultiplexedSession
8182
this.refreshRate = 7;
8283
this._multiplexedSession = null;
8384
this._observabilityOptions = database._observabilityOptions;
85+
process.env.GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS === 'true'
86+
? (this.isMultiplexedEnabled = true)
87+
: (this.isMultiplexedEnabled = false);
8488
}
8589

8690
/**

src/session-factory.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*!
2+
* Copyright 2024 Google LLC. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {Database, Session, Transaction} from '.';
18+
import {
19+
MultiplexedSession,
20+
MultiplexedSessionInterface,
21+
} from './multiplexed-session';
22+
import {
23+
SessionPool,
24+
SessionPoolInterface,
25+
SessionPoolOptions,
26+
} from './session-pool';
27+
import {SessionPoolConstructor} from './database';
28+
import {ServiceObjectConfig} from '@google-cloud/common';
29+
const common = require('./common-grpc/service-object');
30+
31+
/**
32+
* @callback GetSessionCallback
33+
* @param {?Error} error Request error, if any.
34+
* @param {Session} session The read-write session.
35+
* @param {Transaction} transaction The transaction object.
36+
*/
37+
export interface GetSessionCallback {
38+
(
39+
err: Error | null,
40+
session?: Session | null,
41+
transaction?: Transaction | null
42+
): void;
43+
}
44+
45+
/**
46+
* Interface for implementing session-factory logic.
47+
*
48+
* @interface SessionFactoryInterface
49+
*/
50+
export interface SessionFactoryInterface {
51+
/**
52+
* When called returns a session.
53+
*
54+
* @name SessionFactoryInterface#getSession
55+
* @param {GetSessionCallback} callback The callback function.
56+
*/
57+
getSession(callback: GetSessionCallback): void;
58+
59+
/**
60+
* When called returns the pool object.
61+
*
62+
* @name SessionFactoryInterface#getPool
63+
*/
64+
getPool(): SessionPoolInterface;
65+
66+
/**
67+
* To be called when releasing a session.
68+
*
69+
* @name SessionFactoryInterface#release
70+
* @param {Session} session The session to be released.
71+
*/
72+
release(session: Session): void;
73+
}
74+
75+
/**
76+
* Creates a SessionFactory object to manage the creation of
77+
* session-pool and multiplexed session.
78+
*
79+
* @class
80+
*
81+
* @param {Database} database Database object.
82+
* @param {String} name Name of the database.
83+
* @param {SessionPoolOptions|SessionPoolInterface} options Session pool
84+
* configuration options or custom pool inteface.
85+
*/
86+
export class SessionFactory
87+
extends common.GrpcServiceObject
88+
implements SessionFactoryInterface
89+
{
90+
multiplexedSession_: MultiplexedSessionInterface;
91+
pool_: SessionPoolInterface;
92+
constructor(
93+
database: Database,
94+
name: String,
95+
poolOptions?: SessionPoolConstructor | SessionPoolOptions
96+
) {
97+
super({
98+
parent: database,
99+
id: name,
100+
} as {} as ServiceObjectConfig);
101+
this.pool_ =
102+
typeof poolOptions === 'function'
103+
? new (poolOptions as SessionPoolConstructor)(database, null)
104+
: new SessionPool(database, poolOptions);
105+
this.pool_.on('error', this.emit.bind(database, 'error'));
106+
this.pool_.open();
107+
this.multiplexedSession_ = new MultiplexedSession(database);
108+
// Multiplexed sessions should only be created if its enabled.
109+
if ((this.multiplexedSession_ as MultiplexedSession).isMultiplexedEnabled) {
110+
this.multiplexedSession_.on('error', this.emit.bind(database, 'error'));
111+
this.multiplexedSession_.createSession();
112+
}
113+
}
114+
115+
/**
116+
* Retrieves a session, either a regular session or a multiplexed session, based on the environment variable configuration.
117+
*
118+
* If the environment variable `GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS` is set to `true`, the method will attempt to
119+
* retrieve a multiplexed session. Otherwise, it will retrieve a session from the regular pool.
120+
*
121+
* @param {GetSessionCallback} callback The callback function.
122+
*/
123+
124+
getSession(callback: GetSessionCallback): void {
125+
const sessionHandler = (this.multiplexedSession_ as MultiplexedSession)
126+
.isMultiplexedEnabled
127+
? this.multiplexedSession_
128+
: this.pool_;
129+
130+
sessionHandler!.getSession((err, session) => callback(err, session));
131+
}
132+
133+
/**
134+
* Returns the regular session pool object.
135+
*
136+
* @returns {SessionPoolInterface} The session pool used by current instance.
137+
*/
138+
139+
getPool(): SessionPoolInterface {
140+
return this.pool_;
141+
}
142+
143+
/**
144+
* Releases a session back to the session pool.
145+
*
146+
* This method returns a session to the pool after it is no longer needed.
147+
* It is a no-op for multiplexed sessions.
148+
*
149+
* @param {Session} session - The session to be released. This should be an instance of `Session` that was
150+
* previously acquired from the session pool.
151+
*
152+
* @throws {Error} If the session is invalid or cannot be released.
153+
*/
154+
release(session: Session): void {
155+
if (
156+
!(this.multiplexedSession_ as MultiplexedSession).isMultiplexedEnabled
157+
) {
158+
this.pool_.release(session);
159+
}
160+
}
161+
}

src/session-pool.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
setSpanErrorAndException,
3131
startTrace,
3232
} from './instrument';
33-
33+
import {GetSessionCallback} from './session-factory';
3434
import {
3535
isDatabaseNotFoundError,
3636
isInstanceNotFoundError,
@@ -59,20 +59,6 @@ export interface GetWriteSessionCallback {
5959
): void;
6060
}
6161

62-
/**
63-
* @callback GetSessionCallback
64-
* @param {?Error} error Request error, if any.
65-
* @param {Session} session The read-write session.
66-
* @param {Transaction} transaction The transaction object.
67-
*/
68-
export interface GetSessionCallback {
69-
(
70-
err: Error | null,
71-
session?: Session | null,
72-
transaction?: Transaction | null
73-
): void;
74-
}
75-
7662
/**
7763
* Interface for implementing custom session pooling logic, it should extend the
7864
* {@link https://nodejs.org/api/events.html|EventEmitter} class and emit any

0 commit comments

Comments
 (0)