Skip to content

Commit 5d2101f

Browse files
authored
Merge pull request #330 from lutovich/1.5-pool-acquire-fix
Fixed timeout when acquiring connection from the pool
2 parents 2b27814 + b43c046 commit 5d2101f

File tree

4 files changed

+69
-115
lines changed

4 files changed

+69
-115
lines changed

src/v1/internal/pool.js

+13-14
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
* limitations under the License.
1818
*/
1919

20-
import {promiseOrTimeout} from './util';
2120
import PoolConfig from './pool-config';
21+
import {newError} from '../error';
2222

2323
class Pool {
2424
/**
@@ -65,20 +65,17 @@ class Pool {
6565
allRequests[key] = [];
6666
}
6767

68-
let request;
68+
return new Promise((resolve, reject) => {
69+
let request;
6970

70-
return promiseOrTimeout(
71-
this._acquisitionTimeout,
72-
new Promise(
73-
(resolve, reject) => {
74-
request = new PendingRequest(resolve);
71+
const timeoutId = setTimeout(() => {
72+
allRequests[key] = allRequests[key].filter(item => item !== request);
73+
reject(newError(`Connection acquisition timed out in ${this._acquisitionTimeout} ms.`));
74+
}, this._acquisitionTimeout);
7575

76-
allRequests[key].push(request);
77-
}
78-
), () => {
79-
allRequests[key] = allRequests[key].filter(item => item !== request);
80-
}
81-
);
76+
request = new PendingRequest(resolve, timeoutId);
77+
allRequests[key].push(request);
78+
});
8279
}
8380

8481
/**
@@ -208,11 +205,13 @@ function resourceReleased(key, activeResourceCounts) {
208205

209206
class PendingRequest {
210207

211-
constructor(resolve) {
208+
constructor(resolve, timeoutId) {
212209
this._resolve = resolve;
210+
this._timeoutId = timeoutId;
213211
}
214212

215213
resolve(resource) {
214+
clearTimeout(this._timeoutId);
216215
this._resolve(resource);
217216
}
218217

src/v1/internal/util.js

-33
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
* limitations under the License.
1818
*/
1919

20-
import {newError} from '../error';
21-
2220
const ENCRYPTION_ON = "ENCRYPTION_ON";
2321
const ENCRYPTION_OFF = "ENCRYPTION_OFF";
2422

@@ -64,42 +62,11 @@ function isString(str) {
6462
return Object.prototype.toString.call(str) === '[object String]';
6563
}
6664

67-
function promiseOrTimeout(timeout, otherPromise, onTimeout) {
68-
let resultPromise = null;
69-
70-
const timeoutPromise = new Promise((resolve, reject) => {
71-
const id = setTimeout(() => {
72-
if (onTimeout && typeof onTimeout === 'function') {
73-
onTimeout();
74-
}
75-
76-
reject(newError(`Operation timed out in ${timeout} ms.`));
77-
}, timeout);
78-
79-
// this "executor" function is executed immediately, even before the Promise constructor returns
80-
// thus it's safe to initialize resultPromise variable here, where timeout id variable is accessible
81-
resultPromise = otherPromise.then(result => {
82-
clearTimeout(id);
83-
return result;
84-
}).catch(error => {
85-
clearTimeout(id);
86-
throw error;
87-
});
88-
});
89-
90-
if (resultPromise == null) {
91-
throw new Error('Result promise not initialized');
92-
}
93-
94-
return Promise.race([resultPromise, timeoutPromise]);
95-
}
96-
9765
export {
9866
isEmptyObjectOrNull,
9967
isString,
10068
assertString,
10169
assertCypherStatement,
102-
promiseOrTimeout,
10370
ENCRYPTION_ON,
10471
ENCRYPTION_OFF
10572
}

test/internal/pool.test.js

+56-3
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,10 @@ describe('Pool', () => {
419419
done();
420420
});
421421

422-
setTimeout(() => r1.close(), 1000);
422+
setTimeout(() => {
423+
expectNumberOfAcquisitionRequests(pool, key, 1);
424+
r1.close();
425+
}, 1000);
423426
});
424427
});
425428

@@ -445,7 +448,7 @@ describe('Pool', () => {
445448

446449
pool.acquire(key).catch(error => {
447450
expect(error.message).toContain('timed out');
448-
451+
expectNumberOfAcquisitionRequests(pool, key, 0);
449452
done();
450453
});
451454
});
@@ -472,14 +475,64 @@ describe('Pool', () => {
472475

473476
pool.acquire(key).then(r2 => {
474477
expect(r2.id).toEqual(2);
475-
478+
expectNoPendingAcquisitionRequests(pool);
476479
done();
477480
});
478481
});
479482
});
480483

484+
it('should work fine when resources released together with acquisition timeout', done => {
485+
const acquisitionTimeout = 1000;
486+
let counter = 0;
487+
488+
const key = 'bolt://localhost:7687';
489+
const pool = new Pool(
490+
(url, release) => new Resource(url, counter++, release),
491+
resource => {
492+
},
493+
() => true,
494+
new PoolConfig(2, acquisitionTimeout)
495+
);
496+
497+
pool.acquire(key).then(resource1 => {
498+
expect(resource1.id).toEqual(0);
499+
500+
pool.acquire(key).then(resource2 => {
501+
expect(resource2.id).toEqual(1);
502+
503+
// try to release both resources around the time acquisition fails with timeout
504+
// double-release used to cause deletion of acquire requests in the pool and failure of the timeout
505+
// such background failure made this test fail, not the existing assertions
506+
setTimeout(() => {
507+
resource1.close();
508+
resource2.close();
509+
}, acquisitionTimeout);
510+
511+
pool.acquire(key).then(someResource => {
512+
expect(someResource).toBeDefined();
513+
expect(someResource).not.toBeNull();
514+
expectNoPendingAcquisitionRequests(pool);
515+
done(); // ok, promise got resolved before the timeout
516+
}).catch(error => {
517+
expect(error).toBeDefined();
518+
expect(error).not.toBeNull();
519+
expectNoPendingAcquisitionRequests(pool);
520+
done(); // also ok, timeout fired before promise got resolved
521+
});
522+
});
523+
});
524+
});
525+
481526
});
482527

528+
function expectNoPendingAcquisitionRequests(pool) {
529+
expect(pool._acquireRequests).toEqual({});
530+
}
531+
532+
function expectNumberOfAcquisitionRequests(pool, key, expectedNumber) {
533+
expect(pool._acquireRequests[key].length).toEqual(expectedNumber);
534+
}
535+
483536
class Resource {
484537

485538
constructor(key, id, release) {

test/internal/util.test.js

-65
Original file line numberDiff line numberDiff line change
@@ -74,71 +74,6 @@ describe('util', () => {
7474
verifyInvalidCypherStatement(console.log);
7575
});
7676

77-
it('should time out', () => {
78-
expect(() => util.promiseOrTimeout(500, new Promise(), null)).toThrow();
79-
});
80-
81-
it('should not time out', done => {
82-
util.promiseOrTimeout(500, Promise.resolve(0), null).then((result) => {
83-
expect(result).toEqual(0);
84-
done();
85-
})
86-
});
87-
88-
it('should call clear action when timed out', done => {
89-
let marker = 0;
90-
91-
let clearAction = () => {
92-
marker = 1;
93-
};
94-
95-
util.promiseOrTimeout(500, new Promise((resolve, reject) => { }), clearAction).catch((error) => {
96-
expect(marker).toEqual(1);
97-
done();
98-
});
99-
});
100-
101-
it('should not trigger both promise and timeout', done => {
102-
const timeout = 500;
103-
104-
let timeoutFired = false;
105-
let result = null;
106-
let error = null;
107-
108-
const resultPromise = util.promiseOrTimeout(
109-
timeout,
110-
new Promise(resolve => {
111-
setTimeout(() => {
112-
resolve(42);
113-
}, timeout);
114-
}),
115-
() => {
116-
timeoutFired = true;
117-
}
118-
);
119-
120-
resultPromise.then(r => {
121-
result = r;
122-
}).catch(e => {
123-
error = e;
124-
});
125-
126-
setTimeout(() => {
127-
if (timeoutFired) {
128-
// timeout fired - result should not be set, error should be set
129-
expect(result).toBeNull();
130-
expect(error).not.toBeNull();
131-
expect(error.message).toEqual(`Operation timed out in ${timeout} ms.`);
132-
done();
133-
} else {
134-
// timeout did not fire - result should be set, error should not be set
135-
expect(result).toEqual(42);
136-
expect(error).toBeNull();
137-
done();
138-
}
139-
}, timeout * 2);
140-
});
141-
14277
function verifyValidString(str) {
14378
expect(util.assertString(str, 'Test string')).toBe(str);
14479
}

0 commit comments

Comments
 (0)