11 Replies Latest reply on Jan 31, 2018 2:06 PM by D N

    Multiple instances of @Singleton

    cpuffalt Newbie

      All,

       

      I'm seeing multiple (sometimes as many as 9!) instances of a @Singleton bean being created by JBoss AS v6.0.0.  JBoss seems to elect one of these instances eventually as the "singleton" as only one of them ever has their @PreDestroy method called and only one of them ever gets injected into other beans which reference it.

       

      Under what circumstances will JBoss create multiple instances of a @Singleton?  The only thing I can think of is that my constructor takes some time to complete.  Does JBoss get impatient and attempt to create multiple instances in this case?  How do I disable this behavior?

       

      Note: This is not in a cluster, just a simple, single instance of stock JBoss.

       

      Thanks,

      Corey

        • 1. Multiple instances of @Singleton
          jaikiran pai Master

          Please post the relevant code and the logs which shows this behaviour. Also the client code which accesses the singleton.

          • 2. Re: Multiple instances of @Singleton
            cpuffalt Newbie

            The @Singleton class:

             

            {code}

            @Singleton @ConcurrencyManagement(BEAN)

            public class ElasticSearchBean

            {

              private static final Logger log = LoggerFactory.getLogger(ElasticSearchBean.class);

              private static final String cluster = "dev-calgary";

              private static final AtomicInteger instanceCounter = new AtomicInteger(0);

             

              private final int instance = instanceCounter.incrementAndGet();

              private final Node node;

              private final Client client;

             

              public ElasticSearchBean()

              {

                log.info("******************************************");

                log.info("Connecting to our elastic search cluster! " + instance);

                log.info("******************************************");

                try

                {

                  node = nodeBuilder().data(false).clusterName(cluster).node();

                  client = node.client();

                }

                catch(RuntimeException e)

                {

                  log.error("Exception initializing client!", e);

                  throw e;

                }

              }

              @SuppressWarnings("unused") @PostConstruct

              private void init()

              {

                log.info("******************************************");

                log.info("Connected! " + instance);

                log.info("******************************************");

              }

             

              @SuppressWarnings("unused") @PreDestroy

              private void shutdown()

              {

                log.info("******************************************");

                log.info("Shutting down our search node and client! " + instance);

                log.info("******************************************");

             

                client.close();

                node.close();

              }

              public Client client()

              {

                log.info("Returning client instance from " + instance);

                return client;

              }

            }

            {code}

             

            The MDB that references it:

             

             

            {code}

            @MessageDriven(

                activationConfig =

                {

                  @ActivationConfigProperty(

                    propertyName = "destination", propertyValue = "/queue/indexQueue"),

                  @ActivationConfigProperty(

                    propertyName = "destinationType", propertyValue = "javax.jms.Queue"),

                  @ActivationConfigProperty(

                    propertyName = "subscriptionDurability", propertyValue = "Durable"),

                  @ActivationConfigProperty(

                    propertyName = "clientId", propertyValue = "indexer"),

                  @ActivationConfigProperty(

                    propertyName = "subscriptionName", propertyValue = "indexerSub")

                })

            public class IndexerMsgBean implements MessageListener

            {

              private static final Logger log = LoggerFactory.getLogger(IndexerMsgBean.class);

             

              @Inject

              private ElasticSearchBean search;

             

              @Override

              public void onMessage(Message msg)

              {

                log.info("******************************************");

                log.info("Got a message! " + msg);

                log.info("******************************************");

             

                if (msg instanceof TextMessage)

                {

                  TextMessage txtmsg = (TextMessage)msg;

                  Client client = search.client();

                  try

                  {

                    client.prepareIndex("messages", "msg", "1")

                      .setSource(jsonBuilder()

                        .startObject()

                          .field("msg").value(txtmsg.getText())

                        .endObject())

                      .execute()

                      .actionGet();

                  }

                  catch (ElasticSearchException e)

                  {

                    e.printStackTrace();

                  }

                  catch (IOException e)

                  {

                    e.printStackTrace();

                  }

                  catch (JMSException e)

                  {

                    e.printStackTrace();

                  }

                }

              }

            }

             

            {code}

             

            Relevant log snippet:

             

            00:25:19,001 INFO  [org.jboss.ejb3.nointerface.impl.jndi.AbstractNoInterfaceViewBinder] Binding the following entry in Global JNDI for bean:StartupBean

             

             

                      StartupBean/no-interface -> EJB3.1 no-interface view

             

             

            00:25:19,015 INFO  [org.geekden.jee6.bean.ElasticSearchBean] ******************************************

            00:25:19,016 INFO  [org.geekden.jee6.bean.ElasticSearchBean] Connecting to our elastic search cluster! 1

            00:25:19,017 INFO  [org.geekden.jee6.bean.ElasticSearchBean] ******************************************

            00:25:19,575 INFO  [org.geekden.jee6.bean.StartupBean] ******************************************

            00:25:19,575 INFO  [org.geekden.jee6.bean.StartupBean] Sending a message now!

            00:25:19,577 INFO  [org.geekden.jee6.bean.StartupBean] ******************************************

            00:25:19,813 INFO  [org.geekden.jee6.bean.ElasticSearchBean] ******************************************

            00:25:19,813 INFO  [org.geekden.jee6.bean.ElasticSearchBean] Connecting to our elastic search cluster! 3

            00:25:19,814 INFO  [org.geekden.jee6.bean.ElasticSearchBean] ******************************************

            00:25:19,808 INFO  [org.geekden.jee6.bean.ElasticSearchBean] ******************************************

            00:25:19,818 INFO  [org.geekden.jee6.bean.ElasticSearchBean] Connecting to our elastic search cluster! 2

            00:25:19,818 INFO  [org.geekden.jee6.bean.ElasticSearchBean] ******************************************

             

             

             

             

            Sorry about the formatting.  For some reason my {code} tags don't seem to be working.

            • 3. Re: Multiple instances of @Singleton
              Carlo de Wolf Master

              You are seeing multiple instances of the class, not the EJB.

               

              A no-interface view is an extension of the bean class, so each time a no-interface view is looked up you'll get a new instance of the class. The EJB lifecycle itself is handled through the appropriate lifecycle callbacks (post-construct & pre-destroy). You must not rely on the constructor.

              • 4. Re: Multiple instances of @Singleton
                cpuffalt Newbie

                Carlo,

                 

                Thanks for the explanation.  So by adding an interface I might be able to work around this and keep my variables marked as final, or even then it would not be recommended? 

                 

                Thanks again,

                Corey

                • 5. Multiple instances of @Singleton
                  Elias Ross Master

                  Don't use a constructor and don't assign member variables in the class.

                   

                  If you want, delegate the construction to another class.

                  • 6. Re: Multiple instances of @Singleton
                    cpuffalt Newbie

                    Ok, here's what my code looks like now:

                    {code}

                    @Singleton @ConcurrencyManagement(BEAN)

                    public class ElasticSearchBean

                    {

                      private static final Logger log = LoggerFactory.getLogger(ElasticSearchBean.class);

                      private static final String cluster = "dev-calgary";

                     

                      private volatile Node node;

                      private volatile Client client;

                     

                      @SuppressWarnings("unused") @PostConstruct

                      private void init()

                      {

                        log.info("******************************************");

                        log.info("Connecting to our elastic search cluster!");

                        log.info("******************************************");

                        try

                        {

                          node = nodeBuilder().data(false).clusterName(cluster).node();

                          client = node.client();

                        }

                        catch(RuntimeException e)

                        {

                          log.error("Exception initializing client!", e);

                          throw e;

                        }

                        log.info("******************************************");

                        log.info("Connected!");

                        log.info("******************************************");

                      }

                     

                      @SuppressWarnings("unused") @PreDestroy

                      private void shutdown()

                      {

                        log.info("******************************************");

                        log.info("Shutting down our search node and client!");

                        log.info("******************************************");

                     

                        client.close();

                        node.close();

                      }

                     

                      public Client client()

                      {

                        log.info("******************************************");

                        log.info("Returning client instance.");

                        log.info("******************************************");

                        return client;

                      }

                    }

                    {code}

                     

                     

                    Anyone see any issues with that?  It works fine so far in my testing.  Alternatively I could use container-managed concurrency with a @Lock(READ) on my client() method but this way there's no contention at all.  Any holes in my theory?

                     

                    Thanks again for your help.  It still bothers me that I can't use "final" to indicate that my instance variables are immutable but I guess there's no way around that today (maybe EJB8?)

                     

                    Regards,

                    Corey

                    • 7. Re: Multiple instances of @Singleton
                      jaikiran pai Master

                      Either I am not reading the question correctly or I'm missing something. Why are there multiple instances of that @Singleton bean class being created as a result of lookup? And if that's what is happening, I don't see how a state can be maintained in a singleton bean (which is expected to maintain states across calls for the lifetime of the application). This looks like a bug to me.

                       

                      Corey, by the way I see you use @Inject in the MDB to get hold of the singleton bean. Can you replace it to use @EJB instead:

                      public class IndexerMsgBean implements MessageListener
                      {
                        private static final Logger log = LoggerFactory.getLogger(IndexerMsgBean.class);
                      
                        @javax.ejb.EJB
                        private ElasticSearchBean search;
                      
                      

                       

                      I haven't looked at how @Inject is implemented so I don't know if that is a problem. See if using @EJB prevents the multiple instance creation.

                      • 8. Re: Multiple instances of @Singleton
                        cpuffalt Newbie

                        Jaikiran,

                         

                        I switched to using the @EJB annotation as you suggested but I still see the bean being constructed multiple (though possibly, fewer) times.  I haven't read the spec myself to see if this should be considered a bug or not.  Clearly Carlo & Elias feel this is normal and that all initialization has to happen in the @PostConstruct method rather than the constructor.  If that's the case then I think it's unfortunate that the EJB spec is forcing developers to use coding patterns different from idiomatic Java (although EJB 3.1 is a vast improvement over the older incarnations of course).

                         

                        Regards,

                        Corey

                        • 9. Re: Multiple instances of @Singleton
                          Carlo de Wolf Master

                          Ah my favorite pasttime: quoting spec.

                          EJB 3.1 FR 3.4.4 Session Bean's No-Interface View:

                           

                          The developer of an enterprise bean that exposes a no-interface view must not make any assumptions

                          about the number of times the bean class no-arg constructor will be called. For example, it is possible

                          that the acquisition of a client reference to the no-interface view will result in the invocation of the-

                          bean-class constructor. It is recommended that the bean developer place component initialization logic

                          in a @PostConstruct method instead of the bean class no-arg constructor.

                          • 10. Re: Multiple instances of @Singleton
                            jaikiran pai Master

                            Ah yes! I now get it and understand what I missed. I clearly did not fully interpret what you said earlier:

                             

                            A no-interface view is an extension of the bean class, so each time a no-interface view is looked up you'll get a new instance of the class.

                            Makes sense now

                            • 11. Re: Multiple instances of @Singleton
                              D N Newbie

                              Thank you! In my case that was the problem all along...

                              Can you replace it to use @EJB instead:

                              Wrong import... So for all the others:

                               

                              • javax.ejb.Singleton goes together with @javax.ejb.EJB.
                              • javax.inject.Singleton goest together with javax.inject.Inject