/* * Hibernate, Relational Persistence for Idiomatic Java * * JBoss, Home of Professional Open Source * Copyright 2012 Red Hat Inc. and/or its affiliates and other contributors * as indicated by the @authors tag. All rights reserved. * See the copyright.txt in the distribution for a * full listing of individual contributors. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public License, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package org.hibernate.search.test.fileleaks; import java.io.Serializable; import java.util.Arrays; import junit.framework.Assert; import org.apache.lucene.search.MatchAllDocsQuery; import org.hibernate.search.annotations.DocumentId; import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Indexed; import org.hibernate.search.backend.spi.Work; import org.hibernate.search.backend.spi.WorkType; import org.hibernate.search.engine.spi.SearchFactoryImplementor; import org.hibernate.search.indexes.impl.DirectoryBasedIndexManager; import org.hibernate.search.query.engine.spi.HSQuery; import org.hibernate.search.spi.SearchFactoryBuilder; import org.hibernate.search.test.util.ManualConfiguration; import org.hibernate.search.test.util.ManualTransactionContext; import org.hibernate.search.test.util.leakdetection.FileMonitoringDirectory; import org.hibernate.search.test.util.leakdetection.FileMonitoringDirectoryProvider; import org.junit.Test; /** * Test for HSEARCH-1090: IndexReader leaks file handles on close * * @author Sanne Grinovero <sanne@hibernate.org> (C) 2012 Red Hat Inc. */ public class AllFilesClosedTest { private SearchFactoryImplementor searchFactory; @Test public void testFileHandlesReleased() { //We initialize the SearchFactory in the test itself as we want to test it's state *after* shutdown searchFactory = initializeSearchFactory(); //extract the directories now, as they won't be available after SearchFactory#close : FileMonitoringDirectory directoryOne = getDirectory( "index1" ); FileMonitoringDirectory directoryTwo = getDirectory( "index2" ); try { doSomeOperations(); assertDirectoryOpen( directoryOne ); assertDirectoryOpen( directoryTwo ); if (nrtNotEnabled()) assertAllFilesClosed( directoryTwo ); // directoryOne is using resource pooling } finally { searchFactory.close(); } assertAllFilesClosed( directoryOne ); assertAllFilesClosed( directoryTwo ); assertDirectoryClosed( directoryOne ); assertDirectoryClosed( directoryTwo ); } /** * Override point for extending test */ protected boolean nrtNotEnabled() { return true; } /** * Verifies all files in the Directory were closed */ private void assertAllFilesClosed(FileMonitoringDirectory directory) { Assert.assertTrue( "not all files were closed", directory.allFilesWereClosed() ); } /** * Verifies the directory is closed */ private void assertDirectoryClosed(FileMonitoringDirectory directory) { Assert.assertTrue( directory.isClosed() ); } /** * Verifies the directory is open */ private void assertDirectoryOpen(FileMonitoringDirectory directory) { Assert.assertFalse( directory.isClosed() ); } private FileMonitoringDirectory getDirectory(String indexName) { DirectoryBasedIndexManager indexManager = (DirectoryBasedIndexManager) searchFactory.getAllIndexesManager().getIndexManager( indexName ); FileMonitoringDirectoryProvider directoryProvider = (FileMonitoringDirectoryProvider) indexManager.getDirectoryProvider(); FileMonitoringDirectory directory = (FileMonitoringDirectory) directoryProvider.getDirectory(); return directory; } /** * The reported bugs were related to multithreaded operations, but * we can actually trigger it with a sequence of read/writes: we need to re-open * a dirty index to run some query on it. */ private void doSomeOperations() { assertElementsInIndex( 0 ); storeDvd( 1, "Aliens" ); storeDvd( 2, "Predators" ); storeBook( 1, "Hibernate Search, second edition" ); assertElementsInIndex( 3 ); storeDvd( 2, "Prometheus" ); // This is an update storeBook( 2, "Prometheus and the Eagle" ); assertElementsInIndex( 4 ); } /** * Run the actual query. Assert sizes to verify everything else is working. * @param expected number of elements found in the index */ private void assertElementsInIndex(int expected) { HSQuery hsQuery = searchFactory.createHSQuery(); hsQuery .luceneQuery( new MatchAllDocsQuery() ) .targetedEntities( Arrays.asList( new Class<?>[]{ Book.class, Dvd.class } ) ); int resultSize = hsQuery.queryResultSize(); Assert.assertEquals( expected, resultSize ); } private void storeBook(int id, String string) { Book book = new Book(); book.id = id; book.title = string; storeObject( book, id ); } private void storeDvd(int id, String dvdTitle) { Dvd dvd1 = new Dvd(); dvd1.id = id; dvd1.title = dvdTitle; storeObject( dvd1, id ); } private void storeObject(Object entity, Serializable id) { Work work = new Work( entity, id, WorkType.UPDATE, false ); ManualTransactionContext tc = new ManualTransactionContext(); searchFactory.getWorker().performWork( work, tc ); tc.end(); } protected SearchFactoryImplementor initializeSearchFactory() { ManualConfiguration cfg = new ManualConfiguration() .addProperty( "hibernate.search.default.directory_provider", FileMonitoringDirectoryProvider.class.getName() ) .addProperty( "hibernate.search.default.reader.strategy", "shared" ) .addProperty( "hibernate.search.index2.reader.strategy", "not-shared" ) //close all readers closed aggressively .addProperty( "hibernate.search.index2.exclusive_index_use", "false" ) //close all writers closed aggressively .addClass( Book.class ) .addClass( Dvd.class ) ; overrideProperties( cfg ); //allow extending tests with different configuration return new SearchFactoryBuilder() .configuration( cfg ) .buildSearchFactory(); } protected void overrideProperties(ManualConfiguration cfg) { //nothing to do } /** Two mapped entities on two differently configured indexes **/ @Indexed(index="index1") public static final class Dvd { @DocumentId long id; @Field String title; } @Indexed(index="index2") public static final class Book { @DocumentId long id; @Field String title; } }