Skip to content

Commit 69ae045

Browse files
authored
Merge pull request #663 from jglick/BoundObjectTable-JENKINS-16341
[JENKINS-16341] Automatically expire entries from `BoundObjectTable`
2 parents a952218 + c89c724 commit 69ae045

1 file changed

Lines changed: 38 additions & 66 deletions

File tree

core/src/main/java/org/kohsuke/stapler/bind/BoundObjectTable.java

Lines changed: 38 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,19 @@
2323

2424
package org.kohsuke.stapler.bind;
2525

26-
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
2726
import jakarta.servlet.ServletException;
2827
import jakarta.servlet.http.HttpSession;
2928
import java.io.IOException;
3029
import java.io.PrintWriter;
3130
import java.io.Serializable;
32-
import java.lang.ref.WeakReference;
31+
import java.time.Duration;
3332
import java.util.Arrays;
3433
import java.util.HashMap;
3534
import java.util.Map;
3635
import java.util.UUID;
3736
import java.util.logging.Level;
3837
import java.util.logging.Logger;
3938
import org.kohsuke.stapler.Ancestor;
40-
import org.kohsuke.stapler.HttpResponse;
41-
import org.kohsuke.stapler.HttpResponses;
4239
import org.kohsuke.stapler.QueryParameter;
4340
import org.kohsuke.stapler.Stapler;
4441
import org.kohsuke.stapler.StaplerFallback;
@@ -149,22 +146,11 @@ public Table getStaplerFallback() {
149146
return resolve(false);
150147
}
151148

152-
private Bound bind(Ref ref) {
153-
return resolve(true).add(ref);
154-
}
155-
156149
/**
157150
* Binds an object temporarily and returns its URL.
158151
*/
159152
public Bound bind(Object o) {
160-
return bind(new StrongRef(o));
161-
}
162-
163-
/**
164-
* Binds an object temporarily and returns its URL.
165-
*/
166-
public Bound bindWeak(Object o) {
167-
return bind(new WeakRef(o));
153+
return resolve(true).add(new StrongRef(o));
168154
}
169155

170156
/**
@@ -212,7 +198,6 @@ public Table getTable() {
212198
*/
213199
public static class Table implements Serializable {
214200
private final Map<String, Ref> entries = new HashMap<>();
215-
private boolean logging;
216201

217202
private synchronized Bound add(Ref ref) {
218203
final Object target = ref.get();
@@ -225,11 +210,20 @@ private synchronized Bound add(Ref ref) {
225210
return new WellKnownObjectHandle(url, w);
226211
}
227212

213+
// Before adding a new entry, clean up any old ones:
214+
var it = entries.entrySet().iterator();
215+
while (it.hasNext()) {
216+
var entry = it.next();
217+
if (!entry.getValue().valid()) {
218+
LOGGER.fine(() -> "removing stale " + entry.getKey() + ": "
219+
+ entry.getValue().get());
220+
it.remove();
221+
}
222+
}
223+
228224
final String id = UUID.randomUUID().toString();
229225
entries.put(id, ref);
230-
if (logging) {
231-
LOGGER.info(String.format("%s binding %s for %s", toString(), target, id));
232-
}
226+
LOGGER.fine(() -> String.format("%s binding %s for %s", toString(), target, id));
233227

234228
return new Bound() {
235229
@Override
@@ -259,38 +253,22 @@ public Object getDynamic(String id) {
259253
return resolve(id);
260254
}
261255

262-
private synchronized Ref release(String id) {
263-
return entries.remove(id);
256+
/**
257+
* Releases a particular id from the table.
258+
* Normally would be called via {@link #releaseMe} which infers the id from the current request.
259+
*/
260+
public void release(String id) {
261+
var ref = entries.remove(id);
262+
LOGGER.fine(() -> "releasing " + id + ": " + (ref != null ? ref.get() : null));
264263
}
265264

266265
private synchronized Object resolve(String id) {
267266
Ref e = entries.get(id);
268267
if (e == null) {
269-
if (logging) {
270-
LOGGER.info(toString() + " doesn't have binding for " + id);
271-
}
268+
LOGGER.fine(() -> toString() + " doesn't have binding for " + id);
272269
return null;
273270
}
274-
Object v = e.get();
275-
if (v == null) {
276-
if (logging) {
277-
LOGGER.warning(toString() + " had binding for " + id + " but it got garbage collected");
278-
}
279-
entries.remove(id); // reference is already garbage collected.
280-
}
281-
return v;
282-
}
283-
284-
@SuppressFBWarnings(
285-
value = "IS2_INCONSISTENT_SYNC",
286-
justification = "This usage does not create synchronization problems.")
287-
public HttpResponse doEnableLogging() {
288-
if (DEBUG_LOGGING) {
289-
this.logging = true;
290-
return HttpResponses.text("Logging enabled for this session: " + this + "\n");
291-
} else {
292-
return HttpResponses.forbidden();
293-
}
271+
return e.get();
294272
}
295273
}
296274

@@ -332,54 +310,48 @@ public void generateResponse(StaplerRequest2 req, StaplerResponse2 rsp, Object n
332310
*/
333311
interface Ref extends Serializable {
334312
Object get();
313+
314+
boolean valid();
335315
}
336316

337317
private static class StrongRef implements Ref {
338318
private final Object o;
319+
private final long expiration;
339320

340321
StrongRef(Object o) {
341322
this.o = o;
323+
expiration = System.currentTimeMillis() + EXPIRATION_TIME;
342324
}
343325

344326
@Override
345327
public Object get() {
346328
return o;
347329
}
348330

349-
private Object writeReplace() {
350-
if (o instanceof Serializable) {
351-
return this;
352-
} else {
353-
LOGGER.fine(() -> "Refusing to serialize " + o);
354-
return new StrongRef(null);
355-
}
356-
}
357-
}
358-
359-
private static class WeakRef extends WeakReference implements Ref {
360-
private WeakRef(Object referent) {
361-
super(referent);
331+
@Override
332+
public boolean valid() {
333+
return /* compatibility */ expiration == 0 || System.currentTimeMillis() < expiration;
362334
}
363335

364336
private Object writeReplace() {
365-
Object o = get();
366337
if (o instanceof Serializable) {
367338
return this;
368339
} else {
369340
LOGGER.fine(() -> "Refusing to serialize " + o);
370-
return new WeakRef(null);
341+
return new StrongRef(null);
371342
}
372343
}
373344
}
374345

375-
public static final String PREFIX = "/$stapler/bound/";
376-
static final String SCRIPT_PREFIX = "/$stapler/bound/script";
377-
378346
/**
379-
* True to activate debug logging of session fragments.
347+
* How long (in milliseconds) a {@link StrongRef} remains valid for creation if it is not retrieved.
380348
*/
381-
@SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Legacy switch.")
382-
public static boolean DEBUG_LOGGING = Boolean.getBoolean(BoundObjectTable.class.getName() + ".debugLog");
349+
private static final long EXPIRATION_TIME = Long.getLong(
350+
BoundObjectTable.class.getName() + ".EXPIRATION_TIME",
351+
Duration.ofDays(1).toMillis());
352+
353+
public static final String PREFIX = "/$stapler/bound/";
354+
static final String SCRIPT_PREFIX = "/$stapler/bound/script";
383355

384356
private static final Logger LOGGER = Logger.getLogger(BoundObjectTable.class.getName());
385357
}

0 commit comments

Comments
 (0)