Welcome to the community! Here's useful link: https://cwiki.apache.org/confluence/display/IGNITE/How+to+Contribute
On Mon, Mar 27, 2023 at 9:48 PM Prigoreanu, Alexandru < prigoreanu.alexan...@anteash.com> wrote: > 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 >>> > >>> >>