SOLR-1953: It may be possible for temporary files to accumulate until the Solr process is shut down.

This commit is contained in:
markrmiller 2016-12-12 11:10:58 -05:00
parent 22d9af41a4
commit e82399d067
4 changed files with 182 additions and 14 deletions

View File

@ -244,6 +244,9 @@ Bug Fixes
* SOLR-9823: CoreContainer incorrectly setting MDCLoggingContext for core (Jessica Cheng Mallet via Erick Erickson)
* SOLR-1953: It may be possible for temporary files to accumulate until the Solr process is shut down.
(Karl Wright, Mark Miller)
Other Changes
----------------------

View File

@ -45,6 +45,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FileCleaningTracker;
import org.apache.commons.io.input.CloseShieldInputStream;
import org.apache.commons.io.output.CloseShieldOutputStream;
import org.apache.commons.lang.StringUtils;
@ -62,6 +63,7 @@ import org.apache.solr.core.SolrXmlConfig;
import org.apache.solr.request.SolrRequestInfo;
import org.apache.solr.security.AuthenticationPlugin;
import org.apache.solr.security.PKIAuthenticationPlugin;
import org.apache.solr.util.SolrFileCleaningTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -123,6 +125,8 @@ public class SolrDispatchFilter extends BaseSolrFilter {
{
log.trace("SolrDispatchFilter.init(): {}", this.getClass().getClassLoader());
SolrRequestParsers.fileCleaningTracker = new SolrFileCleaningTracker();
StartupLoggingUtils.checkLogDir();
logWelcomeBanner();
String muteConsole = System.getProperty(SOLR_LOG_MUTECONSOLE);
@ -240,6 +244,17 @@ public class SolrDispatchFilter extends BaseSolrFilter {
@Override
public void destroy() {
try {
FileCleaningTracker fileCleaningTracker = SolrRequestParsers.fileCleaningTracker;
if (fileCleaningTracker != null) {
fileCleaningTracker.exitWhenFinished();
}
} catch (Exception e) {
log.warn("Exception closing FileCleaningTracker", e);
} finally {
SolrRequestParsers.fileCleaningTracker = null;
}
if (cores != null) {
try {
cores.shutdown();

View File

@ -42,6 +42,7 @@ import java.util.Map;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileCleaningTracker;
import org.apache.commons.io.input.CloseShieldInputStream;
import org.apache.lucene.util.IOUtils;
import org.apache.solr.common.SolrException;
@ -58,6 +59,7 @@ import org.apache.solr.core.SolrCore;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrQueryRequestBase;
import org.apache.solr.util.RTimerTree;
import org.apache.solr.util.SolrFileCleaningTracker;
import static org.apache.solr.common.params.CommonParams.PATH;
@ -88,6 +90,8 @@ public class SolrRequestParsers
/** Default instance for e.g. admin requests. Limits to 2 MB uploads and does not allow remote streams. */
public static final SolrRequestParsers DEFAULT = new SolrRequestParsers();
public static volatile SolrFileCleaningTracker fileCleaningTracker;
/**
* Pass in an xml configuration. A null configuration will enable
* everything with maximum values.
@ -532,32 +536,31 @@ public class SolrRequestParsers
/**
* Extract Multipart streams
*/
static class MultipartRequestParser implements SolrRequestParser
{
static class MultipartRequestParser implements SolrRequestParser {
private final int uploadLimitKB;
private DiskFileItemFactory factory = new DiskFileItemFactory();
public MultipartRequestParser( int limit )
{
public MultipartRequestParser(int limit) {
uploadLimitKB = limit;
// Set factory constraints
FileCleaningTracker fct = fileCleaningTracker;
if (fct != null) {
factory.setFileCleaningTracker(fileCleaningTracker);
}
// TODO - configure factory.setSizeThreshold(yourMaxMemorySize);
// TODO - configure factory.setRepository(yourTempDirectory);
}
@Override
public SolrParams parseParamsAndFillStreams(
final HttpServletRequest req, ArrayList<ContentStream> streams ) throws Exception
{
final HttpServletRequest req, ArrayList<ContentStream> streams) throws Exception {
if( !ServletFileUpload.isMultipartContent(req) ) {
throw new SolrException( ErrorCode.BAD_REQUEST, "Not multipart content! "+req.getContentType() );
}
MultiMapSolrParams params = parseQueryString( req.getQueryString() );
// Create a factory for disk-based file items
DiskFileItemFactory factory = new DiskFileItemFactory();
// Set factory constraints
// TODO - configure factory.setSizeThreshold(yourMaxMemorySize);
// TODO - configure factory.setRepository(yourTempDirectory);
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setSizeMax( ((long) uploadLimitKB) * 1024L );

View File

@ -0,0 +1,147 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.solr.util;
import java.io.File;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import org.apache.commons.io.FileCleaningTracker;
import org.apache.commons.io.FileDeleteStrategy;
public class SolrFileCleaningTracker extends FileCleaningTracker {
ReferenceQueue<Object> q = new ReferenceQueue<>();
final Collection<Tracker> trackers = Collections.synchronizedSet(new HashSet<Tracker>());
final List<String> deleteFailures = Collections.synchronizedList(new ArrayList<String>());
volatile boolean exitWhenFinished = false;
Thread reaper;
public void track(final File file, final Object marker) {
track(file, marker, null);
}
public void track(final File file, final Object marker, final FileDeleteStrategy deleteStrategy) {
if (file == null) {
throw new NullPointerException("The file must not be null");
}
addTracker(file.getPath(), marker, deleteStrategy);
}
public void track(final String path, final Object marker) {
track(path, marker, null);
}
public void track(final String path, final Object marker, final FileDeleteStrategy deleteStrategy) {
if (path == null) {
throw new NullPointerException("The path must not be null");
}
addTracker(path, marker, deleteStrategy);
}
private synchronized void addTracker(final String path, final Object marker,
final FileDeleteStrategy deleteStrategy) {
if (exitWhenFinished) {
throw new IllegalStateException("No new trackers can be added once exitWhenFinished() is called");
}
if (reaper == null) {
reaper = new Reaper();
reaper.start();
}
trackers.add(new Tracker(path, deleteStrategy, marker, q));
}
public int getTrackCount() {
return trackers.size();
}
public List<String> getDeleteFailures() {
return deleteFailures;
}
public synchronized void exitWhenFinished() {
// synchronized block protects reaper
exitWhenFinished = true;
if (reaper != null) {
synchronized (reaper) {
reaper.interrupt();
try {
reaper.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
private final class Reaper extends Thread {
Reaper() {
super("MultiPart Upload Tmp File Reaper");
setDaemon(true);
}
@Override
public void run() {
while (exitWhenFinished == false || trackers.size() > 0) {
try {
// Wait for a tracker to remove.
final Tracker tracker = (Tracker) q.remove(); // cannot return null
trackers.remove(tracker);
if (!tracker.delete()) {
deleteFailures.add(tracker.getPath());
}
tracker.clear();
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
private static final class Tracker extends PhantomReference<Object> {
private final String path;
private final FileDeleteStrategy deleteStrategy;
Tracker(final String path, final FileDeleteStrategy deleteStrategy, final Object marker,
final ReferenceQueue<? super Object> queue) {
super(marker, queue);
this.path = path;
this.deleteStrategy = deleteStrategy == null ? FileDeleteStrategy.NORMAL : deleteStrategy;
}
public String getPath() {
return path;
}
public boolean delete() {
return deleteStrategy.deleteQuietly(new File(path));
}
}
}