-
Notifications
You must be signed in to change notification settings - Fork 401
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Do not use static state variables in Extension class #389
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
import com.google.common.annotations.VisibleForTesting; | ||
|
||
import hudson.Extension; | ||
import hudson.ExtensionList; | ||
import hudson.model.AdministrativeMonitor; | ||
import hudson.model.Item; | ||
import jenkins.model.Jenkins; | ||
|
@@ -57,7 +58,7 @@ | |
|
||
@Override | ||
public boolean isActivated() { | ||
return DuplicateEventsSubscriber.isDuplicateEventSeen(); | ||
return ExtensionList.lookupSingleton(DuplicateEventsSubscriber.class).isDuplicateEventSeen(); | ||
} | ||
|
||
@Override | ||
|
@@ -75,7 +76,7 @@ | |
public HttpResponse doGetLastDuplicatePayload() { | ||
Jenkins.get().checkPermission(Jenkins.SYSTEM_READ); | ||
JSONObject data; | ||
var lastDuplicate = DuplicateEventsSubscriber.getLastDuplicate(); | ||
var lastDuplicate = ExtensionList.lookupSingleton(DuplicateEventsSubscriber.class).getLastDuplicate(); | ||
if (lastDuplicate != null) { | ||
data = JSONObject.fromObject(lastDuplicate.ghSubscriberEvent().getPayload()); | ||
} else { | ||
|
@@ -102,7 +103,7 @@ | |
|
||
private static final Logger LOGGER = Logger.getLogger(DuplicateEventsSubscriber.class.getName()); | ||
|
||
private static Ticker ticker = Ticker.systemTicker(); | ||
private Ticker ticker = Ticker.systemTicker(); | ||
/** | ||
* Caches GitHub event GUIDs for 10 minutes to track recent events to detect duplicates. | ||
* <p> | ||
|
@@ -114,21 +115,21 @@ | |
* timestamp (assuming caffeine internally keeps long) takes 8 bytes; total of 44 bytes | ||
* per entry. So the maximum memory consumed by this cache is 24k * 44 = 1056k = 1.056 MB. | ||
*/ | ||
private static final Cache<String, Object> EVENT_TRACKER = Caffeine.newBuilder() | ||
.maximumSize(24_000L) | ||
.expireAfterWrite(Duration.ofMinutes(10)) | ||
.ticker(() -> ticker.read()) | ||
.build(); | ||
private final Cache<String, Object> eventTracker = Caffeine.newBuilder() | ||
.maximumSize(24_000L) | ||
.expireAfterWrite(Duration.ofMinutes(10)) | ||
.ticker(() -> ticker.read()) | ||
.build(); | ||
private static final Object DUMMY = new Object(); | ||
|
||
private static volatile TrackedDuplicateEvent lastDuplicate; | ||
private volatile TrackedDuplicateEvent lastDuplicate; | ||
public record TrackedDuplicateEvent( | ||
String eventGuid, Instant lastUpdated, GHSubscriberEvent ghSubscriberEvent) { } | ||
private static final Duration TWENTY_FOUR_HOURS = Duration.ofHours(24); | ||
|
||
@VisibleForTesting | ||
@Restricted(NoExternalUse.class) | ||
static void setTicker(Ticker testTicker) { | ||
void setTicker(Ticker testTicker) { | ||
ticker = testTicker; | ||
} | ||
|
||
|
@@ -174,10 +175,10 @@ | |
if (eventGuid == null) { | ||
return; | ||
} | ||
if (EVENT_TRACKER.getIfPresent(eventGuid) != null) { | ||
if (eventTracker.getIfPresent(eventGuid) != null) { | ||
lastDuplicate = new TrackedDuplicateEvent(eventGuid, getNow(), event); | ||
} | ||
EVENT_TRACKER.put(eventGuid, DUMMY); | ||
eventTracker.put(eventGuid, DUMMY); | ||
} | ||
|
||
/** | ||
|
@@ -187,16 +188,16 @@ | |
* | ||
* @return {@code true} if a duplicate was seen in the last 24 hours, {@code false} otherwise. | ||
*/ | ||
public static boolean isDuplicateEventSeen() { | ||
public boolean isDuplicateEventSeen() { | ||
return lastDuplicate != null | ||
&& Duration.between(lastDuplicate.lastUpdated(), getNow()).compareTo(TWENTY_FOUR_HOURS) < 0; | ||
} | ||
|
||
private static Instant getNow() { | ||
private Instant getNow() { | ||
return Instant.ofEpochSecond(0L, ticker.read()); | ||
} | ||
|
||
public static TrackedDuplicateEvent getLastDuplicate() { | ||
public TrackedDuplicateEvent getLastDuplicate() { | ||
return lastDuplicate; | ||
} | ||
|
||
|
@@ -206,10 +207,10 @@ | |
*/ | ||
@VisibleForTesting | ||
@Restricted(NoExternalUse.class) | ||
static Set<String> getEventCountsTracker() { | ||
return EVENT_TRACKER.asMap().keySet().stream() | ||
.filter(key -> EVENT_TRACKER.getIfPresent(key) != null) | ||
.collect(Collectors.toSet()); | ||
Set<String> getPresentEventKeys() { | ||
return eventTracker.asMap().keySet().stream() | ||
.filter(key -> eventTracker.getIfPresent(key) != null) | ||
Comment on lines
+211
to
+212
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BTW I think this would be better written using a stream on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
would be like,
But since this is a helper method for tests only, and we only need the keys, ig probably keeping on (I now renamed the method to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah it is just a bad pattern to iterate a map’s key set and then look up the value each time. Does not really matter of course. |
||
.collect(Collectors.toSet()); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using
ExtensionList.lookupSingleton
in two places in this class.I considered keeping a class-level subscriber field, but that seem to require extra code to manage the reference correctly.
@Extension
, I think they can't store direct references to each other—perhaps because their initialization order isn't guaranteed?if (subscriber != null) { subscriber = ExtensionList.lookupSingleton(...); }
), but extensions are already looked up in more hot code path - ref code (executed for every event)So I think this approach is fine - (
ExtensionList.lookupSingleton
probably doesn't introduce any overhead)