Skip to content

Commit a84ddcf

Browse files
committed
Make maximum transaction retry time configurable
Commit makes driver aware of a new config property `maxTransactionRetryTime` to tune amount of time transactions executed via `Session#readTransaction(function(Transaction))` and `Session#writeTransaction(function(Transaction))` can be retried. It also adds JSDoc for `connectionPoolSize` property.
1 parent cc2fc96 commit a84ddcf

File tree

7 files changed

+69
-16
lines changed

7 files changed

+69
-16
lines changed

src/v1/driver.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ class Driver {
115115
*/
116116
session(mode, bookmark) {
117117
const sessionMode = Driver._validateSessionMode(mode);
118-
return this._createSession(sessionMode, this._connectionProvider, bookmark);
118+
return this._createSession(sessionMode, this._connectionProvider, bookmark, this._config);
119119
}
120120

121121
static _validateSessionMode(rawMode) {
@@ -132,8 +132,8 @@ class Driver {
132132
}
133133

134134
//Extension point
135-
_createSession(mode, connectionProvider, bookmark) {
136-
return new Session(mode, connectionProvider, bookmark);
135+
_createSession(mode, connectionProvider, bookmark, config) {
136+
return new Session(mode, connectionProvider, bookmark, config);
137137
}
138138

139139
_driverOnErrorCallback(error) {

src/v1/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ let USER_AGENT = "neo4j-javascript/" + VERSION;
100100
* // port, and this is then used to verify the host certificate does not change.
101101
* // This setting has no effect unless TRUST_ON_FIRST_USE is enabled.
102102
* knownHosts:"~/.neo4j/known_hosts",
103+
*
104+
* // The max number of connections that are allowed idle in the pool at any time.
105+
* // Connection will be destroyed if this threshold is exceeded.
106+
* connectionPoolSize: 50,
107+
*
108+
* // Specify the maximum time in milliseconds transactions are allowed to retry via
109+
* // {@link Session#readTransaction()} and {@link Session#writeTransaction()} functions. These functions
110+
* // will retry the given unit of work on `ServiceUnavailable`, `SessionExpired` and transient errors with
111+
* // exponential backoff using initial delay of 1 second. Default value is 30000 which is 30 seconds.
112+
* maxTransactionRetryTime: 30000,
103113
* }
104114
*
105115
* @param {string} url The URL for the Neo4j database, for instance "bolt://localhost"

src/v1/internal/transaction-executor.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ const DEFAULT_RETRY_DELAY_JITTER_FACTOR = 0.2;
2727
export default class TransactionExecutor {
2828

2929
constructor(maxRetryTimeMs, initialRetryDelayMs, multiplier, jitterFactor) {
30-
this._maxRetryTimeMs = maxRetryTimeMs || DEFAULT_MAX_RETRY_TIME_MS;
31-
this._initialRetryDelayMs = initialRetryDelayMs || DEFAULT_INITIAL_RETRY_DELAY_MS;
32-
this._multiplier = multiplier || DEFAULT_RETRY_DELAY_MULTIPLIER;
33-
this._jitterFactor = jitterFactor || DEFAULT_RETRY_DELAY_JITTER_FACTOR;
30+
this._maxRetryTimeMs = _valueOrDefault(maxRetryTimeMs, DEFAULT_MAX_RETRY_TIME_MS);
31+
this._initialRetryDelayMs = _valueOrDefault(initialRetryDelayMs, DEFAULT_INITIAL_RETRY_DELAY_MS);
32+
this._multiplier = _valueOrDefault(multiplier, DEFAULT_RETRY_DELAY_MULTIPLIER);
33+
this._jitterFactor = _valueOrDefault(jitterFactor, DEFAULT_RETRY_DELAY_JITTER_FACTOR);
3434

3535
this._inFlightTimeoutIds = [];
3636

@@ -140,3 +140,10 @@ export default class TransactionExecutor {
140140
}
141141
}
142142
};
143+
144+
function _valueOrDefault(value, defaultValue) {
145+
if (value || value === 0) {
146+
return value;
147+
}
148+
return defaultValue;
149+
}

src/v1/routing-driver.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ class RoutingDriver extends Driver {
3535
return new LoadBalancer(address, connectionPool, driverOnErrorCallback);
3636
}
3737

38-
_createSession(mode, connectionProvider, bookmark) {
39-
return new RoutingSession(mode, connectionProvider, bookmark, (error, conn) => {
38+
_createSession(mode, connectionProvider, bookmark, config) {
39+
return new RoutingSession(mode, connectionProvider, bookmark, config, (error, conn) => {
4040
if (error.code === SERVICE_UNAVAILABLE || error.code === SESSION_EXPIRED) {
4141
// connection is undefined if error happened before connection was acquired
4242
if (conn) {
@@ -66,8 +66,8 @@ class RoutingDriver extends Driver {
6666
}
6767

6868
class RoutingSession extends Session {
69-
constructor(mode, connectionProvider, bookmark, onFailedConnection) {
70-
super(mode, connectionProvider, bookmark);
69+
constructor(mode, connectionProvider, bookmark, config, onFailedConnection) {
70+
super(mode, connectionProvider, bookmark, config);
7171
this._onFailedConnection = onFailedConnection;
7272
}
7373

src/v1/session.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,16 @@ class Session {
3838
* @param {string} mode the default access mode for this session.
3939
* @param {ConnectionProvider} connectionProvider - the connection provider to acquire connections from.
4040
* @param {string} [bookmark=undefined] - the initial bookmark for this session.
41+
* @param {Object} [config={}] - this driver configuration.
4142
*/
42-
constructor(mode, connectionProvider, bookmark) {
43+
constructor(mode, connectionProvider, bookmark, config) {
4344
this._mode = mode;
4445
this._readConnectionHolder = new ConnectionHolder(READ, connectionProvider);
4546
this._writeConnectionHolder = new ConnectionHolder(WRITE, connectionProvider);
4647
this._open = true;
4748
this._hasTx = false;
4849
this._lastBookmark = bookmark;
49-
this._transactionExecutor = new TransactionExecutor()
50+
this._transactionExecutor = _createTransactionExecutor(config);
5051
}
5152

5253
/**
@@ -134,7 +135,7 @@ class Session {
134135
* Transaction will automatically be committed unless the given function throws or returns a rejected promise.
135136
* Some failures of the given function or the commit itself will be retried with exponential backoff with initial
136137
* delay of 1 second and maximum retry time of 30 seconds. Maximum retry time is configurable via driver config's
137-
* {@link #maxTransactionRetryTime} property in milliseconds.
138+
* <code>maxTransactionRetryTime</code> property in milliseconds.
138139
*
139140
* @param {function(Transaction)} transactionWork - callback that executes operations against
140141
* a given {@link Transaction}.
@@ -151,7 +152,7 @@ class Session {
151152
* Transaction will automatically be committed unless the given function throws or returns a rejected promise.
152153
* Some failures of the given function or the commit itself will be retried with exponential backoff with initial
153154
* delay of 1 second and maximum retry time of 30 seconds. Maximum retry time is configurable via driver config's
154-
* {@link #maxTransactionRetryTime} property in milliseconds.
155+
* <code>maxTransactionRetryTime</code> property in milliseconds.
155156
*
156157
* @param {function(Transaction)} transactionWork - callback that executes operations against
157158
* a given {@link Transaction}.
@@ -232,4 +233,9 @@ class _RunObserver extends StreamObserver {
232233
}
233234
}
234235

236+
function _createTransactionExecutor(config) {
237+
const maxRetryTimeMs = (config && config.maxTransactionRetryTime) ? config.maxTransactionRetryTime : null;
238+
return new TransactionExecutor(maxRetryTimeMs);
239+
}
240+
235241
export default Session;

test/internal/transaction-executor.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,25 @@ describe('TransactionExecutor', () => {
150150
}, 1000);
151151
});
152152

153+
it('should allow zero max retry time', () => {
154+
const executor = new TransactionExecutor(0);
155+
expect(executor._maxRetryTimeMs).toEqual(0);
156+
});
157+
158+
it('should allow zero initial delay', () => {
159+
const executor = new TransactionExecutor(42, 0);
160+
expect(executor._initialRetryDelayMs).toEqual(0);
161+
});
162+
163+
it('should disallow zero multiplier', () => {
164+
expect(() => new TransactionExecutor(42, 42, 0)).toThrow();
165+
});
166+
167+
it('should allow zero jitter factor', () => {
168+
const executor = new TransactionExecutor(42, 42, 42, 0);
169+
expect(executor._jitterFactor).toEqual(0);
170+
});
171+
153172
function testRetryWhenTransactionCreatorFails(errorCodes, done) {
154173
const executor = new TransactionExecutor();
155174
const transactionCreator = throwingTransactionCreator(errorCodes, new FakeTransaction());

test/v1/examples.test.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ describe('examples', function() {
9797
});
9898
});
9999

100-
it('should be able to configure session pool size', function (done) {
100+
it('should be able to configure connection pool size', function (done) {
101101
var neo4j = neo4jv1;
102102
// tag::configuration[]
103103
var driver = neo4j.driver("bolt://localhost:7687", neo4j.auth.basic("neo4j", "neo4j"), {connectionPoolSize: 50});
@@ -118,6 +118,17 @@ describe('examples', function() {
118118
});
119119
});
120120

121+
it('should be able to configure maximum transaction retry time', function () {
122+
var neo4j = neo4jv1;
123+
// tag::configuration[]
124+
var maxRetryTimeMs = 45 * 1000; // 45 seconds
125+
var driver = neo4j.driver('bolt://localhost:7687', neo4j.auth.basic('neo4j', 'neo4j'), {maxTransactionRetryTime: maxRetryTimeMs});
126+
//end::configuration[]
127+
128+
var session = driver.session();
129+
expect(session._transactionExecutor._maxRetryTimeMs).toBe(maxRetryTimeMs);
130+
});
131+
121132
it('should document a statement', function(done) {
122133
var session = driverGlobal.session();
123134
// tag::statement[]

0 commit comments

Comments
 (0)