-
-
Notifications
You must be signed in to change notification settings - Fork 9k
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
JENKINS-56937 JCasC support for admin monitors #4552
Conversation
* | ||
* @since TODO | ||
*/ | ||
public Set<String> getDisabledAdministrativeMonitors(){ |
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.
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.
So what's buggy about the CopyOnWriteArraySet
?
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.
https://github.com/search?q=org%3Ajenkinsci+getDisabledAdministrativeMonitors&type=Code
As expected, given it was @Restricted
.
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.
So what's buggy about the
CopyOnWriteArraySet
?
JCasC doesn't support it
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.
to avoid sharing the same data structure w/ the UI, why not just do something like this?
synchronized(this.disabledAdministrativeMonitors) { return new HashSet<String>(this.disabledAdministrativeMonitors); };
... then any client of this class doesn't need to worry about concurrency concerns in the internal data structure; those concerns remain .. internal to this implementation
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.
The issue is that clients modify this set, so returning a copy prevents them updating it
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.
ouch. any reason that clients can't be modified to invoke the setter instead of relying on directly mutating the internal state of this object? the current situation sounds like a pretty leaky design
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 suggested modifying it a couple of comments up, will take a look
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.
have changed, PTAL
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.
suggest wrapping this return ...
statement w/ synchronized(disabledAdministrativeMonitors) {}
as suggested above, since the new HashSet<>(..)
operation will iterate over the collection passed to it
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.
LGTM
* @since TODO | ||
*/ | ||
public void setDisabledAdministrativeMonitors(Set<String> disabledAdministrativeMonitors){ | ||
this.disabledAdministrativeMonitors = disabledAdministrativeMonitors; |
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.
Probably should be cloned here instead of taking a reference to an external object
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.
Why’s that? It’s just a setter
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.
both stapler and jcasc will handle creating a clone that should be safe for Jenkins core to use.
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.
Spotbugs normally warns against taking a reference to an outside object
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.
not on collections or simple object types.
Spotbugs would complain on date objects.
@@ -93,13 +93,26 @@ protected void killComputer(Computer c) { | |||
* Package-protected, but accessed API | |||
* ============================================================================================================== */ | |||
|
|||
/*package*/ final CopyOnWriteArraySet<String> disabledAdministrativeMonitors = new CopyOnWriteArraySet<>(); | |||
private Set<String> disabledAdministrativeMonitors = new HashSet<>(); |
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.
There is a gigantic comment above that you might want to consider removing
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.
It was probably already obsolete after #3873
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 get the visibility change but I'm not so sure about the type change..
* | ||
* @since TODO | ||
*/ | ||
public void setDisabledAdministrativeMonitors(Set<String> disabledAdministrativeMonitors){ |
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.
public void setDisabledAdministrativeMonitors(Set<String> disabledAdministrativeMonitors){ | |
public void setDisabledAdministrativeMonitors(Set<String> disabledAdministrativeMonitors) { |
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.
Makes me slightly nervous removing the thread secure CopyOnWriteArraySet
to a non thread safe HashSet
Can we keep it a |
probably, i'll take a look |
Is what I've pushed what you meant? |
Sort of. I'd also sprinkle in some |
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.
how's this @daniel-beck ?
* ============================================================================================================== */ | ||
|
||
/*package*/ final CopyOnWriteArraySet<String> disabledAdministrativeMonitors = new CopyOnWriteArraySet<>(); | ||
private Set<String> disabledAdministrativeMonitors = new CopyOnWriteArraySet<>(); |
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.
private Set<String> disabledAdministrativeMonitors = new CopyOnWriteArraySet<>(); | |
private volatile Set<String> disabledAdministrativeMonitors = new CopyOnWriteArraySet<>(); |
Better to ask someone else. |
If you want to make that correct, you'll need to make both the getter and setter synchronized as well as any other (public or externally-callable) methods that access or mutate the set. At that point, you can also just use HashSet since everything would be protected by the synchronization mutex. Alternatively, you could work at being almost correct by changing the setter to do a clear() followed by an addAll() rather than changing the field itself. Then no synchronization or volatile is needed, and it only introduces a fairly insignificant race condition. |
Thanks, minding taking a look at what I pushed to see if that would work |
* ============================================================================================================== */ | ||
|
||
/*package*/ final CopyOnWriteArraySet<String> disabledAdministrativeMonitors = new CopyOnWriteArraySet<>(); | ||
private volatile Set<String> disabledAdministrativeMonitors = new HashSet<>(); |
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.
My mistake; this will likely still need to be either CopyOnWriteArraySet
or use Collections.synchronizedSet()
around this. Otherwise, a caller that iterates on the result of getDisabledAdministrativeMonitors()
may get a ConcurrentModificationException
when another thread calls disable()
which modifies the set.
Alternatively, you can update getDisabledAdministrativeMonitors
to wrap the set in a new HashSet
to copy the set and prevent the issue entirely.
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.
Alternatively, you can update getDisabledAdministrativeMonitors to wrap the set in a new HashSet to copy the set and prevent the issue entirely.
That doesn't work as the set is modified through the getter, by calling add on it.
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.
Wait, just saw your comment. If you're also modifying the returned result, then this still won't work properly. What you can do instead would be to store a Collections.synchronizedSet(new HashSet<>())
in the field (make it final
). Next, the methods that were previously synchronized should instead synchronized (disabledAdministrativeMonitors)
in a block inside the method (remove synchronized
from the method keywords). Then callers adding to the set will be synchronized on the same lock. Finally, in the setter, you should (inside the synchronized (disabledAdministrativeMonitors) { ... }
block) clear
followed by addAll
.
Thanks for your patience with my confusion here. I believe this last change I suggested should put things in the proper place! |
/* ================================================================================================================= | ||
* Package-protected, but accessed API | ||
* ============================================================================================================== */ | ||
private final Set<String> disabledAdministrativeMonitors = Collections.synchronizedSet(new HashSet<>()); |
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.
The docs of synchronizedSet says (I've never used this before)
Returns a synchronized (thread-safe) set backed by the specified set. In order to guarantee serial access, it is critical that all access to the backing set is accomplished through the returned set.
It is imperative that the user manually synchronize on the returned set when iterating over it:
Set s = Collections.synchronizedSet(new HashSet());
...
synchronized (s) {
Iterator i = s.iterator(); // Must be in the synchronized block
while (i.hasNext())
foo(i.next());
}
Failure to follow this advice may result in non-deterministic behavior.
I'm not sure what it means by Failure to follow this advice may result in non-deterministic behavior.
in here
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.
There's no direct iteration of this field,
there's 4 usages of it,
- enable / disable, calls add /remove
- isEnabled, calls
contains
- the UI, which iterates over it, not sure if there's much we can do there,
- jcasc, it will use the setter to update it which will clear the set and use what jcasc sets.
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.
My worry is what is the worst that could happen? Will it throw an exception or will it just show old data?
If its the latter then I think its alright but the documentation doesn't seem perfectly clear
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.
#3687 (comment) was the (un)funnest one I've seen. Way up there in my hall of shame
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.
That's only relevant because iterating over a collection is technically n operations, not 1. You need to synchronize for the duration of the logical operation, not n separate synchronizations for n items in the collection. This is all about fencing the allowed execution timeline based on "happens-before" rules in Java.
So, in a sense, no, peppering synchronized
and volatile
everywhere isn't good enough to solve every concurrency issue! 😅
I suggested returning a copy of the Set. There are no concurrency concerns
with the returned object
…On Tue, Mar 24, 2020, 12:35 PM Matt Sicker ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In core/src/main/java/hudson/model/AbstractCIBase.java
<#4552 (comment)>:
>
- @restricted(NoExternalUse.class)
- public CopyOnWriteArraySet<String> getDisabledAdministrativeMonitors(){
+ /**
+ * Get the disabled administrative monitors
+ *
+ * @SInCE TODO
+ */
+ public Set<String> getDisabledAdministrativeMonitors(){
It's to prevent concurrent modification exceptions. If you modify a
non-threadsafe collection while iterating, it throws an exception.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#4552 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAR5KLFMJKBZA2KEAQMOKKTRJDOLNANCNFSM4LDO2YTQ>
.
|
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.
This looks like it would work well enough, too. Thanks for synchronizing on a final field, too, which is the recommended approach in general.
Thanks for the reviews all, marking as ready-for-merge, this will be merged after 24 hours if there's no negative feedback |
See JENKINS-56937.
Example config:
Proposed changelog entries
Proposed upgrade guidelines
N/A
Submitter checklist
Proposed changelog entries
section only if there are breaking changes or other changes which may require extra steps from users during the upgradeDesired reviewers
@mention
Maintainer checklist
Before the changes are marked as
ready-for-merge
:Proposed changelog entries
are correctupgrade-guide-needed
label is set and there is aProposed upgrade guidelines
section in the PR title. (example)lts-candidate