Skip to content

Commit 4e41b71

Browse files
authored
Merge pull request #392 from nuskey8/codex/fix-gc-lifetime-assertion
Make async GC lifetime assertions deterministic
2 parents 50d867c + efbf98a commit 4e41b71

3 files changed

Lines changed: 54 additions & 27 deletions

File tree

‎tests/Lua.Tests/ExceptionLifetimeTests.cs‎

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,25 @@ public void DiscardedExecutionErrorDoesNotRootPayload(bool asynchronous, bool re
3030
[MethodImpl(MethodImplOptions.NoInlining)]
3131
static WeakReference FailExecution(LuaState state, bool asynchronous, bool readMessage)
3232
{
33+
var suspension = new ManualSuspension();
3334
var payload = new object();
3435
state.Environment["payload"] = LuaValue.FromObject(payload);
3536
state.Environment["pause"] = new LuaFunction(
3637
async (context, _) =>
3738
{
3839
if (asynchronous)
39-
await Task.Yield();
40+
await suspension;
4041
return context.Return();
4142
}
4243
);
4344
try
4445
{
45-
state
46+
var operation = state
4647
.DoStringAsync("local value=payload;payload=nil;pause();error(value)")
47-
.AsTask()
48-
.GetAwaiter()
49-
.GetResult();
48+
.AsTask();
49+
if (asynchronous)
50+
suspension.Resume();
51+
operation.GetAwaiter().GetResult();
5052
Assert.Fail("Expected a Lua error.");
5153
}
5254
catch (LuaRuntimeException exception)
@@ -68,22 +70,22 @@ public void DiscardedDirectCallErrorDoesNotRootPayload(bool asynchronous)
6870
[MethodImpl(MethodImplOptions.NoInlining)]
6971
static WeakReference FailDirectCall(LuaState state, bool asynchronous)
7072
{
73+
var suspension = new ManualSuspension();
7174
var payload = new object();
7275
var function = new LuaFunction(
7376
async (context, _) =>
7477
{
7578
if (asynchronous)
76-
await Task.Yield();
79+
await suspension;
7780
throw new LuaRuntimeException(context.State, context.GetArgument(0));
7881
}
7982
);
8083
try
8184
{
82-
state
83-
.CallAsync(function, [LuaValue.FromObject(payload)])
84-
.AsTask()
85-
.GetAwaiter()
86-
.GetResult();
85+
var operation = state.CallAsync(function, [LuaValue.FromObject(payload)]).AsTask();
86+
if (asynchronous)
87+
suspension.Resume();
88+
operation.GetAwaiter().GetResult();
8789
Assert.Fail("Expected a Lua error.");
8890
}
8991
catch (LuaRuntimeException) { }
@@ -101,6 +103,7 @@ public void DiscardedMetamethodErrorDoesNotRootCapturedPayload(bool asynchronous
101103
[MethodImpl(MethodImplOptions.NoInlining)]
102104
static WeakReference FailMetamethod(LuaState state, bool asynchronous)
103105
{
106+
var suspension = new ManualSuspension();
104107
var payload = new object();
105108
var table = new LuaTable
106109
{
@@ -110,7 +113,7 @@ static WeakReference FailMetamethod(LuaState state, bool asynchronous)
110113
async (context, _) =>
111114
{
112115
if (asynchronous)
113-
await Task.Yield();
116+
await suspension;
114117
GC.KeepAlive(payload);
115118
throw new LuaRuntimeException(context.State, "failure");
116119
}
@@ -119,7 +122,10 @@ static WeakReference FailMetamethod(LuaState state, bool asynchronous)
119122
};
120123
try
121124
{
122-
state.AddAsync(table, 1).AsTask().GetAwaiter().GetResult();
125+
var operation = state.AddAsync(table, 1).AsTask();
126+
if (asynchronous)
127+
suspension.Resume();
128+
operation.GetAwaiter().GetResult();
123129
Assert.Fail("Expected a Lua error.");
124130
}
125131
catch (LuaRuntimeException) { }

‎tests/Lua.Tests/ExecutionContextLifetimeTests.cs‎

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,33 @@ public class ExecutionContextLifetimeTests
1111
public void CompletedExecutionDoesNotRootDisposedState(bool asynchronous, bool failure)
1212
{
1313
var reference = RunAndDispose(asynchronous, failure);
14-
// A completed async continuation may still be unwinding on another thread.
15-
Assert.That(
16-
() =>
17-
{
18-
GC.Collect();
19-
GC.WaitForPendingFinalizers();
20-
GC.Collect();
21-
return reference.IsAlive;
22-
},
23-
Is.False.After(1000, 10),
24-
"A pooled VM context retained its Lua state."
25-
);
14+
GC.Collect();
15+
GC.WaitForPendingFinalizers();
16+
GC.Collect();
17+
Assert.That(reference.IsAlive, Is.False, "A pooled VM context retained its Lua state.");
2618
}
2719

2820
[MethodImpl(MethodImplOptions.NoInlining)]
2921
static WeakReference RunAndDispose(bool asynchronous, bool failure)
3022
{
3123
using var state = LuaState.Create();
24+
var suspension = new ManualSuspension();
3225
state.Environment["callback"] = new LuaFunction(
3326
async (context, _) =>
3427
{
3528
if (asynchronous)
36-
await Task.Yield();
29+
await suspension;
3730
if (failure)
3831
throw new LuaRuntimeException(context.State, "failure");
3932
return context.Return(42);
4033
}
4134
);
4235
try
4336
{
44-
state.DoStringAsync("return callback()").AsTask().GetAwaiter().GetResult();
37+
var operation = state.DoStringAsync("return callback()").AsTask();
38+
if (asynchronous)
39+
suspension.Resume();
40+
operation.GetAwaiter().GetResult();
4541
Assert.That(failure, Is.False);
4642
}
4743
catch (LuaRuntimeException) when (failure) { }
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.Runtime.CompilerServices;
2+
3+
namespace Lua.Tests;
4+
5+
sealed class ManualSuspension : INotifyCompletion
6+
{
7+
Action? continuation;
8+
9+
public ManualSuspension GetAwaiter() => this;
10+
11+
public bool IsCompleted => false;
12+
13+
public void GetResult() { }
14+
15+
public void OnCompleted(Action continuation) => this.continuation = continuation;
16+
17+
// Return from the continuation before callers assert collectibility.
18+
[MethodImpl(MethodImplOptions.NoInlining)]
19+
public void Resume()
20+
{
21+
var callback = continuation!;
22+
continuation = null;
23+
callback();
24+
}
25+
}

0 commit comments

Comments
 (0)