asEagerSingleton -- deferred binding problem?

2,128 views
Skip to first unread message

davis

unread,
Aug 21, 2009, 1:28:49 PM8/21/09
to google-gin
Hi, I'm using gin, and I setup some classes like this:

bind(MyClass.class).asEagerSingleton();

..but it does not seem to work. When I launch my GWT app, only the
main class is loaded. None of the constructors from my other classes
are called and I need them to be since they listen for events.

Any idea on what might be going wrong?

Arthur Kalmenson

unread,
Aug 21, 2009, 1:45:14 PM8/21/09
to googl...@googlegroups.com
I've never used asEagerSingleton(), but why not try:

bind(MyClass.class).in(Singleton.class)

--
Arthur Kalmenson

Davis Ford

unread,
Aug 21, 2009, 2:03:17 PM8/21/09
to googl...@googlegroups.com
The difference (as I understand it), is that both methods provide a
singleton, but asEagerSingleton is supposed to instantiate (and then
compile) the class eagerly at bootstrap time, as opposed to lazily.

I don't want the lazy loading -- b/c I need the class available to
listen for events.
--
Zeno Consulting, Inc.
home: http://www.zenoconsulting.biz
blog: http://zenoconsulting.wikidot.com
p: 248.894.4922
f: 313.884.2977

Arthur Kalmenson

unread,
Aug 21, 2009, 2:14:45 PM8/21/09
to googl...@googlegroups.com
Ohh, I see what you mean. In that case, I have the same question :P.
We were looking into doing something like that too, but thought we'd
just have to inject everything that needs to be initialized into some
class.

--
Arthur Kalmenson

Peter Schmitt

unread,
Aug 23, 2009, 5:25:54 PM8/23/09
to googl...@googlegroups.com
Hi davis,

bind(MyClass.class).asEagerSingleton();

..but it does not seem to work.  When I launch my GWT app, only the
main class is loaded.  None of the constructors from my other classes
are called and I need them to be since they listen for events.

I assume you're creating the ginjector by calling GWT.create? Otherwise gin won't be initialized. If you do create the ginjector and you are still running into this problem, can you provide me with a more detailed description of the problem (test cases are best!)?

davis

unread,
Aug 25, 2009, 3:03:58 PM8/25/09
to google-gin
Hi Peter, yes, I am creating the ginjector with GWT.create. I managed
to get around the problem listed (somewhat), but I still am having
issues. Let me try to explain it with a simple example. My app is
more complex than this, but the principles are the same.

> I assume you're creating the ginjector by calling GWT.create? Otherwise gin
> won't be initialized. If you do create the ginjector and you are still
> running into this problem, can you provide me with a more detailed
> description of the problem (test cases are best!)?

I am using MVP pattern, so I have a number of presenter classes. Some
presenter classes need references to other presenters (i.e. panel
inside panel or tab panel, etc.).

Let's say I have a MainPresenter and I'm injecting its display via the
constructor, and injecting the 2 presenters it needs via method
injection.

public class MainPresenter {

private ChildPresenterA;
private ChildPresenterB;
private Display display;

@Inject
public void setChildPresenterA(ChildPresenterA p) {
this.childPresenterA = p;
}

@Inject
public void setChildPresenterB(ChildPresenterB p) {
this.childPresenterB = p;
}

@Inject
public MainPresenter(Display display) {
this.display = display;
display.showChildren(childPresenterA.getDisplay(),
childPresenterB.getDisplay());
}

I bind it like so:

public class UserModule extends AbstractGinModule {
bind(MainPresenter.class).asEagerSingleton();
bind(ChildPresenterA.class).asEagerSingleton();
bind(ChildPresenterB.class).asEagerSingleton();
}

public class User implements EntryPoint {

private final UserGinjector injector = GWT.create
(UserGinjector.class);
@Override
public void onModuleLoad() {
etc..
}
}

The problem is there seems to be race condition. Sometimes it
displays, but sometimes I get a NPE at the line:

display.showChildren(childPresenterA.getDisplay(),
childPresenterB.getDisplay());

because the childPresenterA and childPresenterB references have not
been injected yet. I suppose I could move them into the constructor,
but is there any alternative recommendation here?

Peter Schmitt

unread,
Aug 25, 2009, 3:30:34 PM8/25/09
to googl...@googlegroups.com
because the childPresenterA and childPresenterB references have not
been injected yet.  I suppose I could move them into the constructor,
but is there any alternative recommendation here?

This seems to be a recurring issue. :)

You cannot access member-injected fields in the constructor because members are not available for injection before the constructor has returned. In general it is best not to use member injection (there are a few valid use cases for it but they're rare and this is definitely not one of them). Just inject all you need into the constructor!

Peter

davis

unread,
Aug 25, 2009, 4:24:17 PM8/25/09
to google-gin
> This seems to be a recurring issue. :)
>
> You cannot access member-injected fields in the constructor because members
> are not available for injection before the constructor has returned. In
> general it is best not to use member injection (there are a few valid use
> cases for it but they're rare and this is definitely not one of them). Just
> inject all you need into the constructor!
>
> Peter

Hi Peter, I tried moving it to the constructor now, and I'm still
getting NPE. This is in hosted mode.

The ctor looks like this:

public UserHomePagePresenter(Display display, EventBus eventBus,
UserHeaderPresenter uhPresenter,
UserMenuPresenter umPresenter,
EmployeePresenter ePresenter,
DependentPresenter depPresenter) {
super(display, eventBus);
this.uhPresenter = uhPresenter;
this.umPresenter = umPresenter;
this.ePresenter = ePresenter;
this.depPresenter = depPresenter;
System.out.println("in UserHomePage constructor: ePresenter is
"+ePresenter);
System.out.println("in UserHomePage constructor: dPresenter is
"+depPresenter);
bind();
}

This is what it logs (ePresenter is null).

[INFO] in UserHomePage constructor: ePresenter is null
[INFO] in UserHomePage constructor: dPresenter is
com.example.client.presenter.DependentPresenter@4609b6

The bindings look like this:

@Override
protected void configure() {
bind(EventBus.class).to(DefaultEventBus.class).in(Singleton.class);
bind(PlaceManager.class).in(Singleton.class);
bind(CustomMessages.class).to(CustomMessagesImpl.class).in
(Singleton.class);

bind(LoginHeaderPresenter.Display.class).to(LoginHeaderPanel.class);
bind(LoginPresenter.Display.class).to(LoginPanel.class);
bind(CreateAccountPresenter.Display.class).to
(CreateAccountPanel.class);
bind(UserHeaderPresenter.Display.class).to(UserHeaderPanel.class);
bind(UserMenuPresenter.Display.class).to(UserMenuPanel.class);
bind(UserHomePagePresenter.Display.class).to
(UserHomePagePanel.class);
bind(EmployeePresenter.Display.class).to(EmployeePanel.class);
bind(DependentPresenter.Display.class).to(DependentPanel.class);

bind(UserHomePagePresenter.class).asEagerSingleton();
bind(EmployeePresenter.class).asEagerSingleton();
bind(DependentPresenter.class).asEagerSingleton();
bind(LoginPresenter.class).asEagerSingleton();
bind(CreateAccountPresenter.class).asEagerSingleton();
bind(UserHeaderPresenter.class).asEagerSingleton();
bind(LoginHeaderPresenter.class).asEagerSingleton();
}

I am not seeing why that particular class is injected as null. The
other classes are all the same - configured the same, and they are
injected just fine.

Peter Schmitt

unread,
Aug 25, 2009, 4:50:37 PM8/25/09
to googl...@googlegroups.com

Something that really confused me here is that you have two bindings for each type - the implementation binding and the asEagerSingleton binding. I think the second one overrides the first one. As it turns out there is a discrepancy between our API and Guice's API - you should be able to do this in one binding and you can't in Gin. I'll fix this bug and send in a fix, and I'd appreciate it if you could then patch the change, merge your bindings and tell me whether that fixes the issue.

 

Davis Ford

unread,
Aug 25, 2009, 4:55:06 PM8/25/09
to google-gin
Maybe I'm just not understanding how the code is bootstrapped. Here
is my onModuleLoad():

public void onModuleLoad() {
injector.getEventBus().fireEvent(new PlaceRequestEvent(new
PlaceRequest(LoginPresenter.PLACE)));
injector.getPlaceManager().fireCurrentPlace();
History.newItem(LoginPresenter.PLACE.toString());
}

So, instead of manipulating the root panel directly, I just fire an
event and the LoginPresenter will end up getting that, and he'll
manipulate the view. The problem is that the event fires, and the
LoginPresenter gets it, and *now* he gets a NPE on this method:

protected void onPlaceRequest(PlaceRequest request) {
System.out.println("LOGIN PLACE REQUEST");
RootPanel.get("header").clear();
RootPanel.get("header").add(lhPresenter.getDisplay().asWidget());
<-- NPE
RootPanel.get("button-row").clear();
RootPanel.get("content").clear();
RootPanel.get("content").add(display.asWidget());
}

His ctor looks like this:

@Inject
public LoginPresenter(
LoginPresenter.Display display,
EventBus eventBus,
LoginHeaderPresenter lhPresenter) {
super(display, eventBus);
this.lhPresenter = lhPresenter;
bind();
}

When is it safe to assume that all the classes have been constructed
and their dependencies are injected proper? I'm either making
incorrect assumptions about the way it should behave, or perhaps there
is a bug here somewhere?

Davis Ford

unread,
Aug 25, 2009, 4:56:50 PM8/25/09
to googl...@googlegroups.com
No, the first one is actually an inner interface. It looks like this:

class Presenter {

public interface Display {

}
}

So, I have a binding for the Presenter.Display, and then a binding (as
eager singleton) for the presenter :

bind(EmployeePresenter.Display.class).to(EmployeePanel.class);
bind(EmployeePresenter.class).asEagerSingleton();

Peter Schmitt

unread,
Aug 25, 2009, 5:37:29 PM8/25/09
to googl...@googlegroups.com
Alright, I don't think your problem is related to asEagerSingleton(). I assume that all your presenters are actual classes (so they should easily work with GWT.create, which is what will be used to instantiate them). I don't see why they would be null.

The other thing that I'm confused about is why you are using asEagerSingleton. I can see two possible scenarios:
  • Do all presenters get injected into the event bus so they can receive notifications? In this case the presenters are all created latest at the point where you call ginjector.getEventBus() (i.e. before you ever send an event). In this case bind all presenters with "in(Singleton.class)" and see if that helps.
  • There might be presenters that initiate some UI in their constructor - such as RootPanel.get("foo").add(myView). In these cases, if you want the UI instantiated immediately when the ginjector gets created, and you also don't want to call a getMyPresenter() on the ginjector in your onModuleLoad, using asEagerSingleton is appropriate.
So, for your specific problem: Try to simplify the issue until you can pin exactly what causes the error. If it's in your code - great, fix it! :) If it's something related to our stuff, let me know, as specific as possible and I will see what I can do.

Hope this helps!

Peter

Davis Ford

unread,
Aug 25, 2009, 10:31:03 PM8/25/09
to googl...@googlegroups.com
Hi Peter,

All presenters get an instance of the eventbus injected via the
constructor, and they register a specific type of event handler with
it. This is how I am managing place/history.

Example:

public class Presenter {
public interface Display {
/* show a model object */
void showSomething(Model model);
}

@Inject
public Presenter(Presenter.Display display, EventBus bus) {
this.display = display;
this.bus = bus;
bus.registerEventHandler(PlaceRequestEvent.getType(), new
PlaceRequestHandler() { ... });
}

public void onPlace(PlaceRequestEvent evt) {
if(evt.getPlace().equals(me) {
// change the view
}
}
}

The bindings would thus be:

bind(EventBus.class).to(DefaultEventBus.class).in(Singleton.class);
bind(Presenter.Display.class).to(PresenterView.class);
bind(Presenter.class).asEagerSingleton();

The LoginPresenter also has a LoginHeaderPresenter in the ctor:

public LoginPresenter(Display display, EventBus bus, LoginPresenter
presenter) { }

when the event fires from onModuleLoad( ) -->

injector.getEventBus().fireEvent(new PlaceRequestEvent(new
PlaceRequest(LoginPresenter.PLACE)));

The LoginPresenter will get that event and try to manipulate the root panel:

protected void onPlaceRequest(PlaceRequest request) {
RootPanel.get("header").clear();
RootPanel.get("header").add(presenter.getDisplay().asWidget());


RootPanel.get("content").clear();
RootPanel.get("content").add(display.asWidget());
}

But when that method is called -- the LoginPresenter's reference to
LoginHeaderPresenter (which should have been injected in ctor) is
null, and results in NPE.

I will see what I can do about creating a simple project example that
demonstrates this, but I'd love to hear any ideas on what might be
going wrong. I will change my bindings to .in(Singleton.class) for
presenters to see if it changes.

Davis Ford

unread,
Aug 25, 2009, 10:35:18 PM8/25/09
to googl...@googlegroups.com
Sorry, typo:

LoginPresenter ctor looks like this:

@Inject
public LoginPresenter(
LoginPresenter.Display display,
EventBus eventBus,
LoginHeaderPresenter lhPresenter) {
super(display, eventBus);
this.lhPresenter = lhPresenter;
bind();
}

super class registers place request event handler for us.

Ginjector fires the event in onModuleLoad:

injector.getEventBus().fireEvent(new PlaceRequestEvent(new
PlaceRequest(LoginPresenter.PLACE)));

which ends up calling back into LoginPresenter#onPlaceRequest( )

which tries to de-reference lhPresenter (injected via ctor), but this is null...

Hope that is more clear?

On Tue, Aug 25, 2009 at 10:31 PM, Davis
Ford<davi...@zenoconsulting.biz> wrote:
> Hi Peter,
>

Peter Schmitt

unread,
Aug 27, 2009, 12:28:20 PM8/27/09
to googl...@googlegroups.com
Hi Davis,

I spent another while thinking about your problem and it seems to me like Gin's initialization for eager singletons is somewhat broken. :) Here's my theory (I'll still need to check this):

Currently when we encounter an eager singleton, we add a field to the ginjector implementation, like so:

private MyEagerSingleton myEagerSingleton = createMyEagerSingleton();

We also add a method that will provide the eager singleton to any other method:

private MyEagerSingleton getMyEagerSingleton() {
  return this.myEagerSingleton;
}

This works fine as long as all variables that MyEagerSingleton requires for initialization are not eager singletons. However, if an eager singleton depends on another eager singleton, the following situation can occur (non-deterministic, this depends on the order in which these are written to java source code):
  1. myEagerSingleton field gets initialized when instantiating the Ginjector.
  2. createMyEagerSingleton is called.
  3. createMyEagerSingleton calls getOtherEagerSingleton to retrieve a dependency.
  4. getOtherEagerSingleton returns the value of this.otherEagerSingleton (this is the vulnerable step).
  5. createMyEagerSingelton returns.
  6. otherEagerSingleton gets initialized by the Ginjector initialization by calling its create method.
If things happen in this order - which they can - then getOtherEagerSingleton will return null at the time that the vulnerable step is executed because otherEagerSingleton hasn't been initialized yet.

This problem is relatively simple to fix and I'll send in a patch for it in a short while. Davis, will you be able to patch your local gin build and try your code with my changes to see whether things are fixed?

Peter

Peter Schmitt

unread,
Aug 27, 2009, 1:05:42 PM8/27/09
to googl...@googlegroups.com
Patch can be reviewed and found here: http://codereview.appspot.com/110095

Davis Ford

unread,
Aug 27, 2009, 1:11:19 PM8/27/09
to googl...@googlegroups.com
Peter -- your explanation makes sense. Indeed, it seemed as if I
moved the binding order around, I would get different null injections.

I'm under an enormous time-crunch right now, and as such, I had to
back gin out temporarily until I could resolve this. I basically just
instantiate all my presenters/views/service in the onmodule load
manually.

I'm still setup to introduce gin back in and try when it calms done
somewhat. It may be a week or two, but I'll come back and try the
patch and report my results.

Thanks for following up on this!

Regards,
Davis
Reply all
Reply to author
Forward
0 new messages