diff --git a/src/bun.js/api/bun/dns_resolver.zig b/src/bun.js/api/bun/dns_resolver.zig index ac3ec4b288..7ce0caf0a6 100644 --- a/src/bun.js/api/bun/dns_resolver.zig +++ b/src/bun.js/api/bun/dns_resolver.zig @@ -3043,6 +3043,13 @@ pub const DNSResolver = struct { return resolver.toJS(globalThis); } + pub fn cancel(this: *DNSResolver, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue { + _ = callframe; + const channel = try this.getChannelOrError(globalThis); + c_ares.ares_cancel(channel); + return .undefined; + } + // Resolves the given address and port into a host name and service using the operating system's underlying getnameinfo implementation. // If address is not a valid IP address, a TypeError will be thrown. The port will be coerced to a number. // If it is not a legal port, a TypeError will be thrown. diff --git a/src/bun.js/bindings/BunObject.cpp b/src/bun.js/bindings/BunObject.cpp index 3c5c728679..6a17148b90 100644 --- a/src/bun.js/bindings/BunObject.cpp +++ b/src/bun.js/bindings/BunObject.cpp @@ -58,6 +58,7 @@ BUN_DECLARE_HOST_FUNCTION(Bun__DNS__lookupService); BUN_DECLARE_HOST_FUNCTION(Bun__DNS__prefetch); BUN_DECLARE_HOST_FUNCTION(Bun__DNS__getCacheStats); BUN_DECLARE_HOST_FUNCTION(Bun__DNSResolver__new); +BUN_DECLARE_HOST_FUNCTION(Bun__DNSResolver__cancel); BUN_DECLARE_HOST_FUNCTION(Bun__fetch); BUN_DECLARE_HOST_FUNCTION(Bun__fetchPreconnect); BUN_DECLARE_HOST_FUNCTION(Bun__randomUUIDv7); @@ -378,7 +379,7 @@ static JSValue constructDNSObject(VM& vm, JSObject* bunObject) JSC::PropertyAttribute::DontDelete | 0); dnsObject->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "getCacheStats"_s), 0, Bun__DNS__getCacheStats, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::DontDelete | 0); - dnsObject->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "newResolver"_s), 0, Bun__DNSResolver__new, ImplementationVisibility::Public, NoIntrinsic, + dnsObject->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "newResolver"_s), 1, Bun__DNSResolver__new, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::DontDelete | 0); dnsObject->putDirect(vm, JSC::Identifier::fromString(vm, "ADDRCONFIG"_s), jsNumber(AI_ADDRCONFIG), JSC::PropertyAttribute::DontDelete | 0); diff --git a/src/bun.js/node/node.classes.ts b/src/bun.js/node/node.classes.ts index 5b7ab7d6b3..c5697047d5 100644 --- a/src/bun.js/node/node.classes.ts +++ b/src/bun.js/node/node.classes.ts @@ -57,6 +57,10 @@ export default [ fn: "resolveCname", length: 1, }, + cancel: { + fn: "cancel", + length: 0, + }, }, }), define({ diff --git a/src/js/node/dns.ts b/src/js/node/dns.ts index 579e4240d1..691ac9776e 100644 --- a/src/js/node/dns.ts +++ b/src/js/node/dns.ts @@ -274,7 +274,9 @@ var InternalResolver = class Resolver { this.#resolver = this._handle = dns.newResolver(options); } - cancel() {} + cancel() { + this.#resolver.cancel(); + } static #getResolver(object) { return #resolver in object ? object.#resolver : dns; @@ -751,7 +753,9 @@ const promises = { this.#resolver = this._handle = dns.newResolver(options); } - cancel() {} + cancel() { + this.#resolver.cancel(); + } static #getResolver(object) { return #resolver in object ? object.#resolver : dns; diff --git a/test/js/node/test/parallel/test-dns-channel-cancel.js b/test/js/node/test/parallel/test-dns-channel-cancel.js new file mode 100644 index 0000000000..405b31e4cc --- /dev/null +++ b/test/js/node/test/parallel/test-dns-channel-cancel.js @@ -0,0 +1,46 @@ +'use strict'; +const common = require('../common'); +const { Resolver } = require('dns'); +const assert = require('assert'); +const dgram = require('dgram'); + +const server = dgram.createSocket('udp4'); +const resolver = new Resolver(); + +const desiredQueries = 11; +let finishedQueries = 0; + +server.bind(0, common.mustCall(async () => { + resolver.setServers([`127.0.0.1:${server.address().port}`]); + + const callback = common.mustCall((err, res) => { + assert.strictEqual(err.code, 'ECANCELLED'); + assert.strictEqual(err.syscall, 'queryA'); + assert.strictEqual(err.hostname, `example${finishedQueries}.org`); + + finishedQueries++; + if (finishedQueries === desiredQueries) { + server.close(); + } + }, desiredQueries); + + const next = (...args) => { + callback(...args); + + server.once('message', () => { + resolver.cancel(); + }); + + // Multiple queries + for (let i = 1; i < desiredQueries; i++) { + resolver.resolve4(`example${i}.org`, callback); + } + }; + + server.once('message', () => { + resolver.cancel(); + }); + + // Single query + resolver.resolve4('example0.org', next); +}));