-
-
Notifications
You must be signed in to change notification settings - Fork 8.6k
/
WorkspaceList.java
315 lines (282 loc) · 11.1 KB
/
WorkspaceList.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
/*
* 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 jenkins.util.SystemProperties;
import hudson.model.Computer;
import hudson.model.DirectoryBrowserSupport;
import java.io.Closeable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
/**
* Used by {@link Computer} to keep track of workspaces that are actively in use.
*
* @author Kohsuke Kawaguchi
* @since 1.319
* @see Computer#getWorkspaceList()
*/
public final class WorkspaceList {
private static final class AllocationAt extends Exception {
@Override
public String toString() {
return "Allocation Point";
}
}
/**
* Book keeping for workspace allocation.
*/
public static final class Entry {
/**
* Who acquired this workspace?
*/
public final Thread holder = Thread.currentThread();
/**
* When?
*/
public final long time = System.currentTimeMillis();
/**
* From where?
*/
public final Exception source = new AllocationAt();
/**
* True makes the caller of {@link WorkspaceList#allocate(FilePath)} wait
* for this workspace.
*/
public final boolean quick;
public final @Nonnull FilePath path;
/**
* Multiple threads can acquire the same lock if they share the same context object.
*/
public final Object context;
public int lockCount=1;
private Entry(@Nonnull FilePath path, boolean quick) {
this(path,quick,new Object()); // unique context
}
private Entry(@Nonnull FilePath path, boolean quick, Object context) {
this.path = path;
this.quick = quick;
this.context = context;
}
@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 implements /*Auto*/Closeable {
public final @Nonnull FilePath path;
protected Lease(@Nonnull FilePath path) {
if (path == null) { // Protection from old API
throw new NullPointerException("The specified FilePath is null");
}
this.path = path;
}
/**
* Releases this lease.
*/
public abstract void release();
/**
* By default, calls {@link #release}, but should be idempotent.
* @since 1.600
*/
@Override public void close() {
release();
}
/**
* Creates a dummy {@link Lease} object that does no-op in the release.
*/
public static Lease createDummyLease(@Nonnull FilePath p) {
return new Lease(p) {
public void release() {
// noop
}
};
}
/**
* Creates a {@link Lease} object that points to the specified path, but the lock
* is controlled by the given parent lease object.
*/
public static Lease createLinkedDummyLease(@Nonnull FilePath p, final Lease parent) {
return new Lease(p) {
public void release() {
parent.release();
}
};
}
}
private final Map<String, Entry> inUse = new HashMap<>();
public WorkspaceList() {
}
/**
* Allocates a workspace by adding some variation to the given base to make it unique.
*
* <p>
* This method doesn't block prolonged amount of time. Whenever a desired workspace
* is in use, the unique variation is added.
*/
public synchronized Lease allocate(@Nonnull FilePath base) throws InterruptedException {
return allocate(base,new Object());
}
/**
* See {@link #allocate(FilePath)}
*
* @param context
* Threads that share the same context can re-acquire the same lock (which will just increment the lock count.)
* This allows related executors to share the same workspace.
*/
public synchronized Lease allocate(@Nonnull FilePath base, Object context) throws InterruptedException {
for (int i=1; ; i++) {
FilePath candidate = i==1 ? base : base.withSuffix(COMBINATOR+i);
Entry e = inUse.get(candidate.getRemote());
if(e!=null && !e.quick && e.context!=context)
continue;
return acquire(candidate,false,context);
}
}
/**
* Just record that this workspace is being used, without paying any attention to the synchronization support.
*/
public synchronized Lease record(@Nonnull FilePath p) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "recorded " + p, new Throwable("from " + this));
}
Entry old = inUse.put(p.getRemote(), 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(@Nonnull FilePath p) {
Entry old = inUse.get(p.getRemote());
if (old==null)
throw new AssertionError("Releasing unallocated workspace "+p);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "releasing " + p + " with lock count " + old.lockCount, new Throwable("from " + this));
}
old.lockCount--;
if (old.lockCount==0)
inUse.remove(p.getRemote());
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(@Nonnull 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(@Nonnull FilePath p, boolean quick) throws InterruptedException {
return acquire(p,quick,new Object());
}
/**
* See {@link #acquire(FilePath,boolean)}
*
* @param context
* Threads that share the same context can re-acquire the same lock (which will just increment the lock count.)
* This allows related executors to share the same workspace.
*/
public synchronized Lease acquire(@Nonnull FilePath p, boolean quick, Object context) throws InterruptedException {
Entry e;
Thread t = Thread.currentThread();
String oldName = t.getName();
t.setName("Waiting to acquire "+p+" : "+t.getName());
try {
while (true) {
e = inUse.get(p.getRemote());
if (e==null || e.context==context)
break;
wait();
}
} finally {
t.setName(oldName);
}
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "acquired " + p + (e == null ? "" : " with lock count " + e.lockCount), new Throwable("from " + this));
}
if (e!=null) e.lockCount++;
else inUse.put(p.getRemote(), new Entry(p,quick,context));
return lease(p);
}
/**
* Wraps a path into a valid lease.
*/
private Lease lease(@Nonnull FilePath p) {
return new Lease(p) {
final AtomicBoolean released = new AtomicBoolean();
public void release() {
_release(path);
}
@Override public void close() {
if (released.compareAndSet(false, true)) {
release();
}
}
};
}
/**
* Locates a conventional temporary directory to be associated with a workspace.
* <p>This directory is suitable for temporary files to be deleted later in the course of a build,
* or caches and local repositories which should persist across builds done in the same workspace.
* (If multiple workspaces are present for a single job built concurrently, via {@link #allocate(FilePath)}, each will get its own temporary directory.)
* <p>It may also be used for security-sensitive files which {@link DirectoryBrowserSupport} ought not serve,
* acknowledging that these will be readable by builds of other jobs done on the same node.
* <p>Each plugin using this directory is responsible for specifying sufficiently unique subdirectory/file names.
* {@link FilePath#createTempFile} may be used for this purpose if desired.
* <p>The resulting directory may not exist; you may call {@link FilePath#mkdirs} on it if you need it to.
* It may be deleted alongside the workspace itself during cleanup actions.
* @param ws a directory such as a build workspace
* @return a sibling directory, for example {@code …/something@tmp} for {@code …/something}
* @since 1.652
*/
public static FilePath tempDir(FilePath ws) {
return ws.sibling(ws.getName() + COMBINATOR + "tmp");
}
private static final Logger LOGGER = Logger.getLogger(WorkspaceList.class.getName());
/**
* The token that combines the project name and unique number to create unique workspace directory.
*/
private static final String COMBINATOR = SystemProperties.getString(WorkspaceList.class.getName(),"@");
}