mirror of
https://github.com/FabInfra/capnproto-dotnetcore_Runtime.git
synced 2025-03-12 23:01:44 +01:00
cap. lifecycle fixes + more test cases
This commit is contained in:
parent
ec0df4872f
commit
eb321e5a8e
@ -98,5 +98,23 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
{
|
{
|
||||||
NewDtbdctTestbed().RunTest(Testsuite.BootstrapReuse);
|
NewDtbdctTestbed().RunTest(Testsuite.BootstrapReuse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Ownership1()
|
||||||
|
{
|
||||||
|
NewDtbdctTestbed().RunTest(Testsuite.Ownership1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Ownership2()
|
||||||
|
{
|
||||||
|
NewDtbdctTestbed().RunTest(Testsuite.Ownership2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Ownership3()
|
||||||
|
{
|
||||||
|
NewDtbdctTestbed().RunTest(Testsuite.Ownership3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ using System.Threading.Tasks;
|
|||||||
namespace Capnp.Net.Runtime.Tests
|
namespace Capnp.Net.Runtime.Tests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
|
[TestCategory("Coverage")]
|
||||||
public class LocalRpc: TestBase
|
public class LocalRpc: TestBase
|
||||||
{
|
{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@ -113,5 +114,23 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
{
|
{
|
||||||
NewLocalTestbed().RunTest(Testsuite.Basic);
|
NewLocalTestbed().RunTest(Testsuite.Basic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Ownership1()
|
||||||
|
{
|
||||||
|
NewLocalTestbed().RunTest(Testsuite.Ownership1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Ownership2()
|
||||||
|
{
|
||||||
|
NewLocalTestbed().RunTest(Testsuite.Ownership2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Ownership3()
|
||||||
|
{
|
||||||
|
NewLocalTestbed().RunTest(Testsuite.Ownership3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ using System.Threading.Tasks;
|
|||||||
namespace Capnp.Net.Runtime.Tests
|
namespace Capnp.Net.Runtime.Tests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
|
[TestCategory("Coverage")]
|
||||||
public class TcpRpcErrorHandling: TestBase
|
public class TcpRpcErrorHandling: TestBase
|
||||||
{
|
{
|
||||||
class MemStreamEndpoint : IEndpoint
|
class MemStreamEndpoint : IEndpoint
|
||||||
|
@ -166,5 +166,23 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
|
|
||||||
Assert.IsTrue(impl.IsDisposed);
|
Assert.IsTrue(impl.IsDisposed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Ownership1()
|
||||||
|
{
|
||||||
|
NewLocalhostTcpTestbed().RunTest(Testsuite.Ownership1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Ownership2()
|
||||||
|
{
|
||||||
|
NewLocalhostTcpTestbed().RunTest(Testsuite.Ownership2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Ownership3()
|
||||||
|
{
|
||||||
|
NewLocalhostTcpTestbed().RunTest(Testsuite.Ownership3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -532,5 +532,43 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
Assert.IsFalse(impl.IsDisposed);
|
Assert.IsFalse(impl.IsDisposed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Ownership1(ITestbed testbed)
|
||||||
|
{
|
||||||
|
var impl = new TestMoreStuffImpl(new Counters());
|
||||||
|
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<int>();
|
||||||
|
var ti = new TestInterfaceImpl(new Counters(), tcs);
|
||||||
|
testbed.MustComplete(main.CallFoo(ti, default));
|
||||||
|
testbed.MustComplete(tcs.Task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Ownership2(ITestbed testbed)
|
||||||
|
{
|
||||||
|
var impl = new TestMoreStuffImpl(new Counters());
|
||||||
|
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
|
||||||
|
using (var nullProxy = main.GetNull().Eager(true))
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<int>();
|
||||||
|
var ti = new TestInterfaceImpl(new Counters(), tcs);
|
||||||
|
testbed.MustComplete(nullProxy.CallFoo(ti, default));
|
||||||
|
testbed.MustComplete(tcs.Task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Ownership3(ITestbed testbed)
|
||||||
|
{
|
||||||
|
var impl = new TestMoreStuffImpl(new Counters());
|
||||||
|
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
|
||||||
|
using (var nullProxy = main.GetNull(new CancellationToken(true)).Eager(true))
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<int>();
|
||||||
|
var ti = new TestInterfaceImpl(new Counters(), tcs);
|
||||||
|
testbed.MustComplete(nullProxy.CallFoo(ti, default));
|
||||||
|
testbed.MustComplete(tcs.Task);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,12 +167,12 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
|
|
||||||
void ITestbed.MustComplete(params Task[] tasks)
|
void ITestbed.MustComplete(params Task[] tasks)
|
||||||
{
|
{
|
||||||
Assert.IsTrue(Task.WhenAll(tasks).IsCompleted);
|
Assert.IsTrue(tasks.All(t => t.IsCompleted));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ITestbed.MustNotComplete(params Task[] tasks)
|
void ITestbed.MustNotComplete(params Task[] tasks)
|
||||||
{
|
{
|
||||||
Assert.IsFalse(Task.WhenAny(tasks).IsCompleted);
|
Assert.IsFalse(tasks.Any(t => t.IsCompleted));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,14 +248,30 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
return _client.GetMain<T>();
|
return _client.GetMain<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Task[] GulpExceptions(Task[] tasks)
|
||||||
|
{
|
||||||
|
async Task Gulp(Task t)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await t;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tasks.Select(Gulp).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
void ITestbed.MustComplete(params Task[] tasks)
|
void ITestbed.MustComplete(params Task[] tasks)
|
||||||
{
|
{
|
||||||
Assert.IsTrue(Task.WaitAll(tasks, MediumNonDbgTimeout));
|
Assert.IsTrue(Task.WaitAll(GulpExceptions(tasks), MediumNonDbgTimeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ITestbed.MustNotComplete(params Task[] tasks)
|
void ITestbed.MustNotComplete(params Task[] tasks)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(-1, Task.WaitAny(tasks, ShortTimeout));
|
Assert.AreEqual(-1, Task.WaitAny(GulpExceptions(tasks), ShortTimeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ITestbed.FlushCommunication()
|
void ITestbed.FlushCommunication()
|
||||||
|
@ -45,6 +45,7 @@ namespace Capnp
|
|||||||
/// The kind of object this state currently represents.
|
/// The kind of object this state currently represents.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ObjectKind Kind { get; set; }
|
public ObjectKind Kind { get; set; }
|
||||||
|
bool _disposed;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The capabilities imported from the capability table. Only valid in RPC context.
|
/// The capabilities imported from the capability table. Only valid in RPC context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -65,6 +66,7 @@ namespace Capnp
|
|||||||
StructPtrCount = 1;
|
StructPtrCount = 1;
|
||||||
Kind = ObjectKind.Struct;
|
Kind = ObjectKind.Struct;
|
||||||
Caps = null;
|
Caps = null;
|
||||||
|
_disposed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -685,12 +687,14 @@ namespace Capnp
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (Caps != null)
|
if (Caps != null && !_disposed)
|
||||||
{
|
{
|
||||||
foreach (var cap in Caps)
|
foreach (var cap in Caps)
|
||||||
{
|
{
|
||||||
cap?.Release(false);
|
cap?.Release(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,7 @@ namespace Capnp
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The different kinds of Cap'n Proto objects.
|
/// The different kinds of Cap'n Proto objects.
|
||||||
/// Despite this is a [Flags] enum, it does not make sense to mutually combine literals.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Flags]
|
|
||||||
public enum ObjectKind: byte
|
public enum ObjectKind: byte
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -76,6 +76,7 @@ namespace Capnp.Rpc
|
|||||||
/// <returns>The underlying promise</returns>
|
/// <returns>The underlying promise</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="task"/> is null.</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="task"/> is null.</exception>
|
||||||
/// <exception cref="ArgumentException">The task was not registered using MakePipelineAware.</exception>
|
/// <exception cref="ArgumentException">The task was not registered using MakePipelineAware.</exception>
|
||||||
|
[Obsolete("Please re-generate capnp code-behind. GetAnswer(task).Access(...) was replaced by Access(task, ...)")]
|
||||||
public static IPromisedAnswer GetAnswer(Task task)
|
public static IPromisedAnswer GetAnswer(Task task)
|
||||||
{
|
{
|
||||||
if (!_taskTable.TryGetValue(task, out var answer))
|
if (!_taskTable.TryGetValue(task, out var answer))
|
||||||
|
@ -91,12 +91,28 @@ namespace Capnp.Rpc
|
|||||||
|
|
||||||
async Task<DeserializerState> CallImpl(ulong interfaceId, ushort methodId, DynamicSerializerState args, CancellationToken cancellationToken)
|
async Task<DeserializerState> CallImpl(ulong interfaceId, ushort methodId, DynamicSerializerState args, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var cap = await WhenResolved;
|
ConsumedCapability? cap;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cap = await WhenResolved;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
args.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
args.Dispose();
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
}
|
||||||
|
|
||||||
if (cap == null)
|
if (cap == null)
|
||||||
|
{
|
||||||
|
args.Dispose();
|
||||||
throw new RpcException("Broken capability");
|
throw new RpcException("Broken capability");
|
||||||
|
}
|
||||||
|
|
||||||
using var proxy = new Proxy(cap);
|
using var proxy = new Proxy(cap);
|
||||||
var call = proxy.Call(interfaceId, methodId, args, default);
|
var call = proxy.Call(interfaceId, methodId, args, default);
|
||||||
|
@ -72,7 +72,10 @@ namespace Capnp.Rpc
|
|||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
if (proxy.IsNull)
|
if (proxy.IsNull)
|
||||||
|
{
|
||||||
|
args.Dispose();
|
||||||
throw new RpcException("Broken capability");
|
throw new RpcException("Broken capability");
|
||||||
|
}
|
||||||
|
|
||||||
var call = proxy.Call(interfaceId, methodId, args, default);
|
var call = proxy.Call(interfaceId, methodId, args, default);
|
||||||
var whenReturned = call.WhenReturned;
|
var whenReturned = call.WhenReturned;
|
||||||
|
@ -50,17 +50,9 @@ namespace Capnp.Rpc
|
|||||||
|
|
||||||
static async void DisposeCtrWhenReturned(CancellationTokenRegistration ctr, IPromisedAnswer answer)
|
static async void DisposeCtrWhenReturned(CancellationTokenRegistration ctr, IPromisedAnswer answer)
|
||||||
{
|
{
|
||||||
try
|
try { await answer.WhenReturned; }
|
||||||
{
|
catch { }
|
||||||
await answer.WhenReturned;
|
finally { ctr.Dispose(); }
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ctr.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -80,10 +72,16 @@ namespace Capnp.Rpc
|
|||||||
bool obsoleteAndIgnored, CancellationToken cancellationToken = default)
|
bool obsoleteAndIgnored, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (_disposedValue)
|
if (_disposedValue)
|
||||||
|
{
|
||||||
|
args.Dispose();
|
||||||
throw new ObjectDisposedException(nameof(Proxy));
|
throw new ObjectDisposedException(nameof(Proxy));
|
||||||
|
}
|
||||||
|
|
||||||
if (ConsumedCap == null)
|
if (ConsumedCap == null)
|
||||||
|
{
|
||||||
|
args.Dispose();
|
||||||
throw new InvalidOperationException("Cannot call null capability");
|
throw new InvalidOperationException("Cannot call null capability");
|
||||||
|
}
|
||||||
|
|
||||||
var answer = ConsumedCap.DoCall(interfaceId, methodId, args);
|
var answer = ConsumedCap.DoCall(interfaceId, methodId, args);
|
||||||
|
|
||||||
@ -172,7 +170,7 @@ namespace Capnp.Rpc
|
|||||||
~Proxy()
|
~Proxy()
|
||||||
{
|
{
|
||||||
#if DebugFinalizers
|
#if DebugFinalizers
|
||||||
Logger.LogWarning($"Caught orphaned Proxy, created from here: {CreatorStackTrace}.");
|
Logger?.LogWarning($"Caught orphaned Proxy, created from here: {CreatorStackTrace}.");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Dispose(false);
|
Dispose(false);
|
||||||
|
@ -50,7 +50,7 @@ namespace Capnp.Rpc
|
|||||||
~RefCountingCapability()
|
~RefCountingCapability()
|
||||||
{
|
{
|
||||||
#if DebugFinalizers
|
#if DebugFinalizers
|
||||||
Logger.LogWarning($"Caught orphaned capability, created from here: {CreatorStackTrace}.");
|
Logger?.LogWarning($"Caught orphaned capability, created from here: {CreatorStackTrace}.");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Dispose(false);
|
Dispose(false);
|
||||||
|
@ -112,9 +112,17 @@ namespace Capnp.Rpc
|
|||||||
if (!_question.StateFlags.HasFlag(PendingQuestion.State.TailCall) &&
|
if (!_question.StateFlags.HasFlag(PendingQuestion.State.TailCall) &&
|
||||||
_question.StateFlags.HasFlag(PendingQuestion.State.Returned))
|
_question.StateFlags.HasFlag(PendingQuestion.State.Returned))
|
||||||
{
|
{
|
||||||
if (ResolvedCap == null)
|
try
|
||||||
{
|
{
|
||||||
throw new RpcException("Answer did not resolve to expected capability");
|
if (ResolvedCap == null)
|
||||||
|
{
|
||||||
|
throw new RpcException("Answer did not resolve to expected capability");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
args.Dispose();
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CallOnResolution(interfaceId, methodId, args);
|
return CallOnResolution(interfaceId, methodId, args);
|
||||||
@ -126,11 +134,13 @@ namespace Capnp.Rpc
|
|||||||
#endif
|
#endif
|
||||||
if (_question.StateFlags.HasFlag(PendingQuestion.State.Disposed))
|
if (_question.StateFlags.HasFlag(PendingQuestion.State.Disposed))
|
||||||
{
|
{
|
||||||
|
args.Dispose();
|
||||||
throw new ObjectDisposedException(nameof(PendingQuestion));
|
throw new ObjectDisposedException(nameof(PendingQuestion));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_question.StateFlags.HasFlag(PendingQuestion.State.FinishRequested))
|
if (_question.StateFlags.HasFlag(PendingQuestion.State.FinishRequested))
|
||||||
{
|
{
|
||||||
|
args.Dispose();
|
||||||
throw new InvalidOperationException("Finish request was already sent");
|
throw new InvalidOperationException("Finish request was already sent");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,9 +90,13 @@ namespace Capnp.Rpc
|
|||||||
{
|
{
|
||||||
// Two reasons for ignoring exceptions on the previous task (i.e. not _.Wait()ing):
|
// Two reasons for ignoring exceptions on the previous task (i.e. not _.Wait()ing):
|
||||||
// 1. A faulting predecessor, especially due to cancellation, must not have any impact on this one.
|
// 1. A faulting predecessor, especially due to cancellation, must not have any impact on this one.
|
||||||
// 2. A faulting disembargo request would be a fatal protocol error, resulting in Abort() - we're dead anyway.
|
// 2. A faulting disembargo request would imply that the other side cannot send pending requests anyway.
|
||||||
|
|
||||||
cancellationTokenSource.Token.ThrowIfCancellationRequested();
|
if (cancellationTokenSource.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
args.Dispose();
|
||||||
|
cancellationTokenSource.Token.ThrowIfCancellationRequested();
|
||||||
|
}
|
||||||
|
|
||||||
using var proxy = new Proxy(ResolvedCap);
|
using var proxy = new Proxy(ResolvedCap);
|
||||||
return proxy.Call(interfaceId, methodId, args, default);
|
return proxy.Call(interfaceId, methodId, args, default);
|
||||||
|
@ -11,7 +11,7 @@ namespace Capnp
|
|||||||
/// by the code generator. Particularly, those writer classes are actually specializations of SerializerState, adding convenience methods
|
/// by the code generator. Particularly, those writer classes are actually specializations of SerializerState, adding convenience methods
|
||||||
/// for accessing the struct's fields.
|
/// for accessing the struct's fields.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SerializerState : IStructSerializer
|
public class SerializerState : IStructSerializer, IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a SerializerState instance for use in RPC context.
|
/// Constructs a SerializerState instance for use in RPC context.
|
||||||
@ -42,6 +42,7 @@ namespace Capnp
|
|||||||
internal uint CapabilityIndex { get; set; }
|
internal uint CapabilityIndex { get; set; }
|
||||||
|
|
||||||
SerializerState[]? _linkedStates;
|
SerializerState[]? _linkedStates;
|
||||||
|
bool _disposed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs an unbound serializer state.
|
/// Constructs an unbound serializer state.
|
||||||
@ -1380,5 +1381,18 @@ namespace Capnp
|
|||||||
var cap = StructReadRawCap(slot);
|
var cap = StructReadRawCap(slot);
|
||||||
return new Rpc.BareProxy(cap);
|
return new Rpc.BareProxy(cap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Caps != null && !_disposed)
|
||||||
|
{
|
||||||
|
foreach (var cap in Caps)
|
||||||
|
{
|
||||||
|
cap?.Release(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user