Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
67 changes: 38 additions & 29 deletions handwritten/spanner/src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
* Injects a key-value pair into the gaxOpts.otherArgs.options object
* without mutating the original.
*/
function injectGaxOpt(existingOpts: any, key: string, value: any): any {

Check warning on line 82 in handwritten/spanner/src/transaction.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 82 in handwritten/spanner/src/transaction.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 82 in handwritten/spanner/src/transaction.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
return Object.assign({}, existingOpts, {
otherArgs: Object.assign({}, existingOpts?.otherArgs, {
options: Object.assign({}, existingOpts?.otherArgs?.options, {
Expand Down Expand Up @@ -438,35 +438,12 @@
},
},
};
this.request = (config: any, callback?: Function) => {
let gaxOpts;
if (!config.gaxOpts || Object.keys(config.gaxOpts).length === 0) {
gaxOpts = this._bindGaxOpts as any;
} else {
gaxOpts = injectGaxOpt(
config.gaxOpts,
'affinityKey',
this._affinityKey,
);
}
config = Object.assign({}, config, {gaxOpts});
return session.request(config, callback);
};

this.requestStream = (config: any) => {
let gaxOpts;
if (!config.gaxOpts || Object.keys(config.gaxOpts).length === 0) {
gaxOpts = this._bindGaxOpts as any;
} else {
gaxOpts = injectGaxOpt(
config.gaxOpts,
'affinityKey',
this._affinityKey,
);
}
config = Object.assign({}, config, {gaxOpts});
return session.requestStream(config);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.request = (config: any, callback?: Function) =>
session.request(this._applyAffinityGaxOpts(config), callback);
Comment thread
olavloite marked this conversation as resolved.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.requestStream = (config: any) =>
session.requestStream(this._applyAffinityGaxOpts(config));
} else {
this.request = session.request.bind(session);
this.requestStream = session.requestStream.bind(session);
Expand All @@ -487,6 +464,38 @@
this._mutationKey = null;
}

/**
* Binds the multiplexed session affinity key to the gax options of an
* outgoing request, so that all requests of this transaction are routed to
* the same gRPC channel.
*
* `config` is always a request descriptor that was freshly constructed by the
* caller for this one RPC (and {@link Spanner#prepareGapicRequest_} already
* modifies `config.headers` in place), so the affinity key is assigned
* directly instead of allocating a copy of the descriptor per request.
*
* @private
*
* @param {object} config The request configuration.
* @returns {object} The same request configuration.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private _applyAffinityGaxOpts(config: any): any {
if (!config) {
return config;
}
if (!config.gaxOpts || Object.keys(config.gaxOpts).length === 0) {
config.gaxOpts = this._bindGaxOpts;
Comment thread
olavloite marked this conversation as resolved.
} else {
config.gaxOpts = injectGaxOpt(
config.gaxOpts,
'affinityKey',
this._affinityKey,
);
}
return config;
}
Comment thread
olavloite marked this conversation as resolved.

protected _updatePrecommitToken(resp: PrecommitTokenProvider): void {
if (
this._latestPreCommitToken === null ||
Expand Down Expand Up @@ -1137,11 +1146,11 @@
if (this._affinityKey) {
const database = this.session?.parent as Database;
const spanner = database?.parent?.parent as Spanner;
const client = spanner?.clients_?.get('SpannerClient') as any;

Check warning on line 1149 in handwritten/spanner/src/transaction.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

if (client?.spannerStub) {
Promise.resolve(client.spannerStub)
.then((stub: any) => {

Check warning on line 1153 in handwritten/spanner/src/transaction.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
return stub?.getChannel?.()?.unbind?.(this._affinityKey);
})
.catch(() => {});
Expand Down Expand Up @@ -2919,14 +2928,14 @@
setSpanError(span, err);
}
span.end();
callback(err, resp);

Check warning on line 2931 in handwritten/spanner/src/transaction.ts

View workflow job for this annotation

GitHub Actions / lint

Avoid calling back inside of a promise
});
return null;
})
.catch(err => {
setSpanError(span, err);
span.end();
callback(err, null);

Check warning on line 2938 in handwritten/spanner/src/transaction.ts

View workflow job for this annotation

GitHub Actions / lint

Avoid calling back inside of a promise
});
return;
}
Expand Down Expand Up @@ -2960,7 +2969,7 @@
const database = this.session.parent as Database;
if (this._affinityKey) {
if (!gaxOpts || Object.keys(gaxOpts).length === 0) {
gaxOpts = this._unbindGaxOpts as any;

Check warning on line 2972 in handwritten/spanner/src/transaction.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
} else {
gaxOpts = injectGaxOpt(gaxOpts, 'unbind', true);
}
Expand Down Expand Up @@ -3352,7 +3361,7 @@

if (this._affinityKey) {
if (!gaxOpts || Object.keys(gaxOpts).length === 0) {
gaxOpts = this._unbindGaxOpts as any;

Check warning on line 3364 in handwritten/spanner/src/transaction.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
} else {
gaxOpts = injectGaxOpt(gaxOpts, 'unbind', true);
}
Expand Down
46 changes: 46 additions & 0 deletions handwritten/spanner/test/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
BatchUpdateOptions,
ExecuteSqlRequest,
ReadRequest,
RunCallback,

Check warning on line 44 in handwritten/spanner/test/transaction.ts

View workflow job for this annotation

GitHub Actions / lint

'RunCallback' is defined but never used
} from '../src/transaction';
import {Row} from '../src/partial-result-stream';
import {grpc} from 'google-gax';
Expand Down Expand Up @@ -171,6 +171,25 @@
assert.strictEqual(REQUEST_STREAM.callCount, 1);
});

it('should keep `request` and `requestStream` usable when detached', () => {
REQUEST.resetHistory();
REQUEST_STREAM.resetHistory();
const multiplexedSession = Object.assign({}, SESSION, {
metadata: {multiplexed: true},
});
const txn = new Snapshot(multiplexedSession);

// `TransactionRunner#_interceptErrors` and user code hold on to these
// methods without their receiver, so they must be pre-bound.
const {request, requestStream} = txn;

request({client: 'SpannerClient'}, () => {});
requestStream({client: 'SpannerClient'});

assert.strictEqual(REQUEST.callCount, 1);
assert.strictEqual(REQUEST_STREAM.callCount, 1);
});

it('should generate _affinityKey for multiplexed sessions', () => {
const multiplexedSession = Object.assign({}, SESSION, {
metadata: {multiplexed: true},
Expand Down Expand Up @@ -214,6 +233,33 @@
assert.deepStrictEqual(arg.gaxOpts, txn._bindGaxOpts);
});

it('should merge the affinity key into caller supplied gaxOpts', () => {
REQUEST.resetHistory();
const multiplexedSession = Object.assign({}, SESSION, {
metadata: {multiplexed: true},
});
const txn = new Snapshot(multiplexedSession);
const gaxOpts = {
timeout: 1000,
otherArgs: {options: {unbind: true}},
};

txn.request({client: 'SpannerClient', gaxOpts}, () => {});

const arg = REQUEST.lastCall.args[0];
assert.deepStrictEqual(arg.gaxOpts, {
timeout: 1000,
otherArgs: {
options: {unbind: true, affinityKey: txn._affinityKey},
},
});
// The caller supplied gax options must not be modified.
assert.deepStrictEqual(gaxOpts, {
timeout: 1000,
otherArgs: {options: {unbind: true}},
});
});

it('should set the commonHeaders_', () => {
assert.deepStrictEqual(snapshot.commonHeaders_, {
[CLOUD_RESOURCE_HEADER]: snapshot.session.parent.formattedName_,
Expand Down
Loading