Inversifyjs: Maximum call stack size exceeded

Created on 6 May 2017  路  14Comments  路  Source: inversify/InversifyJS

Sometimes i am getting such errors and it takes a while to debug through inversify code and check bindings to find out reasons behind them. Is there a better to investigate them?

All 14 comments

Can you please share details about your environment (version of node, ts, inversify, browser, etc.) also can you share a code example that reproduces the issue?

Call stack issues are mostly related with circular dependencies but Inversify should catch those and explain where is the circular dependency located.

Maybe in activation handler!

container.bind<Ninja>("Ninja").to(Ninja);

container.bind<Katana>("Katana").to(Katana).onActivation((context, katana) => {
    let handler = {
        apply: function(target, thisArgument, argumentsList) {
            console.log(`Starting: ${new Date().getTime()}`);
            let result = target.apply(thisArgument, argumentsList);
            console.log(`Finished: ${new Date().getTime()}`);
            return result;
        }
    };
    katana.use = new Proxy(katana.use, handler);
    return katana;
});

This demo will throw the Error when run many times(exceed nodejs max callstack size)

@akosyakov can you please confirm that you are using onActivation handler?

@marshalYuan I just tried with the following unit test and it worked fine:

it("Should not throw maximum call stack size exceeded", () => {

    interface Ninja {
        fight(): string;
    }

    interface Katana {
        hit(): string;
    }

    @injectable()
    class Katana implements Katana {
        public hit() {
            return "cut!";
        }
    }

    @injectable()
    class Ninja implements Ninja {

        private _katana: Katana;

        public constructor(
            @inject("Katana") katana: Katana
        ) {
            this._katana = katana;
        }

        public fight() {return this._katana.hit(); }

    }

    let container = new Container();
    container.bind<Ninja>("Ninja").to(Ninja);
    container.bind<Katana>("Katana").to(Katana).onActivation((context, katana) => {
        let handler = {
            apply: function(target: any, thisArgument: any, argumentsList: any) {
                console.log(`Starting: ${new Date().getTime()}`);
                let result = target.apply(thisArgument, argumentsList);
                console.log(`Finished: ${new Date().getTime()}`);
                return result;
            }
        };
        katana.hit = new Proxy(katana.hit, handler);
        return katana;
    });

    let ninja = container.get<Ninja>("Ninja");
    expect(ninja.fight()).eql("cut!");

});

The problem must be something else not the onActivation handler.

We don't use onActivation, but we have a lot of dynamic bindings, factories, and some providers. I will try to come up with a reproducible example.

I think it should something to do with dynamic bindings, e.g.:

interface Foo {
}

class FooImpl implements Foo {
  constructor(
    @inject(Bar) bar: Bar
  ) {}
}

class Bar {
  constructor(
    @inject(Foo) foo: Foo
  ) {}
}

container.bind(FooImpl).toSelf();
container.bind(Foo).toDynamicValue(ctx => 
  ctx.container.get(FooImpl)
);

container.get(Bar);

I have not tried this code, but a closure for the dynamic value triggers a new request and it can be a reason.

Generally we have kind of a pattern:

container.bind(FooImpl).toSelf().inSingletonScope();
container.bind(Foo).toDynamicValue(ctx => 
  ctx.container.get(FooImpl)
).inSingletonScope();

So some clients can inject FooImpl directly and other Foo but both get the same instance. Is there a better way to achieve it without toDynamicValue?

Hi @akosyakov thanks for the hints, I will try a few things to see if I can get to reproduce it. Please share an tests case if you do find it.

@remojansen Could you comment on https://github.com/inversify/InversifyJS/issues/549#issuecomment-304857978 please? How would you achieve it?

As you said, the following two bindings return the same instance of FooImpl:

container.bind(FooImpl).toSelf().inSingletonScope();
container.bind(Foo).toDynamicValue(ctx => 
  ctx.container.get(FooImpl)
).inSingletonScope();

So you have two identifiers FooImpl & Foo that resolve same singleton instance of FooImpl. Can you please explain me why you need two identifiers?

The only other way I can think about is:

container.bind(FooImpl).toSelf().inSingletonScope();
container.bind("PUBLIC_ID_A").toConstantValue(container.get(FooImpl));

Two different interfaces to the same code, one expose only public API, another provide an access to internals, like interfaces.Container and just Container.

I think toDynamicValue works nicely in that case.

I think the issue could be related with circular dependencies. Call stack errors are thrown wen there is a circular dependency. we catch it in a very specific place.

In your case maybe the exception takes place when the dynamic value is resolved. That means that we might need an additional catch. Again, thanks for the info, I think I have now some info to try some test cases :+1:

@akosyakov do you have more insights about this? Is it still happening to you?

@remojansen I've added a test for it: https://github.com/inversify/InversifyJS/pull/604

Was this page helpful?
0 / 5 - 0 ratings