diff --git a/nodejs/models/host.js b/nodejs/models/host.js index 335616b..f006abe 100755 --- a/nodejs/models/host.js +++ b/nodejs/models/host.js @@ -290,9 +290,15 @@ class Host extends Table{ complex looks with wildcards. */ - // Hold lookUp ready while the look up object is being built. - this.__lookUpIsReady = false; - this.lookUpObj = {}; + // Build into a fresh, local tree instead of mutating the live one. + // buildLookUpObj is async (it awaits a redis get() per host) and runs on + // every host create/update/remove. If we wiped and repopulated the live + // this.lookUpObj in place, any concurrent lookUp() — which is called + // synchronously by the host_lookup service and never waits for readiness — + // would resolve against a half-built tree and randomly miss defined hosts. + // We only swap the completed tree in at the very end, so lookUp() always + // sees a complete tree (either the previous one or the new one). + let lookUpObj = {}; try{ @@ -303,7 +309,7 @@ class Host extends Table{ let fragments = host.split('.'); // Hold a pointer to the root of the lookup tree. - let pointer = this.lookUpObj; + let pointer = lookUpObj; // Walk over each fragment, popping from right to left. while(fragments.length){ @@ -325,7 +331,8 @@ class Host extends Table{ } } - // When the look up tree is finished, remove the ready hold. + // Atomically publish the completed tree and mark lookUp ready. + this.lookUpObj = lookUpObj; this.__lookUpIsReady = true; }catch(error){ diff --git a/nodejs/services/host_lookup.js b/nodejs/services/host_lookup.js index aafa546..1e28c02 100644 --- a/nodejs/services/host_lookup.js +++ b/nodejs/services/host_lookup.js @@ -35,6 +35,14 @@ const socket = new SocketServerJson({ // If we don't have a match, return empty object if(!parentHost) return clientSocket.write(JSON.stringify({})); + // lookUp returns the live #record object stored inside the shared + // lookup tree. Everything below mutates parentHost (sets + // wildcard_parent, stringifies every value), so work on a shallow + // copy — mutating the shared node would corrupt the tree, e.g. turn + // wildcard_matchAny into the string "false" (truthy) and break the + // matchAny guard on subsequent lookups. + parentHost = {...parentHost}; + // A wildcard host with matchAny disabled only serves subdomains that // are explicitly defined in redis. Reaching this service means redis // had no direct entry for the requested domain, so an inexact match