12 Replies Latest reply on Mar 20, 2012 6:30 AM by michael_gronau

    Multiple cache listener notifications when creating a new entry

    xcoulon
      Hello,
      First of all, sorry if this question was already asked, I'm new to Infinispan. I'm giving it a try (4.0.0.CR3 for now) as I saw Manik's presentation at Devoxx'09 last november.
      In the context of a demo I'd like to perform, I want to add items to a first cache (for example, a pdf document with metadata such as author and title), and with the help of cache listeners, I want to generate a thumbnail of the first page of the pdf doc and store it in another infinispan cache with the REST API (just to look at several features of the product).
      Yet, I'm stuck on a somehow weird behavior: when I add a new item to the cache, my cache listener will received 2 cacheEntryCreatedEvents and 2 cacheEntryModifiedEvents ! Furthermore, in the received event, the key is set, but not the value, which is sad I think, since in my particular case, this is what I'm interested in too...
      I wrote a simple TestNG class to show the behavior I described. In the current version, the test succeeds because the expected values are 2 and 2, but I'd expect 1 and 0:

      @Test(groups = "unit", testName = "notifications.cachelistener.CacheListenerTest")

      public class CacheListenerTest {


      private Cache<String, Object> cache;

      private MockCacheListener listener;


      @BeforeMethod(alwaysRun = true)

      public void setUp() throws Exception {

      Configuration c = new Configuration();

      c.setCacheMode(Configuration.CacheMode.LOCAL);

      c.setIsolationLevel(IsolationLevel.REPEATABLE_READ);

      c.setTransactionManagerLookupClass(DummyTransactionManagerLookup.class

      .getName());


      CacheManager cm = TestCacheManagerFactory.createCacheManager(c);


      cache = cm.getCache();

      listener = new MockCacheListener();

      cache.addListener(listener);

      }


      @AfterMethod(alwaysRun = true)

      public void tearDown() throws Exception {

      TestingUtil.killCaches(cache);

      }


      @AfterTest

      public void destroyManager() {

      TestingUtil.killCacheManagers(cache.getCacheManager());

      }


      public void testCreateEntry() throws IOException {

      cache.put("key", new String("Sample item"));

      Assert.assertEquals(listener.cacheEntryCreated, 2);

      Assert.assertEquals(listener.cacheEntryModified, 2);

      }


      }

      In its current form, the MockCacheListener class is very basic. It just counts the method invocations:

      @Listener

      public class MockCacheListener {


      public boolean pre = false;

      public int cacheEntryCreated = 0;

      public int cacheEntryVisited = 0;

      public int cacheEntryPassivated = 0;

      public int cacheEntryLoaded = 0;

      public int cacheEntryInvalidated = 0;

      public int cacheEntryEvicted = 0;

      public int cacheEntryActivated = 0;

      public int cacheEntryRemoved = 0;

      public int cacheEntryModified = 0;


      @CacheEntryCreated

      public void cacheEntryCreated(CacheEntryCreatedEvent event) {

      pre = event.isPre();

      cacheEntryCreated++;

      }


      @CacheEntryModified

      public void cacheEntryModified(CacheEntryModifiedEvent event) {

      pre = event.isPre();

      cacheEntryModified++;

      }


      @CacheEntryRemoved

      public void cacheEntryRemoved(CacheEntryRemovedEvent event) {

      pre = event.isPre();

      cacheEntryRemoved++;

      }


      @CacheEntryActivated

      public void cacheEntryActivated(CacheEntryActivatedEvent event) {

      pre = event.isPre();

      cacheEntryActivated++;

      }


      @CacheEntryEvicted

      public void cacheEntryEvicted(CacheEntryEvictedEvent event) {

      pre = event.isPre();

      cacheEntryEvicted++;

      }


      @CacheEntryInvalidated

      public void cacheEntryInvalidated(CacheEntryInvalidatedEvent event) {

      pre = event.isPre();

      cacheEntryInvalidated++;

      }


      @CacheEntryLoaded

      public void cacheEntryLoaded(CacheEntryLoadedEvent event) {

      pre = event.isPre();

      cacheEntryLoaded++;

      }


      @CacheEntryPassivated

      public void cacheEntryPassivated(CacheEntryPassivatedEvent event) {

      pre = event.isPre();

      cacheEntryPassivated++;

      }


      @CacheEntryVisited

      public void cacheEntryVisited(CacheEntryVisitedEvent event) {

      pre = event.isPre();

      cacheEntryVisited++;

      }


      }

       


      Thank you in advance.
      Regards,
      Xavier
        • 1. Re: Multiple cache listener notifications when creating a new entry
          manik

          Your listener will be notified before and after every event - hence the 2 callbacks you see.  If you are only interested in the notification prior to the event, do something like:

           

          @CacheEntryCreated
          public void myCallBack(Event e) {
            if (e.isPre()) {
              // do something
            } else {
              // ignore
            }
          }
          

           

          and if you care about being notified after the event, then flip the logic on the if block above.

           

          Also, behaviour on the value passed in depends on whether the callback happens before or after the event.  In the case of @CacheEntryModified, the value is the old value prior to modification if event.isPre() is true, and the value after modification if event.isPre() is false.  If this is a new entry, then the value prior to modification would be null.

           

          Granted, the Javadocs on these annotations and events could be better. 

           

          Cheers

          Manik

          • 2. Re: Multiple cache listener notifications when creating a new entry
            xcoulon

            Hello Manik,

             

            Thank you for your ansewer. It's clear now, I need to check if the received event was sent before or after the cache entry was created.

            Still, I don't understand why 2 extra "cacheModifiedEvent"s are thrown...

            At the end, I get 4 events in all.. How can I know from the two "cacheModifiedEvent"s that they occurred because of a new entry insertion in the cache ?

            Remember my example, I can use the isPre() method and check whether the value is null or not, and decide to generate a thumbnail for my pdf content... but I don't want my app doing it twice every time a new doc in inserted.

             

            Regards,

            Xavier

            • 3. Re: Multiple cache listener notifications when creating a new entry
              xcoulon

              Hello again,

               

              I looked deeper at the Infinispan code (running in debug mode to understand the chain of interceptors that visit the PutKeyCommand - it's great !)

              From what i've seen and understood, 2 cacheCreatedEvents are sent (pre = true then false), but both of these events have a value set to null.

              Then, two other cacheEntryModifiedEvents are sent (pre = true then false). The first event contains a null value and the second one contains the expected document value, as you wrote earlier.

               

              At the end of the day (and indeed, it's almost 11pm here now - hahaha), it seems that I don't need to catch the cacheEntryCreatedEvents at all.

              So my question is : in what particular use cases are they useful ? Are these events used to "prepare" the datagrid nodes by locking the entry with the given key ?

               

              Thank you in advance

              Regards,

              Xavier

              • 4. Re: Multiple cache listener notifications when creating a new entry
                manik

                Hmm, I wouldn't expect so many calls - proved in CacheNotifierTest too.

                 

                   private void expectSingleEntryCreated(Object key, Object value) {
                      mockNotifier.notifyCacheEntryCreated(eq(key), eq(true), isA(InvocationContext.class));
                      expectLastCall().once();
                      mockNotifier.notifyCacheEntryCreated(eq(key), eq(false), isA(InvocationContext.class));
                      expectLastCall().once();
                      mockNotifier.notifyCacheEntryModified(eq(key), isNull(), eq(true), isA(InvocationContext.class));
                      expectLastCall().once();
                      mockNotifier.notifyCacheEntryModified(eq(key), eq(value), eq(false), isA(InvocationContext.class));
                      expectLastCall().once();
                   }
                
                   public void testCreation() throws Exception {
                      expectSingleEntryCreated("key", "value");
                      replay(mockNotifier);
                      cache.put("key", "value");
                      verify(mockNotifier);
                   }
                
                 
                
                • 5. Re: Multiple cache listener notifications when creating a new entry
                  xcoulon

                  Hi again Manik,

                   

                  Thanks for the answer.

                   

                  Well, I've downloaded the source code from the public SVN, based on tag 4.0.0.CR3

                  Sorry, I'm not used of EasyMock, I usually work with Mockito, so I simply wrote another basic Test Class, called "AnotherCacheNotifierTest", with the following code:

                   

                   

                  @Test(groups = "functional", testName = "notifications.cachelistener.AnotherCacheNotifierTest")

                  public class AnotherCacheNotifierTest {

                  private Cache<Object, Object> cache;


                  @BeforeMethod(alwaysRun = true)

                  public void setUp() throws Exception {

                  Configuration c = new Configuration();

                  c.setCacheMode(Configuration.CacheMode.LOCAL);

                  c.setIsolationLevel(IsolationLevel.REPEATABLE_READ);

                  c.setTransactionManagerLookupClass(DummyTransactionManagerLookup.class

                  .getName());

                  CacheManager cm = TestCacheManagerFactory.createCacheManager(c);

                  cache = cm.getCache();

                   

                  }


                  @AfterTest

                  public void destroyManager() {

                  TestingUtil.killCacheManagers(cache.getCacheManager());

                  }


                  public void testCreationWithAnotherMockListener() throws Exception {

                  AnotherMockCacheListener listener = new AnotherMockCacheListener();

                  cache.addListener(listener);

                  cache.put("key", new String("Mock!"));

                  Assert.assertEquals(listener.events.size(), 4);

                  }

                  }

                  The test uses AnotherMockCacheListener as a listener for the cache under test. The code is very basic too (any event is added to a list of events, so I could check the received events in debug mode...) :

                  @Listener

                  public class AnotherMockCacheListener {


                  List<CacheEntryEvent> events = new ArrayList<CacheEntryEvent>(4);


                  @CacheEntryCreated

                  @CacheEntryModified

                  @CacheEntryRemoved

                  @CacheEntryActivated

                  @CacheEntryEvicted

                  @CacheEntryInvalidated

                  @CacheEntryLoaded

                  @CacheEntryPassivated

                  @CacheEntryVisited

                  public void onCacheEntryEvent(CacheEntryEvent event) {

                  events.add(event);

                  }


                  }

                   

                  The result of the test is : 4 events received, 2 cacheEntryCreatedEvents followed by 2 cacheEntryModifiedEvents

                   

                  Do you obtain the same results ? (I've put both classes as attachments)

                   

                  Thank you in advance.

                  Regards,

                  Xavier

                  • 6. Re: Multiple cache listener notifications when creating a new entry
                    manik

                    Yes, what you see is expected.  As per my test:

                     

                       private void expectSingleEntryCreated(Object key, Object value) {
                          mockNotifier.notifyCacheEntryCreated(eq(key), eq(true), isA(InvocationContext.class));
                          expectLastCall().once();
                          mockNotifier.notifyCacheEntryCreated(eq(key), eq(false), isA(InvocationContext.class));
                          expectLastCall().once();
                          mockNotifier.notifyCacheEntryModified(eq(key), isNull(), eq(true), isA(InvocationContext.class));
                          expectLastCall().once();
                          mockNotifier.notifyCacheEntryModified(eq(key), eq(value), eq(false), isA(InvocationContext.class));
                          expectLastCall().once();
                       }
                    
                     
                    

                     

                    I expect 2 CacheEntryCreated callbacks (once with pre = true and once with pre = false) and 2 CacheEntryModified callbacks (once with pre = true and once with pre = false).

                     

                    If you don't specifically care about the entry being created and only care about the modification, change your listener method so it is just annotated with @CacheEntryModified and not @CacheEntryCreated.

                     

                    Cheers

                    Manik

                    • 7. Re: Multiple cache listener notifications when creating a new entry
                      xcoulon

                      Hello Manik,

                       

                      Sorry, I guess I misunderstood your previous reply. We both agree that 4 events are sent, which is what surprised me at first instance.

                      In my personnal use case, I'll only listen to cacheEntryModifiedEvent, which is fine, but I'm just wondering why so many events, and when should we care about cacheCreatedEvents, especially if in both case, the value is null ? I'm just curious and want to understand the design ;-)

                       

                       

                      Thanks.

                      Regards,

                      Xavier

                      • 8. Re: Multiple cache listener notifications when creating a new entry
                        manik
                        Well, you have registered for all notifications.    I don't see that as common.  You would typically only register for the events you are interested in.  For example it may be useful for people to want to be notified every time a new entry is created (as opposed to an existing one modified).
                        • 9. Re: Multiple cache listener notifications when creating a new entry
                          xcoulon

                          Hello,

                           

                          Of course, I understand that I'll be notified for events my listener is registered ;-)

                          My question is why does the notifier sends cacheEntryModifiedEvents when I create a new entry in the cache ?

                          Shouldn't it just/only send cacheEntryCreatedEvents ?

                           

                          Regards,

                          Xavier

                          • 10. Re: Multiple cache listener notifications when creating a new entry
                            jlafer

                            Hello,

                             

                            I'm new to Infinispan, using 5.1.1.FINAL. I'm seeing the same thing and have the same question: why does CacheEntryCreatedEvent.getKey() return a key that refers to a null cache entry in both notifications? On the surface, it appears that the entry is not yet in the cache at that point. I could understand that (perhaps) when isPre() == true, but why would a call to CacheEntryCreatedEvent.getCache().get( CacheEntryCreatedEvent.getKey() ) return null, even when isPre == false? I see almost no value in CacheEntryCreatedEvent if one cannot access the cache entry. And if I use the CacheEntryModified notification, I won't really know whether the entry has been newly created or modified, which seems problematic for some of my use cases. I'm sure that, being new, I'm missing something important about Infinispan that I should understand.

                             

                            Thanks for any insights.

                             

                            Regards,

                            John

                             

                            • 11. Re: Multiple cache listener notifications when creating a new entry
                              galder.zamarreno

                              @jlafer, @Xavier, I agree that our notification system is a bit funky when it comes to create and modified cache entries. I've suffered these problems myself as well. In the near future, we're gonna be standarizing on JSR-107 like listeners, so I wouldn't get too hang up on how things work at the moment.

                               

                              @jlafer, @Xavier, it seems to me that the only way to differentiate between a cache entry created or modified is to, when isPre=true, check the value of CacheEntryModifiedEvent. If null, cache entry is being created. If not null, cache entry being modified. Unfortunately it's not easy to make that distinction when isPre=false cos value comes with the new value. Any other method requires for listener impls to maintain some kind of state.

                              • 12. Re: Multiple cache listener notifications when creating a new entry
                                michael_gronau

                                Hi,

                                I'm facing the exact same problem, that the events never deliver the modified value. I'm using the cache in combination with the tree api and the notification of a modification comes twice for pre=true and twice for pre=false, but the value is always null?