ExportXMLWordPrintableJSON

    • Type: Bug
    • Resolution: Unresolved
    • Priority: Unknown
    • None
    • Affects Version/s: 7.6.0
    • Component/s: Networking
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      Question: is it intended that the Node driver's autoSelectFamily default of true
      makes Node's process-level opt-out unreachable for MongoDB sockets?

      Expected

      Node exposes a process-wide default for Happy Eyeballs, settable three documented ways:

      • --no-network-family-autoselection
      • NODE_OPTIONS="--no-network-family-autoselection"
      • net.setDefaultAutoSelectFamily(false)

      We expected a MongoClient constructed without an explicit autoSelectFamily option to
      inherit that process default, as other socket-level behavior does.

      Actual

      All three are silently ignored for driver sockets. The driver passes
      autoSelectFamily: true explicitly on every net.createConnection() call, and a
      per-socket option overrides the process default.

      node process default autoSelectFamily : false
      value driver passes to the socket     : true
      autoSelectFamilyAttemptTimeout passed : undefined
      

      Identical output for all three forms above.

      Minimal reproduction

      No running mongod required – the socket call is intercepted before connecting.

      // repro.js
      const net = require('net');
      
      net.createConnection = function (opts) {
        console.log('node process default autoSelectFamily :', net.getDefaultAutoSelectFamily());
        console.log('value driver passes to the socket     :', opts.autoSelectFamily);
        console.log('autoSelectFamilyAttemptTimeout passed :', opts.autoSelectFamilyAttemptTimeout);
        process.exit(0);
      };
      
      const { MongoClient } = require('mongodb');
      new MongoClient('mongodb://127.0.0.1:27017/?directConnection=true')
        .connect()
        .catch(() => {});
      
      node --no-network-family-autoselection repro.js
      

      Where it comes from

      lib/connection_string.js:565 declares a hard default:

      autoSelectFamily: {
          type: 'boolean',
          default: true
      }
      

      lib/cmap/connect.js:226 copies any non-null legal socket option onto the connect call:

      for (const name of exports.LEGAL_TCP_SOCKET_OPTIONS) {
          if (options[name] != null) {
              result[name] = options[name];
          }
      }
      

      Because the default is true, the value is never null, so it is always copied, so the
      per-socket value always wins over the process default.

      The part that looks unintended

      The sibling option behaves the opposite way. autoSelectFamilyAttemptTimeout is declared
      with no default, stays undefined, is therefore not copied, and the process-level
      --network-family-autoselection-attempt-timeout does take effect.

      Option Driver default Passed to socket Process-level setting honored
      autoSelectFamily true always no
      autoSelectFamilyAttemptTimeout none never yes

      So of two adjacent options controlling the same Node feature, one honors process-level
      configuration and one cannot. The TypeScript declaration also types it as optional
      (autoSelectFamily?: boolean, mongodb.d.ts:8518), implying "unset" is a meaningful
      state that the runtime never actually permits.

      We also noticed TODO(NODE-6449) in
      lib/client-side-encryption/auto_encrypter.js:183 regarding autoSelectFamily not being
      threaded through client options, which suggests adjacent plumbing has already been flagged.

      Why it mattered for us

      Istio in ambient mode auto-allocates both an IPv4 and an IPv6 synthetic address for every
      ServiceEntry, regardless of whether the cluster is dual-stack. In our IPv4-only staging
      cluster this made DNS return two addresses where it previously returned one, which armed
      Happy Eyeballs and dropped the effective connect deadline from connectTimeoutMS (30s) to
      the 250ms per-attempt budget. CPU-bound services routinely exceed 250ms of event loop lag,
      so connects began failing as MongoNetworkError: connect ETIMEDOUT wrapping an
      AggregateError, cascading into repeated MongoPoolClearedError (one pod reached
      connectionGeneration: 295).

      --no-network-family-autoselection is the standard mitigation and is what we reached for
      first. It deployed cleanly, changed nothing, and gave no indication it had not applied –
      the only signal was that internalConnectMultiple frames were still present in stack
      traces. Setting autoSelectFamily=false&family=4 on the connection string resolved it.

      Suggested resolution

      Removing default: true from the option descriptor would make the process-level setting
      reachable while preserving current behavior for everyone who has not opted out: with the
      option unset it simply would not be passed, and Node's own default (true on Node >= 20)
      would apply. That appears backward compatible.

      If the hard default is deliberate – i.e. the driver intends to own this setting regardless
      of process configuration – that would be worth stating in the autoSelectFamily docs,
      since the current documentation does not indicate that Node's own flags will not apply.

            Assignee:
            Unassigned
            Reporter:
            Greg Freeland (EXT)
            None
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: