diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchDao.java index 3d334deeda4..51c96c17f0e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISearchDao.java @@ -3,6 +3,9 @@ package ca.uhn.fhir.jpa.dao.data; import java.util.Collection; import java.util.Date; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + /* * #%L * HAPI FHIR JPA Server @@ -36,7 +39,7 @@ public interface ISearchDao extends JpaRepository { public Search findByUuid(@Param("uuid") String theUuid); @Query("SELECT s.myId FROM Search s WHERE s.mySearchLastReturned < :cutoff") - public Collection findWhereLastReturnedBefore(@Param("cutoff") Date theCutoff); + public Slice findWhereLastReturnedBefore(@Param("cutoff") Date theCutoff, Pageable thePage); // @Query("SELECT s FROM Search s WHERE s.myCreated < :cutoff") // public Collection findWhereCreatedBefore(@Param("cutoff") Date theCutoff); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java index dff4f46fa67..4654eab6bc8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java @@ -1,44 +1,23 @@ package ca.uhn.fhir.jpa.search; -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2017 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import java.util.Collection; import java.util.Date; import org.apache.commons.lang3.time.DateUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import com.google.common.annotations.VisibleForTesting; import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.data.ISearchDao; -import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao; -import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; +import ca.uhn.fhir.jpa.dao.data.*; import ca.uhn.fhir.jpa.entity.Search; /** @@ -47,7 +26,6 @@ import ca.uhn.fhir.jpa.entity.Search; public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc { public static final long DEFAULT_CUTOFF_SLACK = 10 * DateUtils.MILLIS_PER_SECOND; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StaleSearchDeletingSvcImpl.class); - /* * We give a bit of extra leeway just to avoid race conditions where a query result @@ -71,41 +49,47 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc { @Autowired private PlatformTransactionManager myTransactionManager; - protected void deleteSearch(final Long theSearchPid) { - TransactionTemplate tt = new TransactionTemplate(myTransactionManager); - tt.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus theArg0) { - Search searchToDelete = mySearchDao.findOne(theSearchPid); - ourLog.info("Deleting search {}/{} - Created[{}] -- Last returned[{}]", searchToDelete.getId(), searchToDelete.getUuid(), searchToDelete.getCreated(), searchToDelete.getSearchLastReturned()); - mySearchIncludeDao.deleteForSearch(searchToDelete.getId()); - mySearchResultDao.deleteForSearch(searchToDelete.getId()); - mySearchDao.delete(searchToDelete); - } - }); + private void deleteSearch(final Long theSearchPid) { + Search searchToDelete = mySearchDao.findOne(theSearchPid); + ourLog.info("Deleting search {}/{} - Created[{}] -- Last returned[{}]", searchToDelete.getId(), searchToDelete.getUuid(), searchToDelete.getCreated(), searchToDelete.getSearchLastReturned()); + mySearchIncludeDao.deleteForSearch(searchToDelete.getId()); + mySearchResultDao.deleteForSearch(searchToDelete.getId()); + mySearchDao.delete(searchToDelete); } - + @Override @Transactional(propagation = Propagation.NOT_SUPPORTED) public void pollForStaleSearchesAndDeleteThem() { - + long cutoffMillis = myDaoConfig.getExpireSearchResultsAfterMillis(); if (myDaoConfig.getReuseCachedSearchResultsForMillis() != null) { cutoffMillis = Math.max(cutoffMillis, myDaoConfig.getReuseCachedSearchResultsForMillis()); } - Date cutoff = new Date((System.currentTimeMillis() - cutoffMillis) - myCutoffSlack); - + final Date cutoff = new Date((System.currentTimeMillis() - cutoffMillis) - myCutoffSlack); + ourLog.debug("Searching for searches which are before {}", cutoff); - Collection toDelete = mySearchDao.findWhereLastReturnedBefore(cutoff); - if (!toDelete.isEmpty()) { - - for (final Long next : toDelete) { - deleteSearch(next); + TransactionTemplate tt = new TransactionTemplate(myTransactionManager); + int count = tt.execute(new TransactionCallback() { + @Override + public Integer doInTransaction(TransactionStatus theStatus) { + Slice toDelete = mySearchDao.findWhereLastReturnedBefore(cutoff, new PageRequest(0, 1000)); + for (final Long next : toDelete) { + deleteSearch(next); + } + return toDelete.getContent().size(); } + }); + + long total = tt.execute(new TransactionCallback() { + @Override + public Long doInTransaction(TransactionStatus theStatus) { + return mySearchDao.count(); + } + }); + + ourLog.info("Deleted {} searches, {} remaining", count, total); - ourLog.info("Deleted {} searches, {} remaining", toDelete.size(), mySearchDao.count()); - } } @Scheduled(fixedDelay = DEFAULT_CUTOFF_SLACK) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 92aab8d3dac..54fd5cc0991 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -176,6 +176,9 @@ elements are a choice type (i.e. named "foo[x]"). Thanks to GitHub user @CarthageKing for the pull request! + + Fix potential deadlock in stale search deleting task in JPA server +