getdnsapi / getdns-node

Node.js bindings of getdns, a modern asynchronous DNS API.
https://getdnsapi.net/
Other
64 stars 8 forks source link

The transaction id handling is unleashing Zalgo #20

Open joelpurra opened 7 years ago

joelpurra commented 7 years ago

It might seem contrived, but it's showing that Zalgo is indeed unleashed.

During testing, and possibly in the wild, the callback being called means the lookup completed, the test is over, and the context can be destroyed. This unfortunately also prevents further synchronous and asynchronous expectations from being checked (or at least from being reported). This means it's hard to tell if/how/where the code will continue to execute.

The following test is written to show that a transaction should not be able to be cancelled twice. The first call to cancel() should return true, second call should false. The second cancel() call is never invoked, within the scope of the test. Outside of the scope of collecting test results it might be called though -- with or without side-effect. (The callback might also inadvertently be called twice, which is a big node.js no-no.) The test passes, despite the synchronous call to expect().fail().

// Take from the tests, executed using mocha.
it("Should not be able to synchronously cancel the query twice", function(done) {
    const ctx = getdns.createContext({
        resolution_type: getdns.RESOLUTION_STUB,
    });

    let callCount = 0;

    const transId = ctx.address("nlnetlabs.nl", (err, result) => {
        // TODO BUG: this callback is being called synchronously due to cancel() being synchronous.
        // TODO BUG: the callback might be called twice. This might not be detectable after the test has ended.
        callCount++;
        expect(callCount).to.be(1);

        expect(err).to.be.an("object");
        expect(result).to.be(null);
        expect(err).to.have.property("msg");
        expect(err).to.have.property("code");
        expect(err.code).to.equal(getdns.CALLBACK_CANCEL);
        shared.destroyContext(ctx, done);
    });

    expect(transId).to.be.ok();

    const cancelResult = ctx.cancel(transId);

    // TODO BUG: this code is never reached, at least not within the scope of the test.
    expect().fail("Something is wrong, because this is never called (or at least not reported).");

    expect(cancelResult).to.be.ok();

    // NOTE: should not be able to cancel the same transaction twice.
    const cancelResult2 = ctx.cancel(transId);
    expect(cancelResult2).to.not.be.ok();
});

Unleasing Zalgo means that work might perform work synchronously, or it might performed asynchronously. With connected functions, such as those on the context object, all public functions should be either synchronous or asynchronous. As the lookups rely on callbacks, all functions need to be asynchronous.

It will most likely require a bunch of work to redesign the getdns-node api to be consistent and more node.js-like. The question is if the current style should be accessible (to be similar to the getdns API for the same of being similar) or if this should be a node.js API built on top of the C API getdns. Any comments?