/******************************************************************************* * * Copyright (c) 2004-2009, Oracle Corporation * * 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: * * * * *******************************************************************************/ 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()); }