1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-07-08 17:45:24 +02:00

Fix bug 149179 - use Mutex to serialize parallel sftp requests where necessary

This commit is contained in:
Martin Oberhuber 2006-08-10 16:16:55 +00:00
parent fb30c9539c
commit ac3e1dfd90
2 changed files with 275 additions and 100 deletions

View file

@ -0,0 +1,148 @@
/*******************************************************************************
* Copyright (c) 2006 Wind River Systems, Inc.
* 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:
* Martin Oberhuber (Wind River) - initial API and implementation
*******************************************************************************/
package org.eclipse.rse.services.ssh.files;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.rse.services.ssh.Activator;
/**
* A Mutual Exclusion Lock for Threads that need to access a resource
* in a serialized manner.
*
* Usage Example:
* <code>
* private Mutex fooMutex;
* boolean doFooSerialized()(IProgressMonitor monitor) {
* if (fooMutex.waitForLock(monitor, 1000)) {
* try {
* return doFoo();
* } finally {
* fooMutex.release();
* }
* }
* return false;
* }
* </code>
*/
public class Mutex {
private boolean fLocked = false;
private List fWaitQueue = new LinkedList();
/**
* Try to acquire the lock maintained by this mutex.
*
* If the thread needs to wait before it can acquire the mutex, it
* will wait in a first-come-first-serve fashion. In case a progress
* monitor was given, it will be updated and checked for cancel every
* second.
*
* @param monitor Eclipse Progress Monitor. May be <code>null</code>.
* @param timeout Maximum wait time given in milliseconds.
* @return <code>true</code> if the lock was obtained successfully.
*/
public synchronized boolean waitForLock(IProgressMonitor monitor, long timeout) {
if (Thread.interrupted()) {
return false;
}
if (fLocked) {
//need to wait for the lock.
boolean canceled = false;
final Thread myself = Thread.currentThread();
try {
fWaitQueue.add(myself);
Activator.trace("Mutex: added "+myself+", size="+fWaitQueue.size()); //$NON-NLS-1$ //$NON-NLS-2$
long start = System.currentTimeMillis();
long timeLeft = timeout;
long pollTime = (monitor!=null) ? 1000 : timeLeft;
long nextProgressUpdate = start+500;
while (timeLeft>0 && !canceled) {
try {
wait(timeLeft > pollTime ? pollTime : timeLeft);
Activator.trace("Mutex: wakeup "+myself+" ?"); //$NON-NLS-1$ //$NON-NLS-2$
//I'm still in the list, nobody is allowed to take me out!
assert !fWaitQueue.isEmpty();
if (!fLocked && fWaitQueue.get(0) == myself) {
break; //gee it's my turn!
}
long curTime = System.currentTimeMillis();
timeLeft = start + timeout - curTime;
if (monitor!=null) {
canceled = monitor.isCanceled();
if (!canceled && (curTime>nextProgressUpdate)) {
monitor.worked(1);
nextProgressUpdate+=1000;
}
}
} catch(InterruptedException e) {
canceled = true;
}
}
} finally {
fWaitQueue.remove(myself);
Activator.trace("Mutex: removed "+myself+", size="+fWaitQueue.size()); //$NON-NLS-1$ //$NON-NLS-2$
}
if (fLocked || canceled) {
//we were not able to acquire the lock due to an exception,
//or because the wait was canceled.
return false;
}
}
//acquire the lock myself now.
fLocked = true;
return true;
}
/**
* Release this mutex's lock.
*
* May only be called by the same thread that originally acquired
* the Mutex.
*/
public synchronized void release() {
fLocked=false;
if (!fWaitQueue.isEmpty()) {
Object nextOneInQueue = fWaitQueue.get(0);
Activator.trace("Mutex: releasing "+nextOneInQueue); //$NON-NLS-1$
notifyAll();
}
}
/**
* Return this Mutex's lock status.
* @return <code>true</code> if this mutex is currently acquired by a thread.
*/
public synchronized boolean isLocked() {
return fLocked;
}
/**
* Interrupt all threads waiting for the Lock, causing their
* {@link #waitForLock(IProgressMonitor, long)} method to return
* <code>false</code>.
* This should be called if the resource that the Threads are
* contending for, becomes unavailable for some other reason.
*/
public void interruptAll() {
Iterator it = fWaitQueue.iterator();
while (it.hasNext()) {
Thread aThread = (Thread)it.next();
aThread.interrupt();
}
}
}

View file

@ -49,6 +49,8 @@ public class SftpFileService extends AbstractFileService implements IFileService
private ISshSessionProvider fSessionProvider; private ISshSessionProvider fSessionProvider;
private ChannelSftp fChannelSftp; private ChannelSftp fChannelSftp;
private String fUserHome; private String fUserHome;
private Mutex fDirChannelMutex = new Mutex();
private long fDirChannelTimeout = 5000; //max.5 seconds to obtain dir channel
// public SftpFileService(SshConnectorService conn) { // public SftpFileService(SshConnectorService conn) {
// fConnector = conn; // fConnector = conn;
@ -130,6 +132,7 @@ public class SftpFileService extends AbstractFileService implements IFileService
if (fChannelSftp!=null && fChannelSftp.isConnected()) { if (fChannelSftp!=null && fChannelSftp.isConnected()) {
fChannelSftp.disconnect(); fChannelSftp.disconnect();
} }
fDirChannelMutex.interruptAll();
fChannelSftp = null; fChannelSftp = null;
} }
@ -140,11 +143,15 @@ public class SftpFileService extends AbstractFileService implements IFileService
//the API docs. //the API docs.
SftpHostFile node = null; SftpHostFile node = null;
SftpATTRS attrs = null; SftpATTRS attrs = null;
try { if (fDirChannelMutex.waitForLock(monitor, fDirChannelTimeout)) {
attrs = getChannel("SftpFileService.getFile").stat(remoteParent+'/'+fileName); //$NON-NLS-1$ try {
Activator.trace("SftpFileService.getFile done"); //$NON-NLS-1$ attrs = getChannel("SftpFileService.getFile").stat(remoteParent+'/'+fileName); //$NON-NLS-1$
} catch(Exception e) { Activator.trace("SftpFileService.getFile done"); //$NON-NLS-1$
Activator.trace("SftpFileService.getFile failed: "+e.toString()); //$NON-NLS-1$ } catch(Exception e) {
Activator.trace("SftpFileService.getFile failed: "+e.toString()); //$NON-NLS-1$
} finally {
fDirChannelMutex.release();
}
} }
if (attrs!=null) { if (attrs!=null) {
node = makeHostFile(remoteParent, fileName, attrs); node = makeHostFile(remoteParent, fileName, attrs);
@ -171,48 +178,52 @@ public class SftpFileService extends AbstractFileService implements IFileService
} }
NamePatternMatcher filematcher = new NamePatternMatcher(fileFilter, true, true); NamePatternMatcher filematcher = new NamePatternMatcher(fileFilter, true, true);
List results = new ArrayList(); List results = new ArrayList();
try { if (fDirChannelMutex.waitForLock(monitor, fDirChannelTimeout)) {
java.util.Vector vv=getChannel("SftpFileService.internalFetch").ls(parentPath); //$NON-NLS-1$ try {
for(int ii=0; ii<vv.size(); ii++) { java.util.Vector vv=getChannel("SftpFileService.internalFetch").ls(parentPath); //$NON-NLS-1$
Object obj=vv.elementAt(ii); for(int ii=0; ii<vv.size(); ii++) {
if(obj instanceof ChannelSftp.LsEntry){ Object obj=vv.elementAt(ii);
ChannelSftp.LsEntry lsEntry = (ChannelSftp.LsEntry)obj; if(obj instanceof ChannelSftp.LsEntry){
String fileName = lsEntry.getFilename(); ChannelSftp.LsEntry lsEntry = (ChannelSftp.LsEntry)obj;
if (".".equals(fileName) || "..".equals(fileName)) { //$NON-NLS-1$ //$NON-NLS-2$ String fileName = lsEntry.getFilename();
//don't show the trivial names if (".".equals(fileName) || "..".equals(fileName)) { //$NON-NLS-1$ //$NON-NLS-2$
continue; //don't show the trivial names
} continue;
if (filematcher.matches(fileName)) { }
SftpHostFile node = makeHostFile(parentPath, fileName, lsEntry.getAttrs()); if (filematcher.matches(fileName)) {
if (isRightType(fileType, node)) { SftpHostFile node = makeHostFile(parentPath, fileName, lsEntry.getAttrs());
results.add(node); if (isRightType(fileType, node)) {
} results.add(node);
} }
} }
} }
Activator.trace("SftpFileService.internalFetch ok"); //$NON-NLS-1$ }
} catch(Exception e) { Activator.trace("SftpFileService.internalFetch ok"); //$NON-NLS-1$
//TODO throw new SystemMessageException. } catch(Exception e) {
//We get a "2: No such file" exception when we try to get contents //TODO throw new SystemMessageException.
//of a symbolic link that turns out to point to a file rather than //We get a "2: No such file" exception when we try to get contents
//a directory. In this case, the result is probably expected. //of a symbolic link that turns out to point to a file rather than
//We should try to classify symbolic links as "file" or "dir" correctly. //a directory. In this case, the result is probably expected.
if (checkSessionConnected()) { //We should try to classify symbolic links as "file" or "dir" correctly.
//TODO not quite sure who should really check for session conneced, if (checkSessionConnected()) {
//perhaps this should be done in the caller? - If we eventually //TODO not quite sure who should really check for session conneced,
//want to support re-connect and re-doing the failed operation //perhaps this should be done in the caller? - If we eventually
//after reconnect this might be necessary. //want to support re-connect and re-doing the failed operation
Activator.trace("SftpFileService.internalFetch failed: "+e.toString()); //$NON-NLS-1$ //after reconnect this might be necessary.
//TODO bug 149181: In case of errors like "4:permission denied", throw an Activator.trace("SftpFileService.internalFetch failed: "+e.toString()); //$NON-NLS-1$
//exception to show an error dialog to the user. //TODO bug 149181: In case of errors like "4:permission denied", throw an
e.printStackTrace(); //exception to show an error dialog to the user.
//TODO bug 149155: since channelSftp is totally single-threaded, multiple e.printStackTrace();
//parallel access to the channel may break it, typically resulting in //TODO bug 149155: since channelSftp is totally single-threaded, multiple
//SftpException here. I'm not sure how to safely avoid this yet. //parallel access to the channel may break it, typically resulting in
//So here's a little workaround to get the system into a "usable" state again: //SftpException here. I'm not sure how to safely avoid this yet.
//disconnect our channel, it will be reconnected on the next operation. //So here's a little workaround to get the system into a "usable" state again:
//Session handling needs to be re-thought... //disconnect our channel, it will be reconnected on the next operation.
fChannelSftp.disconnect(); //Session handling needs to be re-thought...
fChannelSftp.disconnect();
}
} finally {
fDirChannelMutex.release();
} }
} }
return (IHostFile[])results.toArray(new IHostFile[results.size()]); return (IHostFile[])results.toArray(new IHostFile[results.size()]);
@ -429,21 +440,25 @@ public class SftpFileService extends AbstractFileService implements IFileService
public IHostFile createFile(IProgressMonitor monitor, String remoteParent, String fileName) throws SystemMessageException public IHostFile createFile(IProgressMonitor monitor, String remoteParent, String fileName) throws SystemMessageException
{ {
IHostFile result = null; IHostFile result = null;
try { if (fDirChannelMutex.waitForLock(monitor, fDirChannelTimeout)) {
String fullPath = remoteParent + '/' + fileName; try {
OutputStream os = getChannel("SftpFileService.createFile").put(fullPath); //$NON-NLS-1$ String fullPath = remoteParent + '/' + fileName;
//TODO workaround bug 153118: write a single space OutputStream os = getChannel("SftpFileService.createFile").put(fullPath); //$NON-NLS-1$
//since jsch hangs when trying to close the stream without writing //TODO workaround bug 153118: write a single space
os.write(32); //since jsch hangs when trying to close the stream without writing
os.close(); os.write(32);
SftpATTRS attrs = getChannel("SftpFileService.createFile.stat").stat(fullPath); //$NON-NLS-1$ os.close();
result = makeHostFile(remoteParent, fileName, attrs); SftpATTRS attrs = getChannel("SftpFileService.createFile.stat").stat(fullPath); //$NON-NLS-1$
Activator.trace("SftpFileService.createFile ok"); //$NON-NLS-1$ result = makeHostFile(remoteParent, fileName, attrs);
} catch (Exception e) { Activator.trace("SftpFileService.createFile ok"); //$NON-NLS-1$
Activator.trace("SftpFileService.createFile failed: "+e.toString()); //$NON-NLS-1$ } catch (Exception e) {
e.printStackTrace(); Activator.trace("SftpFileService.createFile failed: "+e.toString()); //$NON-NLS-1$
// DKM commenting out because services don't know about this class e.printStackTrace();
// throw new RemoteFileIOException(e); // DKM commenting out because services don't know about this class
// throw new RemoteFileIOException(e);
} finally {
fDirChannelMutex.release();
}
} }
return result; return result;
} }
@ -451,17 +466,21 @@ public class SftpFileService extends AbstractFileService implements IFileService
public IHostFile createFolder(IProgressMonitor monitor, String remoteParent, String folderName) throws SystemMessageException public IHostFile createFolder(IProgressMonitor monitor, String remoteParent, String folderName) throws SystemMessageException
{ {
IHostFile result = null; IHostFile result = null;
try { if (fDirChannelMutex.waitForLock(monitor, fDirChannelTimeout)) {
String fullPath = remoteParent + '/' + folderName; try {
getChannel("SftpFileService.createFolder").mkdir(fullPath); //$NON-NLS-1$ String fullPath = remoteParent + '/' + folderName;
SftpATTRS attrs = getChannel("SftpFileService.createFolder.stat").stat(fullPath); //$NON-NLS-1$ getChannel("SftpFileService.createFolder").mkdir(fullPath); //$NON-NLS-1$
result = makeHostFile(remoteParent, folderName, attrs); SftpATTRS attrs = getChannel("SftpFileService.createFolder.stat").stat(fullPath); //$NON-NLS-1$
Activator.trace("SftpFileService.createFolder ok"); //$NON-NLS-1$ result = makeHostFile(remoteParent, folderName, attrs);
} catch (Exception e) { Activator.trace("SftpFileService.createFolder ok"); //$NON-NLS-1$
Activator.trace("SftpFileService.createFolder failed: "+e.toString()); //$NON-NLS-1$ } catch (Exception e) {
e.printStackTrace(); Activator.trace("SftpFileService.createFolder failed: "+e.toString()); //$NON-NLS-1$
// DKM commenting out because services don't know about this class e.printStackTrace();
//throw new RemoteFileIOException(e); // DKM commenting out because services don't know about this class
//throw new RemoteFileIOException(e);
} finally {
fDirChannelMutex.release();
}
} }
return result; return result;
} }
@ -469,23 +488,27 @@ public class SftpFileService extends AbstractFileService implements IFileService
public boolean delete(IProgressMonitor monitor, String remoteParent, String fileName) throws SystemMessageException public boolean delete(IProgressMonitor monitor, String remoteParent, String fileName) throws SystemMessageException
{ {
boolean ok=false; boolean ok=false;
try { if (fDirChannelMutex.waitForLock(monitor, fDirChannelTimeout)) {
String fullPath = remoteParent + '/' + fileName; try {
SftpATTRS attrs = getChannel("SftpFileService.delete").stat(fullPath); //$NON-NLS-1$ String fullPath = remoteParent + '/' + fileName;
if (attrs==null) { SftpATTRS attrs = getChannel("SftpFileService.delete").stat(fullPath); //$NON-NLS-1$
//doesn't exist, nothing to do if (attrs==null) {
} else if (attrs.isDir()) { //doesn't exist, nothing to do
getChannel("SftpFileService.delete.rmdir").rmdir(fullPath); //$NON-NLS-1$ } else if (attrs.isDir()) {
} else { getChannel("SftpFileService.delete.rmdir").rmdir(fullPath); //$NON-NLS-1$
getChannel("SftpFileService.delete.rm").rm(fullPath); //$NON-NLS-1$ } else {
getChannel("SftpFileService.delete.rm").rm(fullPath); //$NON-NLS-1$
}
ok=true;
Activator.trace("SftpFileService.delete ok"); //$NON-NLS-1$
} catch (Exception e) {
Activator.trace("SftpFileService.delete: "+e.toString()); //$NON-NLS-1$
e.printStackTrace();
// DKM commenting out because services don't know about this class
//throw new RemoteFileIOException(e);
} finally {
fDirChannelMutex.release();
} }
ok=true;
Activator.trace("SftpFileService.delete ok"); //$NON-NLS-1$
} catch (Exception e) {
Activator.trace("SftpFileService.delete: "+e.toString()); //$NON-NLS-1$
e.printStackTrace();
// DKM commenting out because services don't know about this class
//throw new RemoteFileIOException(e);
} }
return ok; return ok;
} }
@ -493,17 +516,21 @@ public class SftpFileService extends AbstractFileService implements IFileService
public boolean rename(IProgressMonitor monitor, String remoteParent, String oldName, String newName) throws SystemMessageException public boolean rename(IProgressMonitor monitor, String remoteParent, String oldName, String newName) throws SystemMessageException
{ {
boolean ok=false; boolean ok=false;
try { if (fDirChannelMutex.waitForLock(monitor, fDirChannelTimeout)) {
String fullPathOld = remoteParent + '/' + oldName; try {
String fullPathNew = remoteParent + '/' + newName; String fullPathOld = remoteParent + '/' + oldName;
getChannel("SftpFileService.rename").rename(fullPathOld, fullPathNew); //$NON-NLS-1$ String fullPathNew = remoteParent + '/' + newName;
ok=true; getChannel("SftpFileService.rename").rename(fullPathOld, fullPathNew); //$NON-NLS-1$
Activator.trace("SftpFileService.rename ok"); //$NON-NLS-1$ ok=true;
} catch (Exception e) { Activator.trace("SftpFileService.rename ok"); //$NON-NLS-1$
Activator.trace("SftpFileService.rename failed: "+e.toString()); //$NON-NLS-1$ } catch (Exception e) {
e.printStackTrace(); Activator.trace("SftpFileService.rename failed: "+e.toString()); //$NON-NLS-1$
// DKM commenting out because services don't know about this class e.printStackTrace();
//throw new RemoteFileIOException(e); // DKM commenting out because services don't know about this class
//throw new RemoteFileIOException(e);
} finally {
fDirChannelMutex.release();
}
} }
return ok; return ok;
} }