Skip to content

Revert "feat(assembler): on-demand assembler serve with live reload and improved frontend DX"#3437

Merged
Mpdreamz merged 1 commit into
mainfrom
revert-3179-feature/assembler-serve
Jun 1, 2026
Merged

Revert "feat(assembler): on-demand assembler serve with live reload and improved frontend DX"#3437
Mpdreamz merged 1 commit into
mainfrom
revert-3179-feature/assembler-serve

Conversation

@Mpdreamz
Copy link
Copy Markdown
Member

@Mpdreamz Mpdreamz commented Jun 1, 2026

Reverts #3179

Was too out of date when merging caused failures on main.

@Mpdreamz Mpdreamz requested review from a team as code owners June 1, 2026 20:02
@Mpdreamz Mpdreamz requested a review from reakaleek June 1, 2026 20:02
@Mpdreamz Mpdreamz temporarily deployed to integration-tests June 1, 2026 20:03 — with GitHub Actions Inactive
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR eliminates the dynamic live-reload assembler serving pipeline by removing AssemblerServeWebHost and AssemblerReloadService, switching the Serve command from on-demand rendering to static file hosting. Corresponding changes simplify resource ownership patterns (removing IDisposable from Aws3LinkIndexReader, the ownsReader parameter from CrossLinkFetcher, and mid-flight invalidation from DocumentationSet). The build system is updated to replace Watch_Full with Watch, and CI/Aspire configuration is updated to use the new Serve command. AssemblerBuilder's public API is consolidated by removing CreateGenerator and BuildOneAsync methods.

Sequence Diagram(s)

Not applicable. This PR is primarily a refactoring that removes live-reload functionality and simplifies disposal patterns; it does not introduce new multi-component interactions or significantly alter control flow beyond removing complexity.

Possibly related PRs

  • elastic/docs-builder#3179: This PR removes the live-reload assembler serving pipeline that was introduced in #3179, and refactors the disposal/ownership patterns that were modified in the cross-link fetcher and documentation set classes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: reverting a previous feature commit that caused merge failures on main.
Description check ✅ Passed The description clearly relates to the changeset by explaining the revert rationale: the original merged feature was outdated and caused failures.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch revert-3179-feature/assembler-serve

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetcher.cs`:
- Around line 61-62: CrossLinkFetcher is disposing the injected ILoggerFactory
(logFactory) in its Dispose implementation which it does not own; remove the
call that disposes logFactory inside CrossLinkFetcher.Dispose (or remove the
IDisposable implementation if the class has no other owned disposable state).
Ensure Dispose still disposes only owned resources (e.g.,
ScopedFileSystem/fileSystem) and suppress finalization if appropriate, but do
not call logFactory.Dispose() or otherwise assume ownership of ILoggerFactory.

In
`@src/Elastic.Documentation.Links/CrossLinks/DocSetConfigurationCrossLinkFetcher.cs`:
- Line 19: The constructor for DocSetConfigurationCrossLinkFetcher currently
calls Aws3LinkIndexReader.CreateAnonymous() inline in multiple places, producing
two distinct readers where only one should be used; fix by creating a single
local variable (e.g., var reader = linkIndexProvider ??
Aws3LinkIndexReader.CreateAnonymous()) and pass that same reader instance
wherever the code currently calls Aws3LinkIndexReader.CreateAnonymous() (notably
the base CrossLinkFetcher(...) call and the later usage around lines 34–35) so
the same reader and its cache are reused by DocSetConfigurationCrossLinkFetcher.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 96c712c1-92a8-4193-b2e7-f3b130198018

📥 Commits

Reviewing files that changed from the base of the PR and between 5cab154 and 1540673.

📒 Files selected for processing (15)
  • .github/workflows/ci.yml
  • aspire/AppHost.cs
  • build/CommandLine.fs
  • build/Targets.fs
  • docs/cli-schema.json
  • src/Elastic.Documentation.LinkIndex/LinkIndexReader.cs
  • src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetcher.cs
  • src/Elastic.Documentation.Links/CrossLinks/DocSetConfigurationCrossLinkFetcher.cs
  • src/Elastic.Markdown/IO/DocumentationSet.cs
  • src/services/Elastic.Documentation.Assembler/AssembleSources.cs
  • src/services/Elastic.Documentation.Assembler/Building/AssemblerBuilder.cs
  • src/tooling/docs-builder/Commands/Assembler/AssemblerCommands.cs
  • src/tooling/docs-builder/Http/AssemblerReloadService.cs
  • src/tooling/docs-builder/Http/AssemblerServeWebHost.cs
  • src/tooling/docs-builder/Http/ReloadableGeneratorState.cs
💤 Files with no reviewable changes (5)
  • src/tooling/docs-builder/Http/AssemblerServeWebHost.cs
  • src/tooling/docs-builder/Http/AssemblerReloadService.cs
  • src/tooling/docs-builder/Http/ReloadableGeneratorState.cs
  • build/Targets.fs
  • build/CommandLine.fs

Comment on lines +61 to 62
public abstract class CrossLinkFetcher(ILoggerFactory logFactory, ILinkIndexReader linkIndexProvider, ScopedFileSystem? fileSystem = null) : IDisposable
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and print the relevant parts of CrossLinkFetcher.cs (class header + Dispose region)
FILE="src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetcher.cs"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE"
  exit 1
fi

echo "=== Class declaration (around 55-75) ==="
nl -ba "$FILE" | sed -n '55,80p'

echo
echo "=== Dispose() implementation (around 180-240) ==="
nl -ba "$FILE" | sed -n '180,240p'

echo
echo "=== Search for Dispose calls / IDisposable implementation in this file ==="
rg -n "IDisposable|Dispose\(" "$FILE"

Repository: elastic/docs-builder

Length of output: 167


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetcher.cs"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE"
  exit 1
fi

echo "=== Class declaration (lines 55-80) ==="
awk 'NR>=55 && NR<=80 {printf "%d\t%s\n", NR, $0}' "$FILE"

echo
echo "=== Dispose() implementation (lines 180-240) ==="
awk 'NR>=180 && NR<=240 {printf "%d\t%s\n", NR, $0}' "$FILE"

echo
echo "=== Search for IDisposable/Dispose in this file ==="
rg -n "IDisposable|Dispose\(" "$FILE"

Repository: elastic/docs-builder

Length of output: 2558


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find derived types of CrossLinkFetcher
rg -n ":\s*CrossLinkFetcher" -S src

# Find any calls to base.Dispose() in derived classes
rg -n "base\.Dispose\(\)" -S src

# Find Dispose overrides/implementations that might interact with CrossLinkFetcher
rg -n "override\s+void\s+Dispose|void\s+Dispose\(" -S src | rg -n "CrossLinkFetcher|CrossLink" || true

Repository: elastic/docs-builder

Length of output: 605


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for Dispose patterns in all classes that derive from CrossLinkFetcher
rg -n "class\s+.+:\s*CrossLinkFetcher|:\s*CrossLinkFetcher" src

echo "---- Derived Dispose overrides (override/Dispose methods) ----"
rg -n "override\s+void\s+Dispose\s*\(|\bvoid\s+Dispose\s*\(" src/services/Elastic.Documentation.Assembler/Links/AssemblerCrossLinkFetcher.cs src/Elastic.Documentation.Links/InboundLinks/LinkIndexCrossLinkFetcher.cs src/Elastic.Documentation.Links/CrossLinks/DocSetConfigurationCrossLinkFetcher.cs

echo "---- base.Dispose() calls in those derived files ----"
rg -n "base\.Dispose\s*\(" src/services/Elastic.Documentation.Assembler/Links/AssemblerCrossLinkFetcher.cs src/Elastic.Documentation.Links/InboundLinks/LinkIndexCrossLinkFetcher.cs src/Elastic.Documentation.Links/CrossLinks/DocSetConfigurationCrossLinkFetcher.cs

echo "---- Check if derived types explicitly implement IDisposable beyond the base ----"
rg -n "IDisposable" src/services/Elastic.Documentation.Assembler/Links/AssemblerCrossLinkFetcher.cs src/Elastic.Documentation.Links/InboundLinks/LinkIndexCrossLinkFetcher.cs src/Elastic.Documentation.Links/CrossLinks/DocSetConfigurationCrossLinkFetcher.cs

Repository: elastic/docs-builder

Length of output: 668


🏁 Script executed:

#!/bin/bash
set -euo pipefail

files=(
  "src/services/Elastic.Documentation.Assembler/Links/AssemblerCrossLinkFetcher.cs"
  "src/Elastic.Documentation.Links/InboundLinks/LinkIndexCrossLinkFetcher.cs"
  "src/Elastic.Documentation.Links/CrossLinks/DocSetConfigurationCrossLinkFetcher.cs"
)

echo "---- Other derived declarations across src (': CrossLinkFetcher') ----"
rg -n ":\s*CrossLinkFetcher" src || true

echo "---- Dispose( occurrences in known derived files ----"
for f in "${files[@]}"; do
  if [ -f "$f" ]; then
    echo "== $f =="
    rg -n "Dispose\s*\(" "$f" || true
  else
    echo "Missing $f"
  fi
done

Repository: elastic/docs-builder

Length of output: 984


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Find all registrations/usages mentioning CrossLinkFetcher or its derived types
rg -n "CrossLinkFetcher|AssemblerCrossLinkFetcher|LinksIndexCrossLinkFetcher|DocSetConfigurationCrossLinkFetcher" src -S -g'*.cs'

echo
echo "---- check ScopedFileSystem type ----"
# Find the declaration file for ScopedFileSystem
fd -a "ScopedFileSystem" src || true
rg -n "class\s+ScopedFileSystem|record\s+ScopedFileSystem|struct\s+ScopedFileSystem|interface\s+ScopedFileSystem" src -S -g'*.cs' || true

echo
echo "---- check whether ScopedFileSystem implements IDisposable ----"
rg -n "ScopedFileSystem.*IDisposable|IDisposable.*ScopedFileSystem" src -S -g'*.cs' || true

echo
echo "---- inspect derived fetcher files for constructors/fields ----"
for f in \
  "src/services/Elastic.Documentation.Assembler/Links/AssemblerCrossLinkFetcher.cs" \
  "src/Elastic.Documentation.Links/InboundLinks/LinkIndexCrossLinkFetcher.cs" \
  "src/Elastic.Documentation.Links/CrossLinks/DocSetConfigurationCrossLinkFetcher.cs"
do
  if [ -f "$f" ]; then
    echo "== $f (top 140 lines) =="
    head -n 140 "$f" | nl -ba | sed -n '1,140p'
  fi
done

Repository: elastic/docs-builder

Length of output: 4181


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Find definition of ScopedFileSystem ==="
rg -n "(\bclass\b|\brecord\b|\bstruct\b|\binterface\b)\s+ScopedFileSystem\b" src -S -g'*.cs' || true

echo
echo "=== Check whether ScopedFileSystem is IDisposable ==="
rg -n "ScopedFileSystem.*IDisposable|IDisposable.*ScopedFileSystem" src -S -g'*.cs' || true

echo
echo "=== Show usages of CrossLinkFetcher-derived types to see how they are disposed ==="
rg -n -C 3 "new\s+(AssemblerCrossLinkFetcher|LinksIndexCrossLinkFetcher|DocSetConfigurationCrossLinkFetcher)\b" src -S -g'*.cs'

echo
echo "=== Search for explicit Dispose() calls on those fetchers ==="
rg -n "(\.Dispose\(\)|using\s+var)\s*(fetcher|crossLinkFetcher|_crossLinkFetcher)\b" src -S -g'*.cs' || true

Repository: elastic/docs-builder

Length of output: 9552


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Files likely involved in lifecycle/disposal
targets=(
  "src/services/Elastic.Documentation.Assembler/AssembleSources.cs"
  "src/services/Elastic.Documentation.Assembler/ContentSources/RepositoryPublishValidationService.cs"
  "src/services/Elastic.Documentation.Assembler/Navigation/NavigationPrefixChecker.cs"
  "src/Elastic.Documentation.Links/InboundLinks/LinkIndexLinkChecker.cs"
  "src/services/Elastic.Documentation.Isolated/IsolatedBuildService.cs"
  "src/tooling/docs-builder/Http/ReloadableGeneratorState.cs"
)

for f in "${targets[@]}"; do
  echo "==== $f (around instantiation) ===="
  rg -n "new\s+(AssemblerCrossLinkFetcher|LinksIndexCrossLinkFetcher|DocSetConfigurationCrossLinkFetcher)\b" "$f" -S || true
  # show a window around each match
  rg -n -S "new\s+(AssemblerCrossLinkFetcher|LinksIndexCrossLinkFetcher|DocSetConfigurationCrossLinkFetcher)\b" "$f" | cut -d: -f1 | head -n 5 | while read -r line; do
    start=$((line-15)); end=$((line+15))
    sed -n "${start},${end}p" "$f" | sed "s/^/[$f:$start] /" >/dev/null
  done
  # simpler: just print a safe chunk near the first match line if exists
  first=$(rg -n -S "new\s+(AssemblerCrossLinkFetcher|LinksIndexCrossLinkFetcher|DocSetConfigurationCrossLinkFetcher)\b" "$f" | head -n1 | cut -d: -f1 || true)
  if [ -n "${first:-}" ]; then
    start=$((first-30)); end=$((first+60))
    [ $start -lt 1 ] && start=1
    echo "---- context $start-$end ----"
    sed -n "${start},${end}p" "$f"
  fi
  echo
done

echo "==== ReloadableGeneratorState dispose implementation ===="
rg -n "Dispose\s*\(" src/tooling/docs-builder/Http/ReloadableGeneratorState.cs -S || true
echo
if [ -f "src/tooling/docs-builder/Http/ReloadableGeneratorState.cs" ]; then
  sed -n '1,220p' src/tooling/docs-builder/Http/ReloadableGeneratorState.cs
fi

Repository: elastic/docs-builder

Length of output: 367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

show_chunk () {
  local file="$1" start="$2" end="$3"
  echo "---- $file [$start-$end] ----"
  awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {printf "%d\t%s\n", NR, $0}' "$file"
  echo
}

targets=(
  "src/services/Elastic.Documentation.Assembler/AssembleSources.cs"
  "src/services/Elastic.Documentation.Assembler/ContentSources/RepositoryPublishValidationService.cs"
  "src/services/Elastic.Documentation.Assembler/Navigation/NavigationPrefixChecker.cs"
  "src/Elastic.Documentation.Links/InboundLinks/LinkIndexLinkChecker.cs"
  "src/services/Elastic.Documentation.Isolated/IsolatedBuildService.cs"
  "src/tooling/docs-builder/Http/ReloadableGeneratorState.cs"
  "src/Elastic.Codex/Building/CodexBuildService.cs"
)

patterns=(
  "new[[:space:]]+AssemblerCrossLinkFetcher"
  "new[[:space:]]+LinksIndexCrossLinkFetcher"
  "new[[:space:]]+DocSetConfigurationCrossLinkFetcher"
)

# Print first instantiation context for each known file
for f in "${targets[@]}"; do
  [ -f "$f" ] || continue
  for p in "${patterns[@]}"; do
    line=$(rg -n -S "$p" "$f" | head -n1 | cut -d: -f1 || true)
    if [ -n "${line:-}" ]; then
      start=$((line-25)); end=$((line+60))
      [ $start -lt 1 ] && start=1
      show_chunk "$f" "$start" "$end"
    fi
  done
done

echo "==== ReloadableGeneratorState.cs Dispose implementation ===="
[ -f "src/tooling/docs-builder/Http/ReloadableGeneratorState.cs" ] && rg -n "Dispose\s*\(" src/tooling/docs-builder/Http/ReloadableGeneratorState.cs -S || true
[ -f "src/tooling/docs-builder/Http/ReloadableGeneratorState.cs" ] && show_chunk "src/tooling/docs-builder/Http/ReloadableGeneratorState.cs" 1 220

echo "==== Any Dispose calls on CrossLinkFetcher instances ===="
rg -n -S "\.Dispose\s*\(\s*\)\s*;|using\s+var\s+\w+\s*=\s*new\s+(AssemblerCrossLinkFetcher|LinksIndexCrossLinkFetcher|DocSetConfigurationCrossLinkFetcher)" src -g'*.cs' || true

echo "==== Any disposal of ILoggerFactory instances (logFactory.Dispose) ===="
rg -n -S "logFactory\.Dispose\(|_logFactory\.Dispose\(|ILoggerFactory.*Dispose\(" src -g'*.cs' || true

Repository: elastic/docs-builder

Length of output: 39105


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetcher.cs"
echo "=== CrossLinkFetcher.cs (around class header/fields/constructor params) ==="
awk 'NR>=55 && NR<=120 {printf "%d\t%s\n", NR, $0}' "$FILE"
echo
echo "=== CrossLinkFetcher.cs (around end / Dispose / owned resources) ==="
awk 'NR>=120 && NR<=230 {printf "%d\t%s\n", NR, $0}' "$FILE"

echo
echo "=== Search for disposal of specific fetcher variables ==="
rg -n -S "(crossLinkFetcher|_crossLinkFetcher)\.Dispose\(" src -g'*.cs' || true
rg -n -S "using\s+var\s+\w+\s*=\s*new\s+(AssemblerCrossLinkFetcher|LinksIndexCrossLinkFetcher|DocSetConfigurationCrossLinkFetcher)\b" src -g'*.cs' || true
rg -n -S "\bDispose\(\)\s*;\s*.*CrossLinkFetcher|CrossLinkFetcher.*Dispose\(" src -g'*.cs' || true

echo
echo "=== Search for any cast/dispose of CrossLinkFetcher instances ==="
rg -n -S "(\(IDisposable\).*)?CrossLinkFetcher.*\.Dispose\(" src -g'*.cs' || true
rg -n -S "\b(IDisposable)\s*\)\s*.*CrossLinkFetcher" src -g'*.cs' || true

echo
echo "=== CrossLinkFetcher constructor ownership: look for stored members ==="
rg -n -S "_fileSystem|linkIndexProvider|_linkIndexProvider|_cachedLinkReferences|_linkIndex" "$FILE"

Repository: elastic/docs-builder

Length of output: 8200


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Any using-var or explicit Dispose for CrossLinkFetcher-derived types ==="
rg -n -S "using\s+var\s+\w+\s*=\s*new\s+(AssemblerCrossLinkFetcher|LinksIndexCrossLinkFetcher|DocSetConfigurationCrossLinkFetcher)\b" src -g'*.cs' || true
rg -n -S "\b(new\s+(AssemblerCrossLinkFetcher|LinksIndexCrossLinkFetcher|DocSetConfigurationCrossLinkFetcher)\b).*\bDispose\s*\(" src -g'*.cs' || true
rg -n -S "(CrossLinkFetcher|AssemblerCrossLinkFetcher|LinksIndexCrossLinkFetcher|DocSetConfigurationCrossLinkFetcher)\b" src -g'*.cs' | head -n 200
echo
echo "=== Any Dispose calls where the variable type is IDisposable and created from CrossLinkFetcher ==="
rg -n -S "(IDisposable)\s+\w+\s*=\s*new\s+(AssemblerCrossLinkFetcher|LinksIndexCrossLinkFetcher|DocSetConfigurationCrossLinkFetcher)\b|new\s+(AssemblerCrossLinkFetcher|LinksIndexCrossLinkFetcher|DocSetConfigurationCrossLinkFetcher)\b\s*\)|\.Dispose\s*\(\s*\)" src -g'*.cs' || true

echo
echo "=== Any casts to IDisposable around CrossLinkFetcher instances ==="
rg -n -S "\(IDisposable\)\s*\w+.*CrossLinkFetcher|CrossLinkFetcher.*\(IDisposable\)" src -g'*.cs' || true

Repository: elastic/docs-builder

Length of output: 7006


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Look for explicit LoggerFactory creation ==="
rg -n -S "new\s+LoggerFactory|LoggerFactory\.Create" src -g'*.cs' || true

echo
echo "=== Look for explicit ILoggerFactory.Dispose calls outside CrossLinkFetcher ==="
rg -n -S "(\.Dispose\(\)\s*;|\bDispose\(\)\b)\s*$" src -g'*.cs' | rg -n "ILoggerFactory|logFactory" || true

echo
echo "=== Look for any using/Dispose of CrossLinkFetcher instances (not just fetcher type) ==="
rg -n -S "using\s+var\s+\w+\s*=\s*new\s+.*CrossLinkFetcher|\.Dispose\(\)\s*;.*CrossLinkFetcher|_crossLinkFetcher\s*\.Dispose" src -g'*.cs' || true

Repository: elastic/docs-builder

Length of output: 378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Call sites for AssembleSources.AssembleAsync( ... , ILoggerFactory logFactory, ...) ==="
rg -n "AssembleSources\.AssembleAsync\s*\(" src -g'*.cs' || true

echo
echo "=== Call sites for ReloadableGeneratorState constructor ==="
rg -n "new\s+ReloadableGeneratorState\s*\(" src -g'*.cs' || true

echo
echo "=== DI registrations for ReloadableGeneratorState (if any) ==="
rg -n "AddSingleton<\s*ReloadableGeneratorState|AddScoped<\s*ReloadableGeneratorState|AddTransient<\s*ReloadableGeneratorState" src -g'*.cs' || true

echo
echo "=== Show ReloadableGeneratorState.Dispose implementation (dispose of _crossLinkFetcher/logFactory?) ==="
rg -n "void\s+Dispose\s*\(" src/tooling/docs-builder/Http/ReloadableGeneratorState.cs -g'*.cs' || true
sed -n '240,310p' src/tooling/docs-builder/Http/ReloadableGeneratorState.cs || true

Repository: elastic/docs-builder

Length of output: 1863


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Any explicit .Dispose() on CrossLinkFetcher types/vars ==="
rg -n -S "\.Dispose\(\)\s*;|using\s+var\s+\w+\s*=\s*new\s+\w*CrossLinkFetcher" src -g'*.cs' || true

echo
echo "=== Any Dispose() call where the receiver is a variable named like *fetcher* that holds a CrossLinkFetcher ==="
rg -n -S "Dispose\(\)\s*;|using\s+var\s+\w+\s*=\s*new\s+.*CrossLinkFetcher" src/services src/tooling -g'*.cs' || true

echo
echo "=== Inspect DocumentationWebHost.cs for ILoggerFactory source ==="
FILE="src/tooling/docs-builder/Http/DocumentationWebHost.cs"
if [ -f "$FILE" ]; then
  sed -n '1,220p' "$FILE"
else
  echo "Missing $FILE"
fi

Repository: elastic/docs-builder

Length of output: 12591


Do not dispose the injected ILoggerFactory in CrossLinkFetcher
CrossLinkFetcher implements IDisposable and calls logFactory.Dispose() in Dispose() (around lines 207-210), but ILoggerFactory is injected and not owned by this class—disposing it can break logging for other components sharing the same factory. Remove the base Dispose()/IDisposable (or at least stop disposing logFactory).

🔧 Minimal fix
-public abstract class CrossLinkFetcher(ILoggerFactory logFactory, ILinkIndexReader linkIndexProvider, ScopedFileSystem? fileSystem = null) : IDisposable
+public abstract class CrossLinkFetcher(ILoggerFactory logFactory, ILinkIndexReader linkIndexProvider, ScopedFileSystem? fileSystem = null)
 {
@@
-	public void Dispose()
-	{
-		logFactory.Dispose();
-		GC.SuppressFinalize(this);
-	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetcher.cs` around lines
61 - 62, CrossLinkFetcher is disposing the injected ILoggerFactory (logFactory)
in its Dispose implementation which it does not own; remove the call that
disposes logFactory inside CrossLinkFetcher.Dispose (or remove the IDisposable
implementation if the class has no other owned disposable state). Ensure Dispose
still disposes only owned resources (e.g., ScopedFileSystem/fileSystem) and
suppress finalization if appropriate, but do not call logFactory.Dispose() or
otherwise assume ownership of ILoggerFactory.

ILinkIndexReader? linkIndexProvider = null,
ILinkIndexReader? codexLinkIndexReader = null)
: CrossLinkFetcher(logFactory, linkIndexProvider ?? Aws3LinkIndexReader.CreateAnonymous(), ownsReader: linkIndexProvider is null)
: CrossLinkFetcher(logFactory, linkIndexProvider ?? Aws3LinkIndexReader.CreateAnonymous())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reuse a single public link-index reader.

Line 19 and Line 34 each call Aws3LinkIndexReader.CreateAnonymous() when no reader is supplied. That creates two separate readers; only the second one is used here, so the base instance is dead weight and its cache never helps.

🔧 One way to keep a single reader
 public class DocSetConfigurationCrossLinkFetcher(
 	ILoggerFactory logFactory,
 	ConfigurationFile configuration,
 	ILinkIndexReader? linkIndexProvider = null,
 	ILinkIndexReader? codexLinkIndexReader = null)
-	: CrossLinkFetcher(logFactory, linkIndexProvider ?? Aws3LinkIndexReader.CreateAnonymous())
+	: CrossLinkFetcher(logFactory, linkIndexProvider ??= Aws3LinkIndexReader.CreateAnonymous())
 {
+	private readonly ILinkIndexReader _publicReader = linkIndexProvider;
 	private readonly ILogger _logger = logFactory.CreateLogger(nameof(DocSetConfigurationCrossLinkFetcher));
 	private readonly ILinkIndexReader? _codexReader = codexLinkIndexReader;
@@
-		var publicReader = linkIndexProvider ?? Aws3LinkIndexReader.CreateAnonymous();
+		var publicReader = _publicReader;

Also applies to: 34-35

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/Elastic.Documentation.Links/CrossLinks/DocSetConfigurationCrossLinkFetcher.cs`
at line 19, The constructor for DocSetConfigurationCrossLinkFetcher currently
calls Aws3LinkIndexReader.CreateAnonymous() inline in multiple places, producing
two distinct readers where only one should be used; fix by creating a single
local variable (e.g., var reader = linkIndexProvider ??
Aws3LinkIndexReader.CreateAnonymous()) and pass that same reader instance
wherever the code currently calls Aws3LinkIndexReader.CreateAnonymous() (notably
the base CrossLinkFetcher(...) call and the later usage around lines 34–35) so
the same reader and its cache are reused by DocSetConfigurationCrossLinkFetcher.

@Mpdreamz Mpdreamz added the chore label Jun 1, 2026
@Mpdreamz Mpdreamz merged commit 1b9c2ac into main Jun 1, 2026
25 of 26 checks passed
@Mpdreamz Mpdreamz deleted the revert-3179-feature/assembler-serve branch June 1, 2026 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant