oracle / node-oracledb

Oracle Database driver for Node.js maintained by Oracle Corp.
http://oracle.github.io/node-oracledb/
Other
2.24k stars 1.07k forks source link

Lob.destroy does not release the connection immediately #1538

Closed sosoba closed 1 year ago

sosoba commented 1 year ago
  1. What versions are you using? 5.5.0
  2. Is it an error or a hang or a crash? Error
  3. What error(s) or behavior you are seeing? NJS-081: concurrent operations on a connection are disabled
  4. Include a runnable Node.js script that shows the problem.
    
    import oracledb from 'oracledb';
    import { pipeline } from 'node:stream/promises';
    import { Writable } from 'node:stream';
    import { callbackify } from 'node:util';
    import { setTimeout } from 'node:timers/promises';

const failOutputStream = new Writable({ write: callbackify(async () => { await setTimeout(1); throw new Error('No left space on device'); }) });

oracledb.errorOnConcurrentExecute = true;

const conn = await oracledb.getConnection({ user: '', password: '', connectString: '', });

const {rows: [[lob]]} = await conn.execute("select lob_column from lob_table"); try { await pipeline(lob, failOutputStream); } catch (e) { console.log('Cannot copy blob to file. Reason:', e.message); }

console.log('closed:', lob.closed, 'destroyed:', lob.destroyed, 'errored:', !!lob.errored, 'inProgress:', conn._inProgress); // closed: false destroyed: true errored: true inProgress: true

await conn.execute('SELECT * FROM DUAL'); // Throws NJS-081: concurrent operations on a connection are disabled



LOB is transferred to an output stream which emits an error. The `pipeline` internally catch error from output stream and pass them to `lob.destroy(err)`. Oracledb documentation says: [This synchronous method explicitly destroys a Lob.](https://node-oracledb.readthedocs.io/en/latest/api_manual/lob.html#lob.destroy). It seems that there is internal race:

Lob waiting for next chunk and locking connection:
https://github.com/oracle/node-oracledb/blob/8388891eb59a13403d607543b578f6e7aa876da8/lib/lob.js#L106-L108

But simultaneously lob changes state to "destroyed an errored" and fire `error` event without releasing the connection: 
https://github.com/oracle/node-oracledb/blob/8388891eb59a13403d607543b578f6e7aa876da8/lib/lob.js#L81-L84

Finally `pipeline` finishes but connection are still locked and throw error next time you use it.
anthony-tuininga commented 1 year ago

The destroy() method is the standard stream method and not one specific to node-oracledb. It is true that node-oracledb requires the use of the connection to destroy the LOB and therefore may require a round trip and is asynchronous in nature. You can resolve the matter by using this code:

  await new Promise((resolve) => {
    lob.on('close', resolve);
    lob.destroy();
  });

I'm not sure if there is a better approach but this one certainly works!

stale[bot] commented 1 year ago

This issue has been automatically marked as inactive because it has not been updated recently. It will be closed if no further activity occurs. Thank you for your contributions.