Skip to content

Commit d992409

Browse files
committed
Fixed timeout when acquiring connection from pool
Connection pool has limited size and returns `Promise<Connection>` instead of `Connection` directly. Returned promise can be failed when connection acquisition timeout has fired. Pool uses helper function `util#promiseOrTimeout()` to race acquisition and timeout promises. This function allowed both timeout callback to be invoked and connection to be acquired. It cleared the timeout outside of the raced promises. As result connection was acquired but timeout callback failed in background. It tried to clear non-existing pending acquisition attempts. This commit fixes the problem by including `#clearTimeout()` call in the chain of raced promises.
1 parent 9040f4e commit d992409

File tree

3 files changed

+60
-5
lines changed

3 files changed

+60
-5
lines changed

src/v1/internal/pool.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,10 @@ class Pool {
162162
// check if there are any pending requests
163163
const requests = this._acquireRequests[key];
164164
if (requests) {
165-
var pending = requests.shift();
165+
const pending = requests.shift();
166166

167167
if (pending) {
168-
var resource = this._acquire(key);
168+
const resource = this._acquire(key);
169169
if (resource) {
170170
pending.resolve(resource);
171171

src/v1/internal/util.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* limitations under the License.
1818
*/
1919

20-
import { newError } from "../error";
20+
import {newError} from '../error';
2121

2222
const ENCRYPTION_ON = "ENCRYPTION_ON";
2323
const ENCRYPTION_OFF = "ENCRYPTION_OFF";
@@ -125,6 +125,8 @@ function trimAndVerify(string, name, url) {
125125
}
126126

127127
function promiseOrTimeout(timeout, otherPromise, onTimeout) {
128+
let resultPromise = null;
129+
128130
const timeoutPromise = new Promise((resolve, reject) => {
129131
const id = setTimeout(() => {
130132
if (onTimeout && typeof onTimeout === 'function') {
@@ -134,10 +136,22 @@ function promiseOrTimeout(timeout, otherPromise, onTimeout) {
134136
reject(newError(`Operation timed out in ${timeout} ms.`));
135137
}, timeout);
136138

137-
otherPromise.then(() => clearTimeout(id), () => clearTimeout(id));
139+
// this "executor" function is executed immediately, even before the Promise constructor returns
140+
// thus it's safe to initialize resultPromise variable here, where timeout id variable is accessible
141+
resultPromise = otherPromise.then(result => {
142+
clearTimeout(id);
143+
return result;
144+
}).catch(error => {
145+
clearTimeout(id);
146+
throw error;
147+
});
138148
});
139149

140-
return Promise.race([ otherPromise, timeoutPromise ]);
150+
if (resultPromise == null) {
151+
throw new Error('Result promise not initialized');
152+
}
153+
154+
return Promise.race([resultPromise, timeoutPromise]);
141155
}
142156

143157
export {

test/internal/util.test.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,47 @@ describe('util', () => {
204204
});
205205
});
206206

207+
it('should not trigger both promise and timeout', done => {
208+
const timeout = 500;
209+
210+
let timeoutFired = false;
211+
let result = null;
212+
let error = null;
213+
214+
const resultPromise = util.promiseOrTimeout(
215+
timeout,
216+
new Promise(resolve => {
217+
setTimeout(() => {
218+
resolve(42);
219+
}, timeout);
220+
}),
221+
() => {
222+
timeoutFired = true;
223+
}
224+
);
225+
226+
resultPromise.then(r => {
227+
result = r;
228+
}).catch(e => {
229+
error = e;
230+
});
231+
232+
setTimeout(() => {
233+
if (timeoutFired) {
234+
// timeout fired - result should not be set, error should be set
235+
expect(result).toBeNull();
236+
expect(error).not.toBeNull();
237+
expect(error.message).toEqual(`Operation timed out in ${timeout} ms.`);
238+
done();
239+
} else {
240+
// timeout did not fire - result should be set, error should not be set
241+
expect(result).toEqual(42);
242+
expect(error).toBeNull();
243+
done();
244+
}
245+
}, timeout * 2);
246+
});
247+
207248
function verifyValidString(str) {
208249
expect(util.assertString(str, 'Test string')).toBe(str);
209250
}

0 commit comments

Comments
 (0)