Skip to content

WeightedConnectionPool.getConnection returns null that leads to NoLivingConnectionsError error #53

@jodevsa

Description

@jodevsa

🐛 Bug Report

requests sent after 6 requests that failed due to a timeout with a weightedConnectionPool that has 2 upstreams will fail with NoLivingConnectionsError even if the upstreams is running again and not timing out anymore.

This is mostly due to the wrong assumption mentioned here:
https://github.com/elastic/elastic-transport-js/blob/main/src/pool/WeightedConnectionPool.ts#L54

if the GCD is 1 and both weights reached 1 after the subtraction we would need about 800 loops for current weight to reach 1 to be able to choose one of the weights

To Reproduce

Steps to reproduce the behavior:
1- clone my branch #54 which has a new test that should reproduce the bug
2- npm run build && node_modules/tap/bin/run.js test/unit/transport.test.ts

Paste your code here:

test('upstreams are down for the first 7 requests', async t => {


  const pool = new WeightedConnectionPool({ Connection: MockConnectionTimeoutForThefirstSevenRequests })

  pool.addConnection('https://localhost:9200')
  pool.addConnection('https://localhost:9201')

  const transport = new Transport({ connectionPool: pool, maxRetries: 0 })
  for(let i=0;i<20;i++){
  try {
    await transport.request({
      method: 'GET',
      path: '/hello'
    })
  } catch (err: any) {
    console.log(err.name)
    t.equal(err.name, 'TimeoutError')
  }
}
})




export class MockConnectionTimeoutForThefirstSevenRequests extends BaseConnection {
  requestCount = -1
  async request (params: ConnectionRequestParams, options: ConnectionRequestOptions): Promise<ConnectionRequestResponse>
  async request (params: ConnectionRequestParams, options: ConnectionRequestOptionsAsStream): Promise<ConnectionRequestResponseAsStream>
  async request (params: ConnectionRequestParams, options: any): Promise<any> {
    
    return new Promise((resolve, reject) => {

      this.requestCount++;
      if( this.requestCount<=6){
      process.nextTick(reject, new TimeoutError('Request timed out'))
      }
  


      const body = JSON.stringify({ hello: 'world' })
      const statusCode = setStatusCode(params.path)
      const headers = {
        'content-type': 'application/json;utf=8',
        date: new Date().toISOString(),
        connection: 'keep-alive',
        'content-length': '17',
        'x-elastic-product': 'Elasticsearch'
      }
      process.nextTick(resolve, { body, statusCode, headers })
    })
  }
}

Expected behavior

we are able to use the pool after the upstreams are up again

Your Environment

  • node version: v16.0.0
  • master branch of this repo
  • os: Mac

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions