@@ -118,8 +118,8 @@ type PathInfo struct {
118118
119119// dnsEnrichmentRequest represents a request to enrich a record with DNS lookup
120120type dnsEnrichmentRequest struct {
121- ip string
122- resultChan chan dnsEnrichmentResult
121+ ip string
122+ callback func ( dnsEnrichmentResult ) // invoked by the worker goroutine when the lookup completes
123123}
124124
125125// dnsEnrichmentResult contains the result of a DNS lookup
@@ -262,11 +262,8 @@ func (c *Correlator) processDNSRequest(req dnsEnrichmentRequest) {
262262 c .dnsCache .Set (req .ip , hostname )
263263 }
264264
265- // Send result back (non-blocking with context check)
266- select {
267- case req .resultChan <- result :
268- case <- c .ctx .Done ():
269- }
265+ // Invoke the callback directly in the worker goroutine (no channel needed)
266+ req .callback (result )
270267}
271268
272269// performDNSLookup does the actual reverse DNS lookup
@@ -292,7 +289,7 @@ func (c *Correlator) performDNSLookup(ctx context.Context, ipStr string) string
292289
293290// enrichWithDNSSync performs synchronous DNS enrichment for an IP address
294291// Only returns hostname if it's already in cache (fast path)
295- // Returns (hostname, needsAsync) where needsAsync=true means caller should use enrichWithDNSBlocking
292+ // Returns (hostname, needsAsync) where needsAsync=true means caller should use submitDNSCallback
296293func (c * Correlator ) enrichWithDNSSync (ipStr string ) (string , bool ) {
297294 if ! c .enableDNSEnrichment || ipStr == "" {
298295 return "" , false
@@ -311,36 +308,50 @@ func (c *Correlator) enrichWithDNSSync(ipStr string) (string, bool) {
311308 return "" , true
312309}
313310
314- // enrichWithDNSBlocking sends a DNS lookup request to the worker pool and blocks until the result is ready
315- func (c * Correlator ) enrichWithDNSBlocking (ipStr string ) string {
316- if ! c .enableDNSEnrichment || ipStr == "" {
317- return ""
318- }
319-
320- resultChan := make (chan dnsEnrichmentResult , 1 )
311+ // submitDNSCallback submits a DNS lookup to the worker pool with a callback that is
312+ // invoked by the worker goroutine when the lookup completes. It is non-blocking: if
313+ // the request channel has capacity the request is sent immediately and the function
314+ // returns; if the channel is full a short-lived goroutine is spawned that blocks only
315+ // until a slot becomes available (or the context is cancelled), but never waits for
316+ // the DNS result itself. This keeps the number of long-lived waiting goroutines bounded
317+ // by the worker pool size rather than the number of in-flight records.
318+ func (c * Correlator ) submitDNSCallback (ipStr string , callback func (dnsEnrichmentResult )) {
321319 req := dnsEnrichmentRequest {
322- ip : ipStr ,
323- resultChan : resultChan ,
320+ ip : ipStr ,
321+ callback : callback ,
324322 }
325323
326- // Send request to worker pool (with timeout).
327- // Use NewTimer instead of time.After so the timer can be stopped
328- // immediately when the send succeeds, avoiding timer accumulation.
329- queueTimer := time .NewTimer (c .dnsTimeout )
330324 select {
331325 case c .dnsRequestChan <- req :
332- queueTimer .Stop ()
333- case <- queueTimer .C :
334- c .logger .Warnf ("DNS enrichment queue timeout for %s" , ipStr )
335- return ""
336- case <- c .ctx .Done ():
337- queueTimer .Stop ()
326+ // Fast path: submitted immediately
327+ default :
328+ // Queue is full; spawn a minimal goroutine that only blocks until a slot
329+ // opens up, not until the DNS result is ready.
330+ go func () {
331+ select {
332+ case c .dnsRequestChan <- req :
333+ case <- c .ctx .Done ():
334+ callback (dnsEnrichmentResult {ip : ipStr })
335+ }
336+ }()
337+ }
338+ }
339+
340+ // enrichWithDNSBlocking sends a DNS lookup request to the worker pool and blocks until the result is ready.
341+ // It is implemented on top of submitDNSCallback and is used in tests and as a convenience for
342+ // callers that can tolerate blocking (e.g. tests in the same package).
343+ func (c * Correlator ) enrichWithDNSBlocking (ipStr string ) string {
344+ if ! c .enableDNSEnrichment || ipStr == "" {
338345 return ""
339346 }
340347
348+ resultChan := make (chan dnsEnrichmentResult , 1 )
349+ c .submitDNSCallback (ipStr , func (result dnsEnrichmentResult ) {
350+ resultChan <- result
351+ })
352+
341353 // Wait for result. Use 2x the DNS timeout to account for both
342354 // queuing delay and the actual DNS lookup performed by the worker.
343- // Use NewTimer so it can be stopped as soon as the result arrives.
344355 resultTimer := time .NewTimer (c .dnsTimeout * 2 )
345356 select {
346357 case result := <- resultChan :
@@ -355,35 +366,45 @@ func (c *Correlator) enrichWithDNSBlocking(ipStr string) string {
355366 }
356367}
357368
358- // EnrichRecordAsync enriches a record with DNS and calls the callback
359- // This is a helper for callers who want to handle async enrichment
360- // Spawns a goroutine, returns immediately (non-blocking)
369+ // EnrichRecordAsync enriches a record with DNS and calls the callback when complete.
370+ // It returns immediately without spawning a goroutine that waits for DNS results;
371+ // instead, callbacks are chained through the DNS worker goroutines. This keeps
372+ // concurrency bounded by the DNS worker pool size regardless of the number of
373+ // in-flight records.
361374func (c * Correlator ) EnrichRecordAsync (record * CollectorRecord , callback func (* CollectorRecord )) {
362375 if ! record .needsDNSEnrichment && ! record .needsServerDNS {
363376 callback (record )
364377 return
365378 }
366379
367- go func () {
368- if record .needsDNSEnrichment {
369- hostname := c .enrichWithDNSBlocking (record .enrichmentIP )
370- if hostname != "" {
371- parts := strings .Split (hostname , "." )
372- if len (parts ) >= 2 {
373- record .UserDomain = strings .Join (parts [len (parts )- 2 :], "." )
380+ // Server DNS step: submitted after user DNS completes (or directly if no user DNS needed).
381+ doServerDNS := func () {
382+ if record .needsServerDNS {
383+ c .submitDNSCallback (record .serverEnrichmentIP , func (result dnsEnrichmentResult ) {
384+ if result .hostname != "" {
385+ record .ServerHostname = result .hostname
374386 }
375- }
387+ callback (record )
388+ })
389+ } else {
390+ callback (record )
376391 }
392+ }
377393
378- if record .needsServerDNS {
379- hostname := c .enrichWithDNSBlocking (record .serverEnrichmentIP )
380- if hostname != "" {
381- record .ServerHostname = hostname
394+ // User DNS step: submitted first; on completion, triggers the server DNS step.
395+ if record .needsDNSEnrichment {
396+ c .submitDNSCallback (record .enrichmentIP , func (result dnsEnrichmentResult ) {
397+ if result .hostname != "" {
398+ parts := strings .Split (result .hostname , "." )
399+ if len (parts ) >= 2 {
400+ record .UserDomain = strings .Join (parts [len (parts )- 2 :], "." )
401+ }
382402 }
383- }
384-
385- callback (record )
386- }()
403+ doServerDNS ()
404+ })
405+ } else {
406+ doServerDNS ()
407+ }
387408}
388409
389410// NeedsDNSEnrichment returns true if the record needs async DNS enrichment
0 commit comments