/******************************************************************************* * Copyright (c) 2000, 2006 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.update.internal.core.connection; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Status; import org.eclipse.osgi.util.NLS; import org.eclipse.update.internal.core.Messages; import org.eclipse.update.internal.core.UpdateCore; /** * This class manages threads that are dispatched to * obtained a valid input stream from an HTTP connection. * Since obtaining an input stream is an I/O operation * that may block for a long time, it is performed * on a separate thread to keep the UI responsive. * <p> * In case that a connection blocks and does not * terminate with an IOException after a timeout, * active threads may accumulate. The manager will * refuse to create more than MAX_COUNT threads and * instead will throw a CoreException with a child * status object for each connection that is still pending. * <p> * If the connection is responsive but slow, the user * may cancel it. In that case, the manager will * close the stream to avoid resource leak. */ public class ConnectionThreadManager { // set connection timeout to 1 minute private static final String CONNECT_TIMEOUT = "60000"; //$NON-NLS-1$ // set read timeout to 1 minute private static final String READ_TIMEOUT = "60000"; //$NON-NLS-1$ // max number of active threads private static final int MAX_COUNT = 9; private List threads = new ArrayList(MAX_COUNT); public static class StreamRunnable implements Runnable { private URLConnection urlConnection; private IOException ioException; private Exception exception; private InputStream is; private boolean disconnected; public StreamRunnable(URLConnection urlConnection) { this.urlConnection = urlConnection; } public InputStream getInputStream() { return is; } public URL getURL() { return urlConnection.getURL(); } public IOException getIOException() { return ioException; } public Exception getException() { return exception; } public void disconnect() { if (urlConnection instanceof HttpURLConnection) ((HttpURLConnection)urlConnection).disconnect(); disconnected = true; } public void run() { try { is = urlConnection.getInputStream(); if (disconnected) { // The connection was slow, but returned // a valid input stream. However, // the user canceled the connection // so we must close to avoid // resource leak. if (is != null) { try { is.close(); } catch (IOException ex) { // at this point, we don't care } finally { is = null; } } } } catch (IOException e) { ioException = e; } catch (Exception e) { exception = e; } finally { //threads. } } } class ConnectionThread extends Thread { private StreamRunnable runnable; public ConnectionThread(StreamRunnable runnable) { super(runnable, "update-connection"); //$NON-NLS-1$ this.runnable = runnable; } public StreamRunnable getRunnable() { return runnable; } } public ConnectionThreadManager() { // In case we are running Sun's code. setIfNotDefaultProperty("sun.net.client.defaultConnectTimeout", CONNECT_TIMEOUT); //$NON-NLS-1$ setIfNotDefaultProperty("sun.net.client.defaultReadTimeout", READ_TIMEOUT); //$NON-NLS-1$ } private void setIfNotDefaultProperty(String key, String value) { String oldValue = System.getProperty(key); if (oldValue==null || oldValue.equals("-1")) //$NON-NLS-1$ System.setProperty(key, value); } public Thread getConnectionThread(StreamRunnable runnable) throws CoreException { validateExistingThreads(); //if (threads == null) // threads = new Vector(); Thread t = new Thread(runnable); t.setDaemon(true); threads.add(t); return t; } /* * Removes threads that are not alive any more from the * list and ensures that there are at most MAX_COUNT threads * still working. */ private void validateExistingThreads() throws CoreException { if ((threads == null) || (threads.size() == 0)) return; int aliveCount = purgeTerminatedThreads(); if (aliveCount > MAX_COUNT) { ArrayList children = new ArrayList(); String pluginId = UpdateCore.getPlugin().getBundle().getSymbolicName(); for (int i = 0; i < threads.size(); i++) { ConnectionThread t = (ConnectionThread) threads.get(i); String url = t.getRunnable().getURL().toString(); IStatus status = new Status( IStatus.ERROR, pluginId, IStatus.OK, NLS.bind(Messages.ConnectionThreadManager_unresponsiveURL, (new String[] { url })), null); children.add(status); } MultiStatus parentStatus = new MultiStatus( pluginId, IStatus.OK, (IStatus[]) children.toArray(new IStatus[children.size()]), Messages.ConnectionThreadManager_tooManyConnections, null); throw new CoreException(parentStatus); } } /* * Removes terminated threads from the list and returns * the number of those still active. */ private int purgeTerminatedThreads() { if (threads.size() == 0) { return 0; } int aliveCount = 0; Object[] array = threads.toArray(); for (int i = 0; i < array.length; i++) { Thread t = (Thread) array[i]; if (!t.isAlive()) threads.remove(t); else aliveCount++; } return aliveCount; } public void shutdown() { // We might want to kill the active threads but // this is not really necessary since they are all // daemons and will not prevent JVM to terminate. threads.clear(); } }