Skip to content

Improved hidden service handling and event management - #243

Merged
yallie merged 45 commits into
theRainbird:auth-refactorfrom
yallie:auth-refactor
Oct 23, 2026
Merged

yallie merged 45 commits into
theRainbird:auth-refactorfrom
yallie:auth-refactor

Conversation

@yallie

@yallie yallie commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator
  1. Hidden system services shouldn't be remotely callable:
var proxy = client.CreateProxy<IDelegateProxyFactory>();
proxy.Create(typeof(Action), null)); // throws RemoteInvocationException: service is hidden
  1. IDependencyInjectionContainer should be resolvable:
public class MyCustomService(IDependencyInjectionContainer container)
{
     // container can be used to resolve other services on demand
}
  1. Added a hidden server-side service for invoking events:
// server component can relay other service's events
eventDispatcher.Invoke<ITestService>(s => nameof(s.ServiceEvent), EventArgs.Empty);
  1. Added reverse index to ClientDelegateRegistry for faster lookup by delegate + proxy pair:
var indexKey = (clientDelegate, serviceProxy); // key is allocated on stack, not heap

if (_reverseIndex.TryGetValue(indexKey, out var existing)) // O(1) lookup
    return existing;

This change fixes bug #244.

  1. Implemented event subscription tracker, to compare active subscriptions on client and server:
var proxy = client.CreateProxy<ITestService>();
proxy.ServiceEvent += eventHandler;

Assert.Equal(1, client.SubscriptionTracker.Count); // SubscriptionTracker is internal, not a public API
Assert.Equal(client.SubscriptionTracker.Checksum, serverTracker.Checksum);
  1. BeforeCall event handler can cancel method invocation:
server.BeforeCall += (sender, ctx)
{
    if (ctx.MethodCallMessage.MethodName == "ProtectedMethod")
        ctx.Cancel = true; // throws RemoteInvocationException on client
}
  1. Added ServerRpcContext.ReturnValue property which can be changed in AfterCall event handler:
server.AfterCall += (s, ctx) => ctx.ReturnValue = newReturnValue;
  1. Added proxiless ServiceInvoker for internal/private interfaces that Castle cannot proxy:
using var invoker = client.CreateInvoker<IPrivateEchoService>();
invoker.Invoke(s => s.Echo("test")));
  1. Renamed EventStub → ServiceEventHub to reflect its role.
  2. DelegateProxyFactory now caches dynamic methods for the delegates of the same type.
  3. Disconnecting a client removes all its subscriptions on server.
  4. Fixed a deadlock between RemotingSession's "goodbye" message handler and DisposeAsync.
  5. Added AtomicBool primitive to use instead of bool flags to guard DisposeAsync methods.
  6. Added internal IServerSubscriptionService to cleanup client delegates on proxy shutdown.
  7. Added deterministic ShutdownProxyAsync that unsubscribes from all server events:
var proxy = client.CreateProxy<IMyService>();
proxy.Event1 += handler1;
proxy.Event2 += handler2;
await client.ShutdownProxyAsync(proxy); // notify server to drop both subscriptions
  1. Added IDelegateProxy.IsDisposed and delegate.IsDisposed() extension method for server components:
private readonly List<Action<string>> _callbacks = new();

public void RegisterCallback(Action<string> callback) => _callbacks.Add(callback);

private void Tick()
{
    foreach (var cb in _callbacks)
        cb("hello");  // dead callback → silent no-op

    _callbacks.RemoveAll(cb => cb.IsDisposed());  // cleanup outdated callbacks
}

@yallie yallie mentioned this pull request Oct 17, 2026
yallie added 17 commits October 22, 2026 03:05
…y, enabled skipped tests for reused dynamic methods.
@yallie
yallie merged commit a16c2d4 into theRainbird:auth-refactor Oct 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant