Archived

This forum has been archived. Please start a new discussion on GitHub.

How to intercept proxy invocations?

I have a lot of client code that just calls methods directly on a proxy:
myProxy.MyMethod(param1, param2);

I need to add some fancy custom invocation wrappers around all methods on all objects. I need to perform call interception on client side rather than server side. I want to do this with minimum changes to client and server code.

Since *Prx type is merely an interface in C#, I can generate, during build or at run time, wrapper classes for all known Slice interfaces like this:
class MyInterceptor : MyServicePrx
{
    public MyServicePrx Inner;

    public void MyMethod(int param1, string param2)
    {
        // stuff to do before invocation
        Inner.MyMethod(param1, param2);
        // stuff to do after invocation
    }

    // explicit context, async, and ObjectPrx methods omitted for brevity
}

Is there a way to avoid code generation? I don't care about specific method parameters or method names. I can use dynamic Ice for all my interception logic.

Ice has server-side dispatch interceptors:
class MyGenericInterceptor : DispatchInterceptor
{
    public Ice.Object Inner;

    public override DispatchStatus dispatch(Request request)
    {
        // stuff to do before invocation
        Inner.ice_dispatch(request);
        // stuff to do after invocation
        return DispatchStatus.DispatchOK;
    }
}

How can I do this client-side?

PrxHelper and DelM classes perform serialization into untyped dynamic requests, but there doesn't seem to be any way to intercept these dynamic requests. They are sent directly to wire protocols. All methods involved in handling of these dynamic requests appear to be private implementation details that shouldn't be touched by application code.

There's the Ice.Router interface and ice_router() proxy modifier, but there's no documentation on how to use this functionality. All documentation talks about Glacier2, which is an inefficient overkill for my use case.

I can create loopback object adapter and add one Ice.Blobject for every proxy that needs to be intercepted.
public class MyGenericInterceptor : Ice.Blobject
{
    public ObjectPrx Inner;

    public bool ice_invoke(byte[] inParams, out byte[] outParams, Ice.Current current);
    {
        // stuff to do before invocation
        var result = Inner.ice_invoke(current.operation, current.mode,
            inParams, out outParams, current.context);
        // stuff to do after invocation
        return result;
    }
}

That however means huge memory leak of Ice objects since proxies are often created one per call just to set some proxy options.

I can use the default servant feature to maintain only one Ice.Blobject for the whole process. I can hook virtual object identity handlers to proxies so that handler lifetime is roughly the same as proxy lifetime from garbage collector viewpoint. That would solve the memory leak issue.

[UPDATED] But this solution also creates new problem. I need to set Ice.ThreadPool.Server.Serialize=1 to keep oneway invocations ordered. Parallel default servant with no request serialization would inevitably reorder oneway invocations, which can completely mess up some applications. But then enabling serialization causes all requests going through the default servant to be serialized with grave consequences.

Every recursive invocation will deadlock. Since layered interceptors necessarily cause recursion on the default servant, I am effectively prevented from layering interceptors, which severely limits what I can do with them.

Some messy workarounds exist. I can for example create one object adapter per interceptor type. This works as far as I don't layer several instances of the same type of interceptor on top of each other. Alternatively I can keep a pool of object adapters and provision them whenever there's a risk of recursion. Risk of recursion however often depends on how applications are structured, which means that recursion avoidance will creep complexity into numerous services.

Anyway I find this solution rather complicated and unnatural. I am hoping for some simpler and cleaner solution.

Comments

  • Ping. Can someone at least confirm this is a limitation of Ice rather than my misunderstanding of how Ice works? Any plans on providing an easy-to-use proxy interception API?

    At the moment the only viable solution seems to be to create one interception adapter for every real adapter the application is connected to. If interceptors are layered, then every layer will need its own intermediary adapter for every real destination adapter. That way there will be no deadlocks for sure and no complexity will creep into individual applications.

    I will create interception proxies with identity constructed like "InterceptorA/InterceptorB/OriginalIdentity". Invocations on such proxy will be intercepted by InterceptorA, which will forward invocations to proxy with identity "InterceptorB/OriginalIdentity". These calls will be handled by InterceptorB, which will then invoke the operation on real proxy with identity "OriginalIdentity". At the moment it looks like clean interceptor layering mechanism. Things will get more messy when I need to merge/split message streams or create interaction between different connections.

    Since we are using micro-service architecture with lots of small services and some services contact every other service in the grid, the number of interception adapters can grow to hundreds per service or thousands per node. That seems inefficient to me, although I haven't benchmarked it.