Thank you!

On Mon, Mar 27, 2023 at 11:33 AM Kseniya Romanova <ksroman...@apache.org>
wrote:

> 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