/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.slaves;
import hudson.FilePath;
import hudson.Functions;
import hudson.model.Computer;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Used by {@link Computer} to keep track of workspaces that are actively in use.
*
* <p>
* SUBJECT TO CHANGE! Do not use this from plugins directly.
*
* @author Kohsuke Kawaguchi
* @since 1.319
* @see Computer#getWorkspaceList()
*/
public final class WorkspaceList {
private static final String WORKSPACE_NAME_SUFFIX = "_";
/**
* Book keeping for workspace allocation.
*/
public static final class Entry {
/**
* Who acquired this workspace?
*/
//TODO: review and check whether we can do it private
public final Thread holder = Thread.currentThread();
/**
* When?
*/
//TODO: review and check whether we can do it private
public final long time = System.currentTimeMillis();
/**
* From where?
*/
//TODO: review and check whether we can do it private
public final Exception source = new Exception();
/**
* True makes the caller of {@link WorkspaceList#allocate(FilePath)} wait
* for this workspace.
*/
//TODO: review and check whether we can do it private
public final boolean quick;
//TODO: review and check whether we can do it private
public final FilePath path;
private Entry(FilePath path, boolean quick) {
this.path = path;
this.quick = quick;
}
public Thread getHolder() {
return holder;
}
public long getTime() {
return time;
}
public Exception getSource() {
return source;
}
public boolean isQuick() {
return quick;
}
public FilePath getPath() {
return path;
}
@Override
public String toString() {
String s = path+" owned by "+holder.getName()+" from "+new Date(time);
if(quick) s+=" (quick)";
s+="\n"+Functions.printThrowable(source);
return s;
}
}
/**
* Represents a leased workspace that needs to be returned later.
*/
public static abstract class Lease {
//TODO: review and check whether we can do it private
public final FilePath path;
protected Lease(FilePath path) {
this.path = path;
}
public FilePath getPath() {
return path;
}
/**
* Releases this lease.
*/
public abstract void release();
/**
* Creates a dummy {@link Lease} object that does no-op in the release.
*/
public static Lease createDummyLease(FilePath p) {
return new Lease(p) {
public void release() {
// noop
}
};
}
}
private final Map<FilePath,Entry> inUse = new HashMap<FilePath,Entry>();
public WorkspaceList() {
}
/**
* Allocates a workspace by adding some variation to the given base to make it unique.
*/
public synchronized Lease allocate(FilePath base) throws InterruptedException {
for (int i=1; ; i++) {
//Workspace suffix was changed from @ to _, because of some issues with SCMs.
//see http://issues.hudson-ci.org/browse/HUDSON-4791
FilePath candidate = i==1 ? base : base.withSuffix(WORKSPACE_NAME_SUFFIX + i);
Entry e = inUse.get(candidate);
if(e!=null && !e.quick)
continue;
return acquire(candidate);
}
}
/**
* Just record that this workspace is being used, without paying any attention to the sycnhronization support.
*/
public synchronized Lease record(FilePath p) {
log("recorded "+p);
Entry old = inUse.put(p, new Entry(p, false));
if (old!=null)
throw new AssertionError("Tried to record a workspace already owned: "+old);
return lease(p);
}
/**
* Releases an allocated or acquired workspace.
*/
private synchronized void _release(FilePath p) {
Entry old = inUse.remove(p);
if (old==null)
throw new AssertionError("Releasing unallocated workspace "+p);
notifyAll();
}
/**
* Acquires the given workspace. If necessary, this method blocks until it's made available.
*
* @return
* The same {@link FilePath} as given to this method.
*/
public synchronized Lease acquire(FilePath p) throws InterruptedException {
return acquire(p,false);
}
/**
* See {@link #acquire(FilePath)}
*
* @param quick
* If true, indicates that the acquired workspace will be returned quickly.
* This makes other calls to {@link #allocate(FilePath)} to wait for the release of this workspace.
*/
public synchronized Lease acquire(FilePath p, boolean quick) throws InterruptedException {
while (inUse.containsKey(p))
wait();
log("acquired "+p);
inUse.put(p,new Entry(p,quick));
return lease(p);
}
/**
* Wraps a path into a valid lease.
*/
private Lease lease(FilePath p) {
return new Lease(p) {
public void release() {
_release(path);
}
};
}
private void log(String msg) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine(Thread.currentThread().getName() + " " + msg);
}
private static final Logger LOGGER = Logger.getLogger(WorkspaceList.class.getName());
}