| /******************************************************************************* |
| * Copyright (c) 2000, 2003 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.update.internal.core; |
| |
| import java.io.*; |
| import java.net.*; |
| import java.util.*; |
| |
| import org.eclipse.core.runtime.*; |
| |
| /** |
| * 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 Vector threads; |
| |
| public static class StreamRunnable implements Runnable { |
| private HttpURLConnection urlConnection; |
| private Exception exception; |
| private InputStream is; |
| private boolean disconnected; |
| |
| public StreamRunnable(HttpURLConnection urlConnection) { |
| this.urlConnection = urlConnection; |
| } |
| |
| public InputStream getInputStream() { |
| return is; |
| } |
| |
| public URL getURL() { |
| return urlConnection.getURL(); |
| } |
| |
| public Exception getException() { |
| return exception; |
| } |
| |
| public void disconnect() { |
| 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 (Exception e) { |
| exception = e; |
| } |
| } |
| } |
| |
| 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 createThread(StreamRunnable runnable) throws CoreException { |
| validateExistingThreads(); |
| if (threads == null) |
| threads = new Vector(); |
| Thread t = new ConnectionThread(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) |
| 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, |
| Policy.bind( |
| "ConnectionThreadManager.unresponsiveURL", //$NON-NLS-1$ |
| url), |
| null); |
| children.add(status); |
| } |
| MultiStatus parentStatus = |
| new MultiStatus( |
| pluginId, |
| IStatus.OK, |
| (IStatus[]) children.toArray(new IStatus[children.size()]), |
| Policy.bind("ConnectionThreadManager.tooManyConnections"), //$NON-NLS-1$ |
| null); |
| throw new CoreException(parentStatus); |
| } |
| } |
| |
| /* |
| * Removes terminated threads from the list and returns |
| * the number of those still active. |
| */ |
| |
| private int purgeTerminatedThreads() { |
| 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(); |
| } |
| } |