/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.support.compress; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadFactory; import freenet.client.InsertException; import freenet.client.InsertException.InsertExceptionMode; import freenet.client.async.ClientContext; import freenet.node.PrioRunnable; import freenet.support.Logger; import freenet.support.io.NativeThread; public class RealCompressor { private final ExecutorService executorService; private ClientContext context; private static volatile boolean logMINOR; static { Logger.registerClass(RealCompressor.class); } public RealCompressor() { this.executorService = Executors.newFixedThreadPool(getMaxRunningCompressionThreads(), new CompressorThreadFactory()); } public void setClientContext(ClientContext context) { this.context = context; } public void enqueueNewJob(final CompressJob j) { if(logMINOR) Logger.minor(this, "Enqueueing compression job: "+j); Future<String> task = null; while(!executorService.isShutdown() && task == null) { try { task = executorService.submit(new PrioRunnable() { @Override public void run() { freenet.support.Logger.OSThread.logPID(this); try { try { j.tryCompress(context); } catch (InsertException e) { j.onFailure(e, null, context); } catch (Throwable t) { Logger.error(this, "Caught in OffThreadCompressor: " + t, t); System.err.println("Caught in OffThreadCompressor: " + t); t.printStackTrace(); // Try to fail gracefully j.onFailure( new InsertException(InsertExceptionMode.INTERNAL_ERROR, t, null), null, context); } } catch (Throwable t) { Logger.error(this, "Caught " + t + " in " + this, t); } } @Override public int getPriority() { return NativeThread.MIN_PRIORITY; } }, "Compressor thread for " + j); if(logMINOR) Logger.minor(this, "Compression job: "+j+ "has been enqueued."); }catch (RejectedExecutionException e) { Logger.error(this, "RejectedExectutionException for "+j,e); task = null; } } } private static int getMaxRunningCompressionThreads() { int maxRunningThreads = 1; String osName = System.getProperty("os.name"); if(!osName.contains("Windows") && (osName.toLowerCase().indexOf("mac os x") > 0) || (!NativeThread.usingNativeCode())) // OS/X niceness is really weak, so we don't want any more background CPU load than necessary // Also, on non-Windows, we need the native threads library to be working. maxRunningThreads = 1; else { // Most other OSs will have reasonable niceness, so go by RAM. Runtime r = Runtime.getRuntime(); int max = r.availableProcessors(); // FIXME this may change in a VM, poll it long maxMemory = r.maxMemory(); if(maxMemory < 128 * 1024 * 1024) max = 1; else // one compressor thread per (128MB of ram + available core) max = Math.min(max, (int) (Math.min(Integer.MAX_VALUE, maxMemory / (128 * 1024 * 1024)))); maxRunningThreads = max; } Logger.minor(RealCompressor.class, "Maximum Compressor threads: " + maxRunningThreads); return maxRunningThreads; } public void shutdown() { // TODO: should we wait here? this.executorService.shutdown(); } public static class CompressorThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { return new NativeThread(r, "Compressor thread", NativeThread.MIN_PRIORITY, true); } } }