Document & Media Workspace: Limit route generation (Closes #22910)#23085
Document & Media Workspace: Limit route generation (Closes #22910)#23085nielslyngsoe wants to merge 3 commits into
Conversation
|
|
This looks to be OK to me @nielslyngsoe. I've tested it using a custom using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Web.UI.Custom.Segments;
public class MySegmentComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.Services.AddUnique<ISegmentService, MySegmentService>();
builder.Services.Configure<SegmentSettings>(settings => settings.Enabled = true);
}
}
public class MySegmentService : ISegmentService
{
// High segment count to reproduce the N² split-view route explosion from issue #22910.
// Variant options ≈ cultures × (1 + SegmentCount); the workspace editor generates that count
// squared in routes. 150 segments on a single culture (~22,800 routes) makes the per-keystroke
// freeze clearly visible.
private const int SegmentCount = 150;
private readonly Segment[] _segments =
Enumerable.Range(1, SegmentCount)
.Select(i => new Segment { Alias = $"segment-{i:D3}", Name = $"Segment {i:D3}" })
.ToArray();
public Task<Attempt<PagedModel<Segment>?, SegmentOperationStatus>> GetPagedSegmentsAsync(int skip = 0, int take = 100)
=> Task.FromResult
(
Attempt.SucceedWithStatus<PagedModel<Segment>?, SegmentOperationStatus>
(
SegmentOperationStatus.Success,
new PagedModel<Segment> { Total = _segments.Length, Items = _segments.Skip(skip).Take(take) }
)
);
}With that in place, when I've opened up a segment variant document there's a very clear lag of several seconds with the code on So I'm fine going with this. It's simpler. I wonder if using a dynamic route (as in #22958) is arguably the more correct approach, since it avoids generating static routes for every variant pairing (≈22,800 here, from 151 × 151 variant options), but with your fix in place that generation no longer causes a practical problem. As far as I can tell, the problematic code is the de-duplication check in From the original reporter's scenario, that was ~35k routes, so ~1.25 billion comparisons per keystroke, observed as a ~7-second freeze. You've effectively fixed this by no longer regenerating the routes on every keystroke (the variant options no longer re-emit when only the document name changes), so the setter is no longer called with a fresh same-length route set — which is the only case that hits the expensive branch. You might consider also improving the algorithm itself, in case it gets called with a large route set again in future: public set routes(value: UmbRoute[] | undefined) {
value ??= [];
const oldValue = this.#router.routes;
- if (
- value.length !== oldValue?.length ||
- value.filter((route) => oldValue?.findIndex((r) => r.path === route.path) === -1).length > 0
- ) {
+ if (!oldValue || value.length !== oldValue.length) {
+ this.#router.routes = value;
+ return;
+ }
+
+ const oldPaths = new Set(oldValue.map((route) => route.path));
+ if (value.some((route) => !oldPaths.has(route.path))) {
this.#router.routes = value;
}
}This takes it from This is shared router infrastructure rather than part of your change, so it could be a follow-up, but flagging here mainly because this issue is what surfaced it. |



Only generate routes when the dependent data for the routes change.
Fixes #22910
Replaces PR: #22958
No Unit Tests:
This PR has no unit tests, testing this scenario would require quite a bit of mocking, which is hard to maintain and could case tests that parse despite they in real life would not.
As Claude put it:
the test is probably not worth it, and it's likely to cause friction later
Test Notes:
See issue for aspects to test.