-
Notifications
You must be signed in to change notification settings - Fork 5
fix: Wait for the tunnel to close before finishing client close COMPASS-4474 #352
Changes from all commits
51f0a80
e4c4576
61b0283
5beed8c
7e1ac00
851db7e
97ebcd3
6158306
6a4c2b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
const { EventEmitter } = require('events'); | ||
const { EventEmitter, once } = require('events'); | ||
const fs = require('fs'); | ||
const async = require('async'); | ||
const { | ||
|
@@ -12,7 +12,7 @@ const { | |
const { MongoClient } = require('mongodb'); | ||
const { parseConnectionString } = require('mongodb/lib/core'); | ||
const Connection = require('./extended-model'); | ||
const createSSHTunnel = require('./ssh-tunnel'); | ||
const { default: SSHTunnel } = require('@mongodb-js/ssh-tunnel'); | ||
|
||
const debug = require('debug')('mongodb-connection-model:connect'); | ||
|
||
|
@@ -125,8 +125,9 @@ const getTasks = (model, setupListeners) => { | |
const tasks = {}; | ||
const _statuses = {}; | ||
let options = {}; | ||
let tunnel; | ||
let client; | ||
/** @type {SSHTunnel} */ | ||
let tunnel = null; | ||
let client = null; | ||
|
||
const status = (message, cb) => { | ||
if (_statuses[message]) { | ||
|
@@ -189,19 +190,27 @@ const getTasks = (model, setupListeners) => { | |
}); | ||
|
||
assign(tasks, { | ||
[Tasks.CreateSSHTunnel]: (cb) => { | ||
const ctx = status('Create SSH Tunnel', cb); | ||
[Tasks.CreateSSHTunnel]: async() => { | ||
const ctx = status('Create SSH Tunnel'); | ||
|
||
if (model.sshTunnel === 'NONE') { | ||
return ctx.skip('The selected SSH Tunnel mode is NONE.'); | ||
} | ||
|
||
tunnel = createSSHTunnel(model, ctx); | ||
tunnel = new SSHTunnel(model.sshTunnelOptions); | ||
|
||
try { | ||
await tunnel.listen(); | ||
ctx(null); | ||
} catch (err) { | ||
ctx(err); | ||
throw err; | ||
} | ||
} | ||
}); | ||
|
||
assign(tasks, { | ||
[Tasks.ConnectToMongoDB]: (cb) => { | ||
[Tasks.ConnectToMongoDB]: async() => { | ||
const ctx = status('Connect to MongoDB'); | ||
|
||
// @note: Durran: | ||
|
@@ -215,6 +224,7 @@ const getTasks = (model, setupListeners) => { | |
|
||
validOptions.useNewUrlParser = true; | ||
validOptions.useUnifiedTopology = true; | ||
|
||
if ( | ||
model.directConnection === undefined && | ||
model.hosts.length === 1 && | ||
|
@@ -234,29 +244,37 @@ const getTasks = (model, setupListeners) => { | |
setupListeners(mongoClient); | ||
} | ||
|
||
mongoClient.connect((err, _client) => { | ||
ctx(err); | ||
|
||
if (err) { | ||
if (tunnel) { | ||
debug('data-service connection error, shutting down ssh tunnel'); | ||
tunnel.close(); | ||
/** @type {Promise<never>} */ | ||
const waitForTunnelError = (async() => { | ||
const [error] = await once(tunnel || new EventEmitter(), 'error'); | ||
throw error; | ||
})(); | ||
|
||
const closeTunnelOnError = async(tunnelToClose) => { | ||
if (tunnelToClose) { | ||
debug('data-service connection error, shutting down ssh tunnel'); | ||
try { | ||
await tunnelToClose.close(); | ||
debug('ssh tunnel stopped'); | ||
} catch (err) { | ||
debug('ssh tunnel stopped with error: %s', err.message); | ||
} | ||
|
||
return cb(err); | ||
} | ||
}; | ||
|
||
try { | ||
const _client = await Promise.race([ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isn't this: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We have a whole suite of tests for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! makes a lot of sense :), could have checked myself. |
||
mongoClient.connect(), | ||
waitForTunnelError | ||
]); | ||
Comment on lines
+266
to
+269
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really happy with this bit here as we are not really waiting for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point! Are user actionable errors about the tunnel thrown by The one thing I was assuming here is this: all the ssh related errors are thrown on If that is true i think we can even ignore the race with For sure we could give more info but is super fine, we are making it connect when it should, which now does not, we already improve the user life immensely. If however instead errors like wrong password can only be catched by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This doesn't happen until first connection happens, tunnel is established for every incoming connection to ssh tunnel server, so things like forward out are just not happening before that. If we want them to be thrown when So in current implementation both errors emitted by tunnel or provided in callback can be helpful. But as mentioned, if tunnel emitted one, it probably will have a higher priority in terms of being useful to the user. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When it gets to forward the connection to ssh should already be established, isn't it?
why? can't we just both create a new tunnel and 'forwarding tcp server' on listen instead? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
client = _client; | ||
|
||
if (tunnel) { | ||
client.on('close', () => { | ||
debug('data-service disconnected. shutting down ssh tunnel'); | ||
tunnel.close(); | ||
}); | ||
} | ||
|
||
cb(null, { url: model.driverUrlWithSsh, options: validOptions }); | ||
}); | ||
ctx(null); | ||
return { url: model.driverUrlWithSsh, options: validOptions }; | ||
} catch (err) { | ||
await closeTunnelOnError(tunnel); | ||
ctx(err); | ||
throw err; | ||
} | ||
} | ||
}); | ||
|
||
|
@@ -332,7 +350,7 @@ const connect = (model, setupListeners, done) => { | |
|
||
logTaskStatus('Successfully connected'); | ||
|
||
return done(null, tasks.client, connectionOptions); | ||
return done(null, tasks.client, tasks.tunnel, connectionOptions); | ||
}); | ||
|
||
return tasks.state; | ||
|
This file was deleted.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😍