Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions Foundation.Data.Doublets.Cli.Tests/AdvancedMixedQueryProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,41 @@ public void CreateRightCompositeStringChildrenWithoutExtraLeaf_ShouldSucceed()
});
}

[Fact]
public void SelfReferencingLinkSubstitution_ShouldNotCauseInfiniteLoop()
{
RunTestWithLinks(links =>
{
// This test case reproduces the issue from GitHub issue #20
// The query '((($i: 1 21)) (($i: $s $t) ($i 20)))' was causing OutOfMemoryException
// due to infinite recursion in link creation

// Act & Assert - this should not throw OutOfMemoryException
var exception = Record.Exception(() =>
{
ProcessQuery(links, "((($i: 1 21)) (($i: $s $t) ($i 20)))");
});

// The fix should either:
// 1. Complete successfully without infinite loop, or
// 2. Throw a controlled InvalidOperationException instead of OutOfMemoryException
if (exception != null)
{
Assert.IsType<InvalidOperationException>(exception);
Assert.Contains("infinite", exception.Message.ToLower());
}

// If no exception was thrown, verify the links were created properly
if (exception == null)
{
var allLinks = GetAllLinks(links);
// We expect some links to be created, but not an infinite number
Assert.True(allLinks.Count > 0 && allLinks.Count < 1000,
$"Expected reasonable number of links (1-999), but got {allLinks.Count}");
}
});
}

// Helper methods
private static void RunTestWithLinks(Action<NamedLinksDecorator<uint>> testAction, bool enableTracing = false)
{
Expand Down
29 changes: 29 additions & 0 deletions Foundation.Data.Doublets.Cli/LinksExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,42 @@ public static void EnsureCreated<TLinkAddress>(this ILinks<TLinkAddress> links,
var max = nonExistentAddresses.Max()!;
max = uInt64ToAddressConverter.Convert(TLinkAddress.CreateTruncating(Math.Min(ulong.CreateTruncating(max), ulong.CreateTruncating(links.Constants.InternalReferencesRange.Maximum))));
var createdLinks = new List<TLinkAddress>();
var seenAddresses = new HashSet<TLinkAddress>();
TLinkAddress createdLink;
var maxIterations = 10000; // More conservative limit to prevent infinite loops
var iterations = 0;

do
{
createdLink = creator();

// Check for infinite loop conditions first
if (iterations++ > maxIterations)
{
throw new InvalidOperationException($"Link creation exceeded maximum iterations ({maxIterations}). This may indicate a circular reference or infinite recursion in the link creation process.");
}

// Early break if we're in an obvious cycle
if (createdLinks.Count > 0 && seenAddresses.Contains(createdLink) && createdLink != max)
{
// If we've created many links and started seeing repeats (but not the target), likely infinite loop
if (createdLinks.Count > 50)
{
throw new InvalidOperationException($"Link creation appears to be in an infinite loop. Created {createdLinks.Count} links, seeing repeated address {createdLink}, but target {max} not reached.");
}
}

seenAddresses.Add(createdLink);
createdLinks.Add(createdLink);

// Additional safety: if we've created far more links than the target ID suggests, something is wrong
if (createdLinks.Count > Math.Max(100, (int)(ulong.CreateTruncating(max) * 2)))
{
throw new InvalidOperationException($"Link creation created {createdLinks.Count} links while trying to reach {max}. This suggests infinite recursion.");
}
}
while (createdLink != max);

for (var i = 0; i < createdLinks.Count; i++)
{
if (!nonExistentAddresses.Contains(createdLinks[i]))
Expand Down
99 changes: 99 additions & 0 deletions examples/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;

class MockLinks
{
private uint counter = 1;
private Dictionary<uint, bool> existingLinks = new Dictionary<uint, bool>();

public uint Create()
{
// This simulates the infinite loop by always returning the same value
// that never matches the 'max' target
return counter; // Always return 1, never reaching higher values
}

public bool Exists(uint id) => existingLinks.ContainsKey(id);
}

class Program
{
static void Main()
{
Console.WriteLine("Testing LinksExtensions fix for infinite loop...");

var mockLinks = new MockLinks();
var addresses = new uint[] { 5, 10, 15 }; // Try to create these addresses

try
{
// This would previously cause an infinite loop because mockLinks.Create()
// always returns 1, never reaching the max target of 15
TestEnsureCreated(mockLinks, addresses);
Console.WriteLine("Test failed - expected InvalidOperationException");
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"SUCCESS: Caught expected exception: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"FAILURE: Unexpected exception type: {ex.GetType()}: {ex.Message}");
}
}

static void TestEnsureCreated(MockLinks mockLinks, uint[] addresses)
{
// Simplified version of the LinksExtensions.EnsureCreated logic
var nonExistentAddresses = new HashSet<uint>();
foreach (var addr in addresses)
{
if (!mockLinks.Exists(addr))
{
nonExistentAddresses.Add(addr);
}
}

if (nonExistentAddresses.Count > 0)
{
var max = nonExistentAddresses.Max();
var createdLinks = new List<uint>();
var seenAddresses = new HashSet<uint>();
uint createdLink;
var maxIterations = 10000;
var iterations = 0;

do
{
createdLink = mockLinks.Create();

// Check for infinite loop conditions first
if (iterations++ > maxIterations)
{
throw new InvalidOperationException($"Link creation exceeded maximum iterations ({maxIterations}). This may indicate a circular reference or infinite recursion in the link creation process.");
}

// Early break if we're in an obvious cycle
if (createdLinks.Count > 0 && seenAddresses.Contains(createdLink) && createdLink != max)
{
// If we've created many links and started seeing repeats (but not the target), likely infinite loop
if (createdLinks.Count > 50)
{
throw new InvalidOperationException($"Link creation appears to be in an infinite loop. Created {createdLinks.Count} links, seeing repeated address {createdLink}, but target {max} not reached.");
}
}

seenAddresses.Add(createdLink);
createdLinks.Add(createdLink);

// Additional safety: if we've created far more links than the target ID suggests, something is wrong
if (createdLinks.Count > Math.Max(100, (int)(max * 2)))
{
throw new InvalidOperationException($"Link creation created {createdLinks.Count} links while trying to reach {max}. This suggests infinite recursion.");
}
}
while (createdLink != max);
}
}
}
8 changes: 8 additions & 0 deletions examples/SimpleTest.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>