Skip to content

Commit 88711de

Browse files
authored
chore: add commit retry logic (#2322)
* chore: add commit retry logic * refactor test
1 parent e8cdbed commit 88711de

File tree

3 files changed

+95
-1
lines changed

3 files changed

+95
-1
lines changed

src/transaction.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,8 @@ export type CommitCallback =
229229
type PrecommitTokenProvider =
230230
| spannerClient.spanner.v1.ITransaction
231231
| spannerClient.spanner.v1.IPartialResultSet
232-
| spannerClient.spanner.v1.IExecuteBatchDmlResponse;
232+
| spannerClient.spanner.v1.IExecuteBatchDmlResponse
233+
| spannerClient.spanner.v1.ICommitResponse;
233234

234235
/**
235236
* @typedef {object} TimestampBounds
@@ -1831,6 +1832,7 @@ export class Transaction extends Dml {
18311832
commitTimestamp?: PreciseDate;
18321833
commitTimestampProto?: spannerClient.protobuf.ITimestamp;
18331834
private _queuedMutations: spannerClient.spanner.v1.Mutation[];
1835+
private _retryCommit: Boolean;
18341836

18351837
/**
18361838
* Timestamp at which the transaction was committed. Will be populated once
@@ -1884,6 +1886,7 @@ export class Transaction extends Dml {
18841886
this._options = {readWrite: options};
18851887
this._options.isolationLevel = IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED;
18861888
this.requestOptions = requestOptions;
1889+
this._retryCommit = false;
18871890
}
18881891

18891892
/**
@@ -2291,6 +2294,16 @@ export class Transaction extends Dml {
22912294
err: null | Error,
22922295
resp: spannerClient.spanner.v1.ICommitResponse,
22932296
) => {
2297+
if (
2298+
resp &&
2299+
'MultiplexedSessionRetry' in resp &&
2300+
!this._retryCommit
2301+
) {
2302+
this._retryCommit = true;
2303+
this._updatePrecommitToken(resp);
2304+
return this.commit(options, callback);
2305+
}
2306+
22942307
this.end();
22952308

22962309
if (err) {

test/session-factory.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ describe('SessionFactory', () => {
130130
process.env.GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS = 'true';
131131
});
132132

133+
after(() => {
134+
process.env.GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS = 'false';
135+
});
136+
133137
it('should create a MultiplexedSession object', () => {
134138
assert(
135139
sessionFactory.multiplexedSession_ instanceof MultiplexedSession,
@@ -171,6 +175,11 @@ describe('SessionFactory', () => {
171175
process.env.GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW = 'true';
172176
});
173177

178+
after(() => {
179+
process.env.GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS = 'false';
180+
process.env.GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW = 'false';
181+
});
182+
174183
it('should correctly initialize the isMultiplexedRW field', () => {
175184
const sessionFactory = new SessionFactory(DATABASE, NAME, POOL_OPTIONS);
176185
assert.strictEqual(sessionFactory.isMultiplexedRW, true);
@@ -213,6 +222,10 @@ describe('SessionFactory', () => {
213222
process.env.GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS = 'true';
214223
});
215224

225+
after(() => {
226+
process.env.GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS = 'false';
227+
});
228+
216229
it('should return the multiplexed session', done => {
217230
(
218231
sandbox.stub(
@@ -283,6 +296,11 @@ describe('SessionFactory', () => {
283296
process.env.GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW = 'true';
284297
});
285298

299+
after(() => {
300+
process.env.GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS = 'false';
301+
process.env.GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW = 'false';
302+
});
303+
286304
it('should return the multiplexed session', done => {
287305
(
288306
sandbox.stub(
@@ -330,6 +348,10 @@ describe('SessionFactory', () => {
330348
process.env.GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS = 'true';
331349
});
332350

351+
after(() => {
352+
process.env.GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS = 'false';
353+
});
354+
333355
it('should not call the release method', () => {
334356
const releaseStub = sandbox.stub(sessionFactory.pool_, 'release');
335357
const fakeMuxSession = createMuxSession();

test/transaction.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1879,6 +1879,65 @@ describe('Transaction', () => {
18791879
assert.strictEqual(transaction.commitTimestampProto, fakeTimestamp);
18801880
});
18811881

1882+
it('should retry commit only once upon sending precommitToken to read-only participants', () => {
1883+
const requestStub = sandbox.stub(transaction, 'request');
1884+
1885+
const expectedTimestamp = new PreciseDate(0);
1886+
const fakeTimestamp = {seconds: 0, nanos: 0};
1887+
1888+
const fakeResponse = {commitTimestamp: fakeTimestamp};
1889+
const fakePrecommitToken = {
1890+
precommitToken: Buffer.from('precommit-token-commit'),
1891+
seqNum: 1,
1892+
};
1893+
1894+
transaction._latestPreCommitToken = fakePrecommitToken;
1895+
1896+
// retry response on commit retry
1897+
const fakeCommitRetryResponse = {
1898+
commitTimestamp: null,
1899+
MultiplexedSessionRetry: 'precommitToken',
1900+
precommitToken: {
1901+
precommitToken: Buffer.from('precommit-token-commit-retry'),
1902+
seqNum: 2,
1903+
},
1904+
};
1905+
1906+
requestStub.onFirstCall().callsFake((_, callback) => {
1907+
// assert that the transaction contains the precommit token
1908+
assert.deepStrictEqual(
1909+
transaction._latestPreCommitToken,
1910+
fakePrecommitToken,
1911+
);
1912+
// retry commit response
1913+
callback(null, fakeCommitRetryResponse);
1914+
});
1915+
1916+
requestStub.onSecondCall().callsFake((_, callback) => {
1917+
// assert that before second commit retry the _latestPreCommitToken
1918+
// containing the commit retry reponse in the transaction object
1919+
assert.deepStrictEqual(
1920+
transaction._latestPreCommitToken,
1921+
fakeCommitRetryResponse.precommitToken,
1922+
);
1923+
callback(null, fakeResponse);
1924+
});
1925+
1926+
transaction.commit((err, resp) => {
1927+
// assert there is no error
1928+
assert.ifError(err);
1929+
// make sure that retry happens only once
1930+
assert.strictEqual(requestStub.callCount, 2);
1931+
assert.deepStrictEqual(
1932+
transaction.commitTimestamp,
1933+
expectedTimestamp,
1934+
);
1935+
assert.strictEqual(transaction.commitTimestampProto, fakeTimestamp);
1936+
// assert on the successfull commit response
1937+
assert.deepStrictEqual(resp, fakeResponse);
1938+
});
1939+
});
1940+
18821941
it('should return any errors and the response', () => {
18831942
const requestStub = sandbox.stub(transaction, 'request');
18841943

0 commit comments

Comments
 (0)