diff --git a/Capnp.Net.Runtime/Capnp.Net.Runtime.csproj b/Capnp.Net.Runtime/Capnp.Net.Runtime.csproj index 6f81750..b7675b9 100644 --- a/Capnp.Net.Runtime/Capnp.Net.Runtime.csproj +++ b/Capnp.Net.Runtime/Capnp.Net.Runtime.csproj @@ -29,7 +29,7 @@ - DebugFinalizers + diff --git a/Capnp.Net.Runtime/DeserializerState.cs b/Capnp.Net.Runtime/DeserializerState.cs index 702176c..735ae7e 100644 --- a/Capnp.Net.Runtime/DeserializerState.cs +++ b/Capnp.Net.Runtime/DeserializerState.cs @@ -636,9 +636,6 @@ namespace Capnp /// /// Capability interface /// index within this struct's pointer table - /// debugging aid - /// debugging aid - /// debugging aid /// capability instance or null if pointer was null /// negative index /// state does not represent a struct, invalid pointer, @@ -685,15 +682,19 @@ namespace Capnp return (Rpc.CapabilityReflection.CreateProxy(Caps[(int)CapabilityIndex]) as T)!; } + /// + /// Releases the capability table + /// public void Dispose() { if (Caps != null && !_disposed) { foreach (var cap in Caps) { - cap?.Release(false); + cap?.Release(); } + Caps = null; _disposed = true; } } diff --git a/Capnp.Net.Runtime/Rpc/CapabilityReflection.cs b/Capnp.Net.Runtime/Rpc/CapabilityReflection.cs index 6e10a58..4edf578 100644 --- a/Capnp.Net.Runtime/Rpc/CapabilityReflection.cs +++ b/Capnp.Net.Runtime/Rpc/CapabilityReflection.cs @@ -251,9 +251,6 @@ namespace Capnp.Rpc /// /// Capability interface. Must be annotated with . /// low-level capability - /// debugging aid - /// debugging aid - /// debugging aid /// The Proxy instance which implements . /// is null. /// did not qualify as capability interface. diff --git a/Capnp.Net.Runtime/Rpc/ConsumedCapability.cs b/Capnp.Net.Runtime/Rpc/ConsumedCapability.cs index 452bcd3..3faa492 100644 --- a/Capnp.Net.Runtime/Rpc/ConsumedCapability.cs +++ b/Capnp.Net.Runtime/Rpc/ConsumedCapability.cs @@ -20,11 +20,7 @@ namespace Capnp.Rpc internal abstract void Unfreeze(); internal abstract void AddRef(); - internal abstract void Release( - bool keepAlive, - [System.Runtime.CompilerServices.CallerMemberName] string methodName = "", - [System.Runtime.CompilerServices.CallerFilePath] string filePath = "", - [System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0); + internal abstract void Release(); #if DebugFinalizers internal Proxy? OwningProxy { get; set; } diff --git a/Capnp.Net.Runtime/Rpc/IPromisedAnswer.cs b/Capnp.Net.Runtime/Rpc/IPromisedAnswer.cs index 6fc1788..729b34d 100644 --- a/Capnp.Net.Runtime/Rpc/IPromisedAnswer.cs +++ b/Capnp.Net.Runtime/Rpc/IPromisedAnswer.cs @@ -24,6 +24,12 @@ namespace Capnp.Rpc /// Pipelined low-level capability ConsumedCapability? Access(MemberAccessPath access); + /// + /// + /// + /// Creates a low-level capability for promise pipelining. + /// Task returning the proxy whose ownership will be taken over + /// ConsumedCapability? Access(MemberAccessPath access, Task proxyTask); } } \ No newline at end of file diff --git a/Capnp.Net.Runtime/Rpc/Impatient.cs b/Capnp.Net.Runtime/Rpc/Impatient.cs index 1e621b5..7b2c7b3 100644 --- a/Capnp.Net.Runtime/Rpc/Impatient.cs +++ b/Capnp.Net.Runtime/Rpc/Impatient.cs @@ -93,6 +93,13 @@ namespace Capnp.Rpc return answer; } + /// + /// Returns a promise-pipelined capability for a remote method invocation Task. + /// + /// remote method invocation task + /// path to the desired capability + /// task returning a proxy to the desired capability + /// Pipelined low-level capability public static ConsumedCapability? Access(Task task, MemberAccessPath access, Task proxyTask) { var answer = TryGetAnswer(task); @@ -106,9 +113,6 @@ namespace Capnp.Rpc /// /// Capability interface type /// The task - /// debugging aid - /// debugging aid - /// debugging aid /// A proxy for the given task. /// is null. /// did not diff --git a/Capnp.Net.Runtime/Rpc/Interception/CensorCapability.cs b/Capnp.Net.Runtime/Rpc/Interception/CensorCapability.cs index 3323c16..0889c87 100644 --- a/Capnp.Net.Runtime/Rpc/Interception/CensorCapability.cs +++ b/Capnp.Net.Runtime/Rpc/Interception/CensorCapability.cs @@ -18,7 +18,7 @@ namespace Capnp.Rpc.Interception protected override void ReleaseRemotely() { - InterceptedCapability.Release(false); + InterceptedCapability.Release(); } internal override IPromisedAnswer DoCall(ulong interfaceId, ushort methodId, DynamicSerializerState args) diff --git a/Capnp.Net.Runtime/Rpc/LocalAnswerCapability.cs b/Capnp.Net.Runtime/Rpc/LocalAnswerCapability.cs index 696dbbb..08a5437 100644 --- a/Capnp.Net.Runtime/Rpc/LocalAnswerCapability.cs +++ b/Capnp.Net.Runtime/Rpc/LocalAnswerCapability.cs @@ -12,7 +12,7 @@ namespace Capnp.Rpc var result = await answer; var cap = access.Eval(result); var proxy = new Proxy(cap); - cap?.Release(false); + cap?.Release(); return proxy; } diff --git a/Capnp.Net.Runtime/Rpc/LocalCapability.cs b/Capnp.Net.Runtime/Rpc/LocalCapability.cs index 5ae7c91..3e1954a 100644 --- a/Capnp.Net.Runtime/Rpc/LocalCapability.cs +++ b/Capnp.Net.Runtime/Rpc/LocalCapability.cs @@ -22,7 +22,6 @@ namespace Capnp.Rpc } public Skeleton ProvidedCap { get; } - int _releaseFlag; LocalCapability(Skeleton providedCap) { @@ -31,20 +30,12 @@ namespace Capnp.Rpc internal override void AddRef() { - if (0 == Interlocked.CompareExchange(ref _releaseFlag, 0, 1)) - ProvidedCap.Claim(); + ProvidedCap.Claim(); } - internal override void Release( - bool keepAlive, - [System.Runtime.CompilerServices.CallerMemberName] string methodName = "", - [System.Runtime.CompilerServices.CallerFilePath] string filePath = "", - [System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0) + internal override void Release() { - if (keepAlive) - Interlocked.Exchange(ref _releaseFlag, 1); - else - ProvidedCap.Relinquish(); + ProvidedCap.Relinquish(); } internal override IPromisedAnswer DoCall(ulong interfaceId, ushort methodId, DynamicSerializerState args) diff --git a/Capnp.Net.Runtime/Rpc/PendingAnswer.cs b/Capnp.Net.Runtime/Rpc/PendingAnswer.cs index 3b4bc38..4bc0f89 100644 --- a/Capnp.Net.Runtime/Rpc/PendingAnswer.cs +++ b/Capnp.Net.Runtime/Rpc/PendingAnswer.cs @@ -155,7 +155,7 @@ namespace Capnp.Rpc { foreach (var cap in aorcq.Answer.Caps) { - cap?.Release(false); + cap?.Release(); } } } diff --git a/Capnp.Net.Runtime/Rpc/PendingQuestion.cs b/Capnp.Net.Runtime/Rpc/PendingQuestion.cs index 548f907..6d4f34c 100644 --- a/Capnp.Net.Runtime/Rpc/PendingQuestion.cs +++ b/Capnp.Net.Runtime/Rpc/PendingQuestion.cs @@ -277,13 +277,13 @@ namespace Capnp.Rpc { foreach (var cap in inParams.Caps!) { - cap?.Release(false); + cap?.Release(); } } if (target != null) { - target.Release(false); + target.Release(); } } @@ -291,7 +291,7 @@ namespace Capnp.Rpc { foreach (var cap in outParams.Caps!) { - cap?.Release(false); + cap?.Release(); } } diff --git a/Capnp.Net.Runtime/Rpc/Proxy.cs b/Capnp.Net.Runtime/Rpc/Proxy.cs index b6dd739..22957a9 100644 --- a/Capnp.Net.Runtime/Rpc/Proxy.cs +++ b/Capnp.Net.Runtime/Rpc/Proxy.cs @@ -11,6 +11,12 @@ namespace Capnp.Rpc /// public class Proxy : IDisposable, IResolvingCapability { + /// + /// Creates a new proxy object for an existing implementation or proxy, sharing its ownership. + /// + /// Capability interface + /// instance to share + /// public static T Share(T obj) where T: class { if (obj is Proxy proxy) @@ -149,14 +155,14 @@ namespace Capnp.Rpc { if (disposing) { - ConsumedCap?.Release(false); + ConsumedCap?.Release(); } else { // When called from the Finalizer, we must not throw. // But when reference counting goes wrong, ConsumedCapability.Release() will throw an InvalidOperationException. // The only option here is to suppress that exception. - try { ConsumedCap?.Release(false); } + try { ConsumedCap?.Release(); } catch { } } diff --git a/Capnp.Net.Runtime/Rpc/RefCountingCapability.cs b/Capnp.Net.Runtime/Rpc/RefCountingCapability.cs index d47a46f..7ad404e 100644 --- a/Capnp.Net.Runtime/Rpc/RefCountingCapability.cs +++ b/Capnp.Net.Runtime/Rpc/RefCountingCapability.cs @@ -26,17 +26,8 @@ namespace Capnp.Rpc // Value 0 has the special meaning of being in state C. long _refCount = 1; -#if DebugCapabilityLifecycle || DebugFinalizers - ILogger Logger { get; } = Logging.CreateLogger(); -#endif - -#if DebugCapabilityLifecycle - string? _releasingMethodName; - string? _releasingFilePath; - int _releasingLineNumber; -#endif - #if DebugFinalizers + ILogger Logger { get; } = Logging.CreateLogger(); string CreatorStackTrace { get; set; } #endif @@ -83,11 +74,7 @@ namespace Capnp.Rpc { lock (_reentrancyBlocker) { - if (_refCount == int.MinValue) - { - _refCount = 2; - } - else if (++_refCount <= 1) + if (++_refCount <= 1) { --_refCount; @@ -101,30 +88,16 @@ namespace Capnp.Rpc } } - internal sealed override void Release( - bool keepAlive, - [System.Runtime.CompilerServices.CallerMemberName] string methodName = "", - [System.Runtime.CompilerServices.CallerFilePath] string filePath = "", - [System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0) + internal sealed override void Release() { lock (_reentrancyBlocker) { switch (_refCount) { - case 2 when keepAlive: - _refCount = int.MinValue; - break; - case 1: // initial state, actually ref. count 0 case 2: // actually ref. count 1 _refCount = 0; -#if DebugCapabilityLifecycle - _releasingMethodName = methodName; - _releasingFilePath = filePath; - _releasingLineNumber = lineNumber; -#endif - Dispose(true); GC.SuppressFinalize(this); break; diff --git a/Capnp.Net.Runtime/Rpc/RemoteAnswerCapability.cs b/Capnp.Net.Runtime/Rpc/RemoteAnswerCapability.cs index 4631f8b..e2a49ac 100644 --- a/Capnp.Net.Runtime/Rpc/RemoteAnswerCapability.cs +++ b/Capnp.Net.Runtime/Rpc/RemoteAnswerCapability.cs @@ -42,7 +42,7 @@ namespace Capnp.Rpc var result = await question.WhenReturned; var cap = access.Eval(result); var proxy = new Proxy(cap); - cap?.Release(false); + cap?.Release(); return proxy; } diff --git a/Capnp.Net.Runtime/Rpc/RpcEngine.cs b/Capnp.Net.Runtime/Rpc/RpcEngine.cs index 8b6d9ce..195fcd5 100644 --- a/Capnp.Net.Runtime/Rpc/RpcEngine.cs +++ b/Capnp.Net.Runtime/Rpc/RpcEngine.cs @@ -60,11 +60,25 @@ namespace Capnp.Rpc } } + /// + /// Stateful implementation for hosting a two-party RPC session. may own multiple mutually + /// independent endpoints. + /// public class RpcEndpoint : IEndpoint, IRpcEndpoint { + /// + /// Endpoint state + /// public enum EndpointState { + /// + /// Active means ready for exchanging RPC messages. + /// Active, + + /// + /// The session is closed, either deliberately or due to an error condition. + /// Dismissed } @@ -95,8 +109,14 @@ namespace Capnp.Rpc State = EndpointState.Active; } + /// + /// Session state + /// public EndpointState State { get; private set; } + /// + /// Closes the session, clears export table, terminates all pending questions and enters 'Dismissed' state. + /// public void Dismiss() { lock (_reentrancyBlocker) @@ -120,6 +140,10 @@ namespace Capnp.Rpc _tx.Dismiss(); } + /// + /// Feeds a frame for processing + /// + /// frame to process public void Forward(WireFrame frame) { if (State == EndpointState.Dismissed) @@ -129,11 +153,43 @@ namespace Capnp.Rpc ProcessFrame(frame); } + /// + /// Number of frames sent so far + /// public long SendCount => Interlocked.Read(ref _sendCount); + + /// + /// Number of frames received so far + /// public long RecvCount => Interlocked.Read(ref _recvCount); - public int ImportedCapabilityCount => _importTable.Count; - public int ExportedCapabilityCount => _exportTable.Count; + /// + /// Current number of entries in import table + /// + public int ImportedCapabilityCount + { + get + { + lock (_reentrancyBlocker) + { + return _importTable.Count; + } + } + } + + /// + /// Current number of entries in export table + /// + public int ExportedCapabilityCount + { + get + { + lock (_reentrancyBlocker) + { + return _exportTable.Count; + } + } + } void Tx(WireFrame frame) { @@ -1095,6 +1151,10 @@ namespace Capnp.Rpc } } + /// + /// Queries the peer for its bootstrap capability + /// + /// low-level capability public ConsumedCapability QueryMain() { var mb = MessageBuilder.Create(); @@ -1370,7 +1430,7 @@ namespace Capnp.Rpc else { postAction += cap.Export(this, capDesc); - cap.Release(false); + cap.Release(); } } @@ -1498,6 +1558,11 @@ namespace Capnp.Rpc readonly ConcurrentBag _inboundEndpoints = new ConcurrentBag(); + /// + /// Adds an endpoint + /// + /// endpoint for handling outgoing messages + /// endpoint for handling incoming messages public RpcEndpoint AddEndpoint(IEndpoint outboundEndpoint) { var inboundEndpoint = new RpcEndpoint(this, outboundEndpoint); diff --git a/Capnp.Net.Runtime/SerializerState.cs b/Capnp.Net.Runtime/SerializerState.cs index 26549f7..8a19bba 100644 --- a/Capnp.Net.Runtime/SerializerState.cs +++ b/Capnp.Net.Runtime/SerializerState.cs @@ -1382,15 +1382,19 @@ namespace Capnp return new Rpc.BareProxy(cap); } + /// + /// Releases the capability table + /// public void Dispose() { if (Caps != null && !_disposed) { foreach (var cap in Caps) { - cap?.Release(false); + cap?.Release(); } + Caps.Clear(); _disposed = true; } }