Hi Alex! You can check if the preferred ASF Jira name is free and make a
request at https://selfserve.apache.org/jira-account.html
One of PMC members will approve right after this.

Cheers,
Kseniya

On Fri, Mar 24, 2023 at 10:24 PM Prigoreanu, Alexandru <
prigoreanu.alexan...@anteash.com> wrote:

> hello everyone! please could you create a profile for me on jira so i can
> submit this issue there? thank you!
>
> On Wed, Mar 1, 2023 at 5:07 AM Prigoreanu, Alexandru <
> prigoreanu.alexan...@anteash.com> wrote:
>
> > Hello everyone! thank you for your time.
> >
> > - we have an entity PlaceImpl annotated @Cache(usage =
> > CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
> > - we use Ignite's L2 Hibernate cache implementation through the
> > application property
> >
> spring.jpa.properties.hibernate.cache.region.factory_class=org.apache.ignite.cache.hibernate.HibernateRegionFactory
> > - we use ignite 2.14.0, ignite-hibernate-ext 5.3.0, hibernate 5.4.33
> > - we have a complex transaction that creates a new PlaceImpl, saves it in
> > the database, and updates it.
> > - when the PlaceImpl is returned from the level 2 cache it does not
> > contain the latest data for some fields. let's say we expect that
> > PlaceImpl.description is not null while PlaceImpl.description is null.
> >
> > after a bit of debugging we got the following data:
> > - during the transaction the changes to PlaceImpl are flushed twice or
> > more: one in the middle of the transaction and the second one before the
> > transaction is committed.
> > - given the CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, this results
> in
> > 2 EntityUpdateAction for our PlaceImpl that was created (we don't talk
> > about inserts here) added to the collection of processes in
> > AfterTransactionCompletionProcessQueue.processes
> > - after the transaction is completed, on the processes of the
> > aforementioned list hibernate invokes doAfterTransactionCompletion
> >
> > public void afterTransactionCompletion(boolean success) {
> >   while ( !processes.isEmpty() ) {
> >     try {
> >       processes.poll().doAfterTransactionCompletion( success, session );
> >     }
> >
> > - the first EntityUpdateAction contains an incomplete PlaceImpl that does
> > not yet have all the fields set
> > - the second EntityUpdateAction contains the complete PlaceImpl with all
> > the fields set
> >
> > - the first EntityUpdateAction.doAfterTransactionCompletion gets to
> > execute HibernateNonStrictAccessStrategy.afterUpdate: here ctx is not
> null
> > and the if ctx != null branch is executed and the incomplete PlaceImpl is
> > put in the level 2 cache
> >
> >  @Override public boolean afterUpdate(Object key, Object val) {
> >      WriteContext ctx = writeCtx.get();
> >
> >
> >      if (log.isDebugEnabled())
> >          log.debug("Put after update [cache=" + cache.name() + ", key="
> +
> > key + ", val=" + val + ']');
> >
> >
> >      if (ctx != null) {
> >          ctx.updated(key, val);
> >
> >
> >          unlock(key);
> >
> >
> >          return true;
> >      }
> >
> >
> >      return false;
> >  }
> >
> > which invokes also unlock(key);
> >
> >  @Override public void unlock(Object key) {
> >      try {
> >          WriteContext ctx = writeCtx.get();
> >
> >
> >          if (ctx != null && ctx.unlocked(key)) {
> >              writeCtx.remove();
> >
> >
> >              ctx.updateCache(cache);
> >          }
> >      }
> >
> >      catch (IgniteCheckedException e) {
> >          throw convertException(e);
> >      }
> >  }
> >
> > that removes writeCtx from the current thread with writeCtx.remove();
> >
> > - the second EntityUpdateAction.doAfterTransactionCompletion gets to
> > execute HibernateNonStrictAccessStrategy.afterUpdate: here ctx is null
> and
> > the if ctx != null branch is not executed, so the level 2 cache is never
> > updated with the latest changes in the PlaceImpl entity.
> >
> > we were able to have a minimal dummy text example of the transaction that
> > creates a PlaceImpl
> >
> >  @Test
> >  public void testPlaceImplCacheWorksWithFlush() throws Exception {
> >      long[] placeId = new long [] {0L};
> >      doInTransaction(() -> {
> >          PlaceImpl place = new PlaceImpl();
> >          entityManager.persist(place);
> >          placeId[0] = place.getId();
> >          entityManager.flush();
> >          place.setName("NAME"); //set some place properties
> >          entityManager.flush();
> >          place.setDescription("description"); //set some other place
> > properties
> >          assertThat(place.getDescription(), Matchers.is("description"));
> >      });
> >      //load place from the cache
> >      Place place = placeImplRepository.findOne(placeId[0]);
> >      assertThat(place.getName(), Matchers.is("NAME"));
> >      //the following assertion fails
> >      assertThat(place.getDescription(), Matchers.is("description"));
> >  }
> >
> > we are aware the given example should not manually invoke flushes but in
> > our real transaction the flush is not manual, our code provokes
> > inadvertently autoFlushIfRequired that happens to flush also updates to
> our
> > new PlaceImpl entity
> >
> > what are your thoughts on the matter?
> > could this be a bug?
> > should we not use Ignite's hibernate level 2 cache implementation
> > HibernateRegionFactory when transactions update entities with multiple
> > flushes?
> > if you have any pointers on a solution we could also try to provide you a
> > pull request with the implementation.
> >
> > thank you for your time!
> >
> > Alex
> >
>

Reply via email to