blob: e943bbf90811f1c5e7a8e973e9b25b8b9cb118ef [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 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();
}
}