Skip to content
Merged
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
28 changes: 27 additions & 1 deletion Fluid.Tests/TemplateContextTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Fluid.Values;
using Fluid.Ast;
using Fluid.Values;
using System;
using System.Globalization;
using System.Linq;
Expand Down Expand Up @@ -135,6 +136,31 @@ public void ScopeSetValueAcceptsNull()
Assert.Equal(NilValue.Instance, context.GetValue("text"));
}

[Fact]
public async Task ShouldNotReleaseScopeAsynchronously()
{
var parser = new FluidParser();

parser.RegisterEmptyBlock("sleep", async (statements, writer, encoder, context) =>
{
context.EnterChildScope();
context.IncrementSteps();
context.SetValue("id", "0");
await Task.Delay(100);
await statements.RenderStatementsAsync(writer, encoder, context);
context.ReleaseScope();
return Completion.Normal;
});

var context = new TemplateContext { };
context.SetValue("id", "1");
var template = parser.Parse(@"{{id}}{%sleep%}{{id}}{%endsleep%}{{id}}");

var output = await template.RenderAsync(context);

Assert.Equal("101", output);
}

private class TestClass
{
public string Name { get; set; }
Expand Down
36 changes: 8 additions & 28 deletions Fluid/FluidTemplateExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public static void Render(this IFluidTemplate template, TemplateContext context,
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ValueTask<string> RenderAsync(this IFluidTemplate template, TemplateContext context, TextEncoder encoder)
public static async ValueTask<string> RenderAsync(this IFluidTemplate template, TemplateContext context, TextEncoder encoder)
{
if (context == null)
{
Expand All @@ -44,45 +44,25 @@ public static ValueTask<string> RenderAsync(this IFluidTemplate template, Templa
ExceptionHelper.ThrowArgumentNullException(nameof(template));
}

static async ValueTask<string> Awaited(
ValueTask task,
StringWriter writer,
StringBuilderPool builder)
{
await task;
await writer.FlushAsync();
var s = builder.ToString();
builder.Dispose();
writer.Dispose();
return s;
}

var sb = StringBuilderPool.GetInstance();
var writer = new StringWriter(sb.Builder);

// A template is evaluated in a child scope such that the provided TemplateContext is immutable
context.EnterChildScope();

try
{
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What will be returns if an exception is occured?!!

Copy link
Copy Markdown
Owner Author

@sebastienros sebastienros Dec 14, 2021

Choose a reason for hiding this comment

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

An Exception

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

So this intentionally?!! coz isn't throw before

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Probably we need a remark on that method to make clear to everyone

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't think the behavior has changed here. New code just awaits to prevent control flow from getting out with unfinished continuations that could run after finally has been processed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What confuses me the old behavior returns the actual string from the StringBuilderPool instace in both case (Exception or Without Exception) but now the string is returned if no exception occurs. Am I right?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The old code would have done the await on Awaited method which would have bubbled the exception. So both versions will throw, this one obeys template context life time.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Wrong, if there had been an exception, it would have been thrown by the task that is returned.
No exception is caught here, so everything will bubble up.

// A template is evaluated in a child scope such that the provided TemplateContext is immutable
context.EnterChildScope();

var task = template.RenderAsync(writer, encoder, context);
if (!task.IsCompletedSuccessfully)
{
return Awaited(task, writer, sb);
}
await template.RenderAsync(writer, encoder, context);

writer.Flush();
return sb.ToString();
}
finally
{
sb.Dispose();
writer.Dispose();
context.ReleaseScope();
}

var result = sb.ToString();
sb.Dispose();
writer.Dispose();

return new ValueTask<string>(result);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down