diff --git a/@vates/nbd-client/.USAGE.md b/@vates/nbd-client/.USAGE.md index da7bfdf3aa1..ddbbd3b96cd 100644 --- a/@vates/nbd-client/.USAGE.md +++ b/@vates/nbd-client/.USAGE.md @@ -1,19 +1,16 @@ -### `new NdbClient({address, exportname, secure = true, port = 10809})` - -create a new nbd client +### `new NdbClient({address, exportname, secure = true, port = 10809})` +create a new nbd client ```js +import NbdClient from '@vates/nbd-client' +const client = new NbdClient({ + address: 'MY_NBD_HOST', + exportname: 'MY_SECRET_EXPORT', + cert: 'Server certificate', // optional, will use encrypted link if provided +}) - import NbdClient from '@vates/nbd-client' - const client = new NbdClient({ - address: 'MY_NBD_HOST', - exportname: 'MY_SECRET_EXPORT', - cert: 'Server certificate', // optional, will use encrypted link if provided - }) - - await client.connect() - const block = await client.readBlock(blockIndex, BlockSize) - await client.disconnect() - +await client.connect() +const block = await client.readBlock(blockIndex, BlockSize) +await client.disconnect() ``` diff --git a/@vates/nbd-client/index.js b/@vates/nbd-client/index.js index ed14af1005b..3be9518be02 100644 --- a/@vates/nbd-client/index.js +++ b/@vates/nbd-client/index.js @@ -50,20 +50,17 @@ module.exports = class NbdClient { async #tlsConnect() { return new Promise((resolve, reject) => { - this.#serverSocket = connect( - { - socket: this.#serverSocket, - rejectUnauthorized: false, - cert: this.#serverCert, - }, - resolve - ) - this.#serverSocket.once('error', reject) - // @todo : why is this never called ? - this.#serverSocket.once('connect', () => { + this.#serverSocket = connect({ + socket: this.#serverSocket, + rejectUnauthorized: false, + cert: this.#serverCert, + }) + this.#serverSocket.once('secureConnect', () => { this.#serverSocket.removeListener('error', reject) resolve() }) + + this.#serverSocket.once('error', reject) }) } @@ -85,6 +82,11 @@ module.exports = class NbdClient { // to tls during the handshake await this.#unsecureConnect() await this.#handshake() + + // reset internal state if we reconnected a nbd client + this.#commandQueryBacklog = new Map() + this.#nbCommands = 0 + this.#waitingForResponse = false } async disconnect() { @@ -163,10 +165,10 @@ module.exports = class NbdClient { // when one read fail ,stop everything async #rejectAll(error) { - this.this.#commandQueryBacklog.forEach(({ reject }, blockQueryId) => { + this.#commandQueryBacklog.forEach(({ reject }) => { reject(error) - this.#commandQueryBacklog.delete(blockQueryId) }) + await this.disconnect() } async #readBlockResponse() { @@ -175,32 +177,39 @@ module.exports = class NbdClient { if (this.#waitingForResponse) { return } - this.#waitingForResponse = true - this.#nbCommands-- - const magic = await this.#readInt32() - if (magic !== NBD_REPLY_MAGIC) { - return this.#rejectAll(new Error(`magic number for block answer is wrong : ${magic} ${NBD_REPLY_MAGIC}`)) - } - - const error = await this.#readInt32() - if (error !== 0) { - // @todo use error code from constants.mjs - return this.#rejectAll(new Error(`GOT ERROR CODE : ${error}`)) - } - - const blockQueryId = await this.#readInt64() - const query = this.#commandQueryBacklog.get(blockQueryId) - if (!query) { - return this.#rejectAll(new Error(` no query associated with id ${blockQueryId}`)) - } - this.#commandQueryBacklog.delete(blockQueryId) - const data = await this.#read(query.size) - query.resolve(data) - - this.#waitingForResponse = false - if (this.#nbCommands > 0) { - await this.#readBlockResponse() + try { + this.#waitingForResponse = true + this.#nbCommands-- + const magic = await this.#readInt32() + + if (magic !== NBD_REPLY_MAGIC) { + throw new Error(`magic number for block answer is wrong : ${magic} ${NBD_REPLY_MAGIC}`) + } + + const error = await this.#readInt32() + if (error !== 0) { + // @todo use error code from constants.mjs + throw new Error(`GOT ERROR CODE : ${error}`) + } + + const blockQueryId = await this.#readInt64() + const query = this.#commandQueryBacklog.get(blockQueryId) + if (!query) { + throw new Error(` no query associated with id ${blockQueryId}`) + } + this.#commandQueryBacklog.delete(blockQueryId) + const data = await this.#read(query.size) + query.resolve(data) + this.#waitingForResponse = false + if (this.#nbCommands > 0) { + await this.#readBlockResponse() + } + } catch (error) { + // reject all the promises + // we don't need to call readBlockResponse on failure + // since we will empty the backlog + await this.#rejectAll(error) } } @@ -227,8 +236,11 @@ module.exports = class NbdClient { reject, }) // really send the command to the server - this.#write(buffer).then(() => this.#readBlockResponse()) - // it can never reject + this.#write(buffer).catch(reject) + + // #readBlockResponse never throws directly + // but if it fails it will reject all the promises in the backlog + this.#readBlockResponse() }) } }