Browse Source

detect cycles in yaml sequence definitions (#2060)

pull/2061/head
Jason Dove 1 day ago committed by GitHub
parent
commit
c4c164df6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 26
      ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs
  3. 7
      ErsatzTV.Core/Search/AdjGraph.cs

2
CHANGELOG.md

@ -36,7 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -36,7 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- This makes it obvious when hardware acceleration will not work as configured
- Add button in schedule editor to clone schedule item
- Allow YAML playout sequence definitions to reference other sequences
- Playout builder will behave in unexpected ways if nesting is too deep
- Cycles will be detected and logged, and sequences with cycles will prevent the playout from building
- Add `repeat` property to YAML sequence instruction
- This tells the playout builder how many times this sequence should repeat
- Omitting this value is the same as setting it to `1`

26
ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs

@ -6,6 +6,7 @@ using ErsatzTV.Core.Interfaces.Repositories; @@ -6,6 +6,7 @@ using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Scheduling;
using ErsatzTV.Core.Scheduling.YamlScheduling.Handlers;
using ErsatzTV.Core.Scheduling.YamlScheduling.Models;
using ErsatzTV.Core.Search;
using Microsoft.Extensions.Logging;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
@ -127,10 +128,16 @@ public class YamlPlayoutBuilder( @@ -127,10 +128,16 @@ public class YamlPlayoutBuilder(
}
}
int flattenCount = 0;
if (DetectCycle(context.Definition))
{
logger.LogError("YAML sequence contains a cycle; unable to build playout");
return playout;
}
var flattenCount = 0;
while (context.Definition.Playout.Any(x => x is YamlPlayoutSequenceInstruction))
{
if (flattenCount > 10)
if (flattenCount > 100)
{
logger.LogError(
"YAML playout definition contains sequence nesting that is too deep; this introduces undefined behavior");
@ -197,6 +204,21 @@ public class YamlPlayoutBuilder( @@ -197,6 +204,21 @@ public class YamlPlayoutBuilder(
return playout;
}
private static bool DetectCycle(YamlPlayoutDefinition definition)
{
var graph = new AdjGraph();
foreach (YamlPlayoutSequenceItem sequence in definition.Sequence)
{
foreach (YamlPlayoutSequenceInstruction instruction in sequence.Items.OfType<YamlPlayoutSequenceInstruction>())
{
graph.AddEdge(sequence.Key, instruction.Sequence);
}
}
return graph.HasAnyCycle();
}
private async Task<int> GetDaysToBuild() =>
await configElementRepository
.GetValue<int>(ConfigElementKey.PlayoutDaysToBuild)

7
ErsatzTV.Infrastructure/Search/AdjGraph.cs → ErsatzTV.Core/Search/AdjGraph.cs

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
namespace ErsatzTV.Infrastructure.Search;
namespace ErsatzTV.Core.Search;
public class AdjGraph
{
@ -21,6 +21,11 @@ public class AdjGraph @@ -21,6 +21,11 @@ public class AdjGraph
return HasCycleImpl(from.ToLowerInvariant(), visited, stack);
}
public bool HasAnyCycle()
{
return _edges.Any(edge => HasCycle(edge.From));
}
private bool HasCycleImpl(string node, ISet<string> visited, ISet<string> stack)
{
if (stack.Contains(node))
Loading…
Cancel
Save