Skip to content
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-34426] Fix to handle SECURITY-243 #34

Merged
merged 1 commit into from
May 19, 2016

Conversation

fbelzunc
Copy link
Contributor

Stacktrace

Failed to retrieve user <user>
hudson.plugins.active_directory.CacheAuthenticationException: Authentication failed because there was a problem caching user <user>; nested exception is com.google.common.util.concurrent.UncheckedExecutionException: java.lang.IllegalStateException: Recursive load
    at hudson.plugins.active_directory.ActiveDirectoryUnixAuthenticationProvider.retrieveUser(ActiveDirectoryUnixAuthenticationProvider.java:340)
    at hudson.plugins.active_directory.ActiveDirectoryUnixAuthenticationProvider.retrieveUser(ActiveDirectoryUnixAuthenticationProvider.java:199)
    at hudson.plugins.active_directory.ActiveDirectoryUnixAuthenticationProvider.retrieveUser(ActiveDirectoryUnixAuthenticationProvider.java:141)
    at org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:122)
    at org.acegisecurity.providers.ProviderManager.doAuthentication(ProviderManager.java:200)
    at org.acegisecurity.AbstractAuthenticationManager.authenticate(AbstractAuthenticationManager.java:47)
    at org.acegisecurity.ui.webapp.AuthenticationProcessingFilter.attemptAuthentication(AuthenticationProcessingFilter.java:74)
    at org.acegisecurity.ui.AbstractProcessingFilter.doFilter(AbstractProcessingFilter.java:252)
    at hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)
    at jenkins.security.BasicHeaderProcessor.doFilter(BasicHeaderProcessor.java:93)
    at hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)
    at org.acegisecurity.context.HttpSessionContextIntegrationFilter.doFilter(HttpSessionContextIntegrationFilter.java:249)
    at hudson.security.HttpSessionContextIntegrationFilter2.doFilter(HttpSessionContextIntegrationFilter2.java:67)
    at hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:87)
    at hudson.security.ChainedServletFilter.doFilter(ChainedServletFilter.java:76)
    at hudson.security.HudsonFilter.doFilter(HudsonFilter.java:171)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
    at org.kohsuke.stapler.compression.CompressionFilter.doFilter(CompressionFilter.java:49)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
    at hudson.util.CharacterEncodingFilter.doFilter(CharacterEncodingFilter.java:81)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
    at org.kohsuke.stapler.DiagnosticThreadNameFilter.doFilter(DiagnosticThreadNameFilter.java:30)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:553)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
    at org.eclipse.jetty.server.Server.handle(Server.java:499)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
    at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:544)
    at winstone.BoundedExecutorService$1.run(BoundedExecutorService.java:77)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: com.google.common.util.concurrent.UncheckedExecutionException: java.lang.IllegalStateException: Recursive load
    at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2234)
    at com.google.common.cache.LocalCache.get(LocalCache.java:3965)
    at com.google.common.cache.LocalCache$LocalManualCache.get(LocalCache.java:4764)
    at hudson.plugins.active_directory.ActiveDirectoryUnixAuthenticationProvider.retrieveUser(ActiveDirectoryUnixAuthenticationProvider.java:232)
    ... 40 more
Caused by: java.lang.IllegalStateException: Recursive load
    at com.google.common.base.Preconditions.checkState(Preconditions.java:145)
    at com.google.common.cache.LocalCache$Segment.waitForLoadingValue(LocalCache.java:2330)
    at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2320)
    at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2228)
    at com.google.common.cache.LocalCache.get(LocalCache.java:3965)
    at com.google.common.cache.LocalCache$LocalManualCache.get(LocalCache.java:4764)
    at hudson.plugins.active_directory.ActiveDirectoryUnixAuthenticationProvider.retrieveUser(ActiveDirectoryUnixAuthenticationProvider.java:232)
    at hudson.plugins.active_directory.ActiveDirectoryUnixAuthenticationProvider.retrieveUser(ActiveDirectoryUnixAuthenticationProvider.java:199)
    at hudson.plugins.active_directory.ActiveDirectoryUnixAuthenticationProvider.retrieveUser(ActiveDirectoryUnixAuthenticationProvider.java:141)
    at hudson.plugins.active_directory.AbstractActiveDirectoryAuthenticationProvider.loadUserByUsername(AbstractActiveDirectoryAuthenticationProvider.java:54)
    at hudson.plugins.active_directory.ActiveDirectorySecurityRealm.loadUserByUsername(ActiveDirectorySecurityRealm.java:731)
    at hudson.model.User$UserIDCanonicalIdResolver.resolveCanonicalId(User.java:1048)
    at hudson.model.User.get(User.java:394)
    at hudson.model.User.get(User.java:363)
    at hudson.model.User.get(User.java:483)
    at hudson.plugins.active_directory.ActiveDirectoryUserDetail.getJenkinsUser(ActiveDirectoryUserDetail.java:177)
    at hudson.plugins.active_directory.ActiveDirectoryUserDetail.updateUserInfo(ActiveDirectoryUserDetail.java:188)
    at hudson.plugins.active_directory.ActiveDirectoryUnixAuthenticationProvider$1.call(ActiveDirectoryUnixAuthenticationProvider.java:315)
    at hudson.plugins.active_directory.ActiveDirectoryUnixAuthenticationProvider$1.call(ActiveDirectoryUnixAuthenticationProvider.java:232)
    at com.google.common.cache.LocalCache$LocalManualCache$1.load(LocalCache.java:4767)
    at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3568)
    at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2350)
    at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2313)
    at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2228)
    ... 43 more

and it is produced because of SECURITY-243 - https://github.com/jenkinsci/jenkins/blob/jenkins-2.3/core/src/main/java/hudson/model/User.java#L1048 - Which is producing a recursive load.

@reviewbybees @andresrc @jtnord

@ghost
Copy link

ghost commented May 17, 2016

This pull request originates from a CloudBees employee. At CloudBees, we require that all pull requests be reviewed by other CloudBees employees before we seek to have the change accepted. If you want to learn more about our process please see this explanation.

@jtnord
Copy link
Member

jtnord commented May 17, 2016

In ActiveDirectoryUserDetail.updateUserInfo() you already know the users ID so should not call User.get() but [user.getByID(String, boolean)](http://javadoc.jenkins-ci.org/hudson/model/User.html#getById%28java.lang.String, boolean%29)

@andresrc
Copy link

andresrc commented May 17, 2016

@fbelzunc surely @jtnord had more insight.

@fbelzunc
Copy link
Contributor Author

fbelzunc commented May 17, 2016

@jtnord If I use your suggestion then I need to have jenkins as bigger version jenkins-1.651.2 in the pom.xml as your fix is very recent.

jenkinsci/jenkins@49d10a9

Do you suggest to do this?

@fbelzunc fbelzunc closed this May 17, 2016
@fbelzunc fbelzunc reopened this May 17, 2016
@andresrc
Copy link

@fbelzunc I would keep the original fix then and include a TODO to update it when the baseline is incremented

@fbelzunc
Copy link
Contributor Author

@andresrc I knew someone would say this :-) @jtnord WDYT?

@jtnord
Copy link
Member

jtnord commented May 17, 2016

well the problem with keeping the original is IIUC that you will constantly be hitting ActiveDirectoryUser.updateUserInfo() which will call back into the plugin and it is only via the User.get self defense against recursive callbacks that you survive, so introducing a performance regression.

The plugin implementation without the latest fix is wrong, if there is any call for a TODO IMO then it should be in the pom.xml to remove the HPI hack for the lowest compatible core to be removed once the company that is behind the maintainance of this plugin no longer supports < 1.651.2.

@jtnord
Copy link
Member

jtnord commented May 17, 2016

when I was originally discussing implications with @jglick I recall the pom/hpi hack was what he suggested - so ping'ing him in case I remember incorrectly.

<index>true</index>
<manifestEntries>
<Hudson-Version>1.609.18.1</Hudson-Version>
<Jenkins-Version>1.609.18.1</Jenkins-Version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm…rather dangerous. It would be much too easy to inadvertently introduce a dependency on some unrelated API added between 1.609 and 1.651.

(In jenkinsci/pipeline-plugin#197 I used this trick only for a baseline swap within an LTS line, where the risk of such an added API was quite low.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather suggest reflection for such cases:

try { // TODO 1.651.2+ remove reflection
    return (hudson.model.User) hudson.model.User.class.getMethod("getById", String.class, boolean.class).invoke(getUsername(), true);
} catch (NoSuchMethodException x) {
    // fine, older baseline
} catch (Exception x) { // unexpected
    LOGGER.log(Level.WARNING, null, x);
}
return hudson.model.User.get(getUsername());

Copy link
Member

@stephenc stephenc May 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+∞ on using reflection with a //TODO to replace with direct call

Copy link
Member

@jtnord jtnord May 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

problem with reflection is we must use the new call. so it is not fine if it does not exist. (return hudson.model.User.get(getUsername()); will cause a stack trace detailed in JENKINS-34426)
You would still need to set a baseline of jenkins to use. so what would you use?
1.651.2 - can't plugin won;t be available for Cloudbees customers.
1.609.18.2 - can't plugin won't be buildable as we do not publish cloudbees propriatary wars...
1.609.3 - well will still blow up when the API is not available via reflection...

So you still will end up installing this plugin on something without the core update which will break the plugin. so given you need to barf we may as well just barf and keep code the same...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you install it on something without the core update the reflection will not find the method and fall through to the old code path, so no worse than today. On the core versions with the method, reflection will find and invoke the method and all will be well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course one problem with a reflection-based approach is that hpi:run and any automated in-plugin tests will be using an old core that does not reflect the behavior of current users. Acceptance tests would be the only things catching “facepalm” regressions.

@jglick
Copy link
Member

jglick commented May 17, 2016

What is the root cause here? resolveCanonicalId does have a (tested) defense against stack overflows. But it seems that this SecurityRealm’s implementation of loadUserByUsername is not reëntrant?

@fbelzunc
Copy link
Contributor Author

What is the root cause here? resolveCanonicalId does have a (tested) defense against stack overflows. > But it seems that this SecurityRealm’s implementation of loadUserByUsername is not reëntrant?

@jglick right

@andresrc
Copy link

@jglick Exactly, the cache is not reentrant.

// fine, older baseline
} catch (Exception e) { // unexpected
LOGGER.log(Level.WARNING, String.format("There was a problem obtaining the Jenkins user %s by Id", getUsername()), e);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe InvocationTargetException should be caught to unwind any legitimate exception getById may throw.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andresrc Are you sure it is necessary? getById should not throw any exception. So if .invoke fails you will just go tot the catch of Exception to record it. Is it not fine?

@andresrc
Copy link

I would keep the updateUserInfo outside the Callable anyway to remove the risk of reentrant calls.

@fbelzunc
Copy link
Contributor Author

@stephenc @andresrc @jtnord This should be fine now - can I have your final review with a bee, please? ;-)

@@ -330,7 +330,7 @@ public UserDetails call() throws AuthenticationException {
closeQuietly(context);
}
}
});
}).updateUserInfo();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will now cause the user info to be updated always. Previously we only updated the info if the value was not in the cache

@stephenc
Copy link
Member

I think this reintroduces a 🐛 whereby the user details are continually being saved with no actual changes. You probably want to have a final boolean[] needsUpdate = new boolean[1]; just before this line then inside the callable you can needsUpdate[0] = true and finally you then do if (needsUpdate[0]) ...

better yet you could revert the type changes in the cache and just have a final ActiveDirectoryUserDetail[] cacheMiss = new ActiveDirectoryUserDetail[1] and then if (cacheMiss[0] != null) cacheMiss[0].updateUserInfo();

@@ -312,11 +317,12 @@ public UserDetails call() throws AuthenticationException {
Set<GrantedAuthority> groups = resolveGroups(domainDN, dnFormatted, context);
groups.add(SecurityRealm.AUTHENTICATED_AUTHORITY);

return new ActiveDirectoryUserDetail(username, password, true, true, true, true, groups.toArray(new GrantedAuthority[groups.size()]),
cacheMiss[0] = new ActiveDirectoryUserDetail(username, password, true, true, true, true, groups.toArray(new GrantedAuthority[groups.size()]),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this does not look thread safe to me. cacheMiss is a class level field...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I asked for a method scoped field

@jtnord
Copy link
Member

jtnord commented May 18, 2016

🐛 for the threadsafety issue.

I would keep the updateUserInfo outside the Callable anyway to remove the risk of reentrant calls.

I would disagree :-)

/**
* To determinate if we need or not to update the User Information
*/
final ActiveDirectoryUserDetail[] cacheMiss = new ActiveDirectoryUserDetail[1];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must be a local variable in retrieveUser to avoid threading issues.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐛 I said add it at line 322 (now 319) not at the class level

@jtnord
Copy link
Member

jtnord commented May 18, 2016

I think this reintroduces a 🐛 whereby the user details are continually being saved with no actual changes.

Partially incorrect[1] - the code checks and only makes changes to the User object if they are different - thus no save if the Users details are the same.

See - ActiveDirectoryUserDetail.updateUserDetails()

Now that code is not correctly checking - and if you get a cache miss due to the cache timeout etc then you are still also making saves for no valid reason as there is no check that the email is different from what is stored. So the bug (unneeded saves) is still present - perhaps this should just be addressed.

[1] partially incorrect - as the bug is still there so not re-introduced - just hit more often :)

@andresrc
Copy link

I would disagree :-)

Normally I wouldn't agree with myself as well :), but in this specific one updates are only performed in some very specific cases, not always as @stephenc said and provides a safety net against core change behaviors like the one triggering this fix.

In any case, detecting the cache miss (correctly) is much better.

@jtnord
Copy link
Member

jtnord commented May 18, 2016

the whole cachemiss thing looks very threading issue prone to me if it is done outside the loop.

What if the same user is attempting to login from 2 different computers simultaneously (say automation tooling)?

Due to threading nature the User object for one could be returned before it is correctly updated (even created!) if the thread that populates the cache is paused before it updates the user.
This sounds nit picking but it seems like a 🐛 waiting to happen...

@andresrc
Copy link

@jtnord nit but true. The alternative would be detecting the "reentrancy" at the retrieveUser method level and bypass the cache in that case. The outer cache call would provide the needed thread safety.

@jtnord
Copy link
Member

jtnord commented May 18, 2016

In any case, detecting the cache miss (correctly) is much better.

It would be if you could gaurantee that a cache hit for the same user would not overtake the cache miss and population before creating the user object in updateUserInfo.
This is not synchronized - and I think any attempt to make it synchronized will kill performance :-o

@andresrc
Copy link

It would be if you could gaurantee that a cache hit for the same user would not overtake the cache miss and population before creating the user object in updateUserInfo.

@jtnord you are right

@jtnord
Copy link
Member

jtnord commented May 18, 2016

@andresrc we have not had any re-entry so far - so that bug just does not happen.
with the new API we hit the re-entry so the fix was to update the code to use the new API.

Big bugs like re-entry are easy to find and fix. Bugs due to threading issues are a PITA to uncover.

@andresrc
Copy link

Big bugs like re-entry are easy to find and fix. Bugs due to threading issues are a PITA to uncover.

@jtnord agree

@stephenc
Copy link
Member

FFS cacheMiss should be a method scoped final variable

@@ -97,13 +97,18 @@
/**
* The {@link UserDetails} cache.
*/
private final Cache<String, UserDetails> userCache;
private final Cache<String, ActiveDirectoryUserDetail> userCache;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be a UserDetail, I suggest reverting this

@fbelzunc
Copy link
Contributor Author

reflection is not done correctly - the method is static so need to start by null

@@ -177,6 +178,19 @@ public static long getSerialVersionUID() {
* Gets the corresponding {@link hudson.model.User} object.
*/
public hudson.model.User getJenkinsUser() {
try { // TODO 1.651.2+ remove reflection
return (hudson.model.User) hudson.model.User.class.getMethod("getById", String.class, boolean.class).invoke(getUsername(), true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invoke(null, getUsername(), true)

@stephenc
Copy link
Member

🐛 in reflection

@fbelzunc
Copy link
Contributor Author

@stephenc @jtnord @andresrc I prefer not to use the canMiss for the reasons @jtnord explained - in any case I learned with your comments.

Now, I am just using reflection and correctly using invoke(null, getUsername(), true) with first argument as ´null´ as it is a static method.

Can I have your final review, please? I think now should be fine.

@stephenc
Copy link
Member

🐛 put the cacheMiss stuff back, when it is a method scoped variable there is no race condition as the closure does not escape the life of the method call. James was objecting to the use of an instance field

@fbelzunc
Copy link
Contributor Author

@stephenc I don't like a lot to have that - but I am doing it right now :-)

@stephenc
Copy link
Member

@fbelzunc you had it right and then you decided to remove for no clear reason. There are other ways to achieve the same, eg if you created the closure as a variable then the closure could have a field to hold the return value. But ultimately we need the update to be defanged from the code path it was on and the final array trick is the smallest change to affect this

@fbelzunc
Copy link
Contributor Author

fbelzunc commented May 18, 2016

@stephenc It is now update with the cacheMiss - I want to release this before it kicks the ass of a lot of people :-)

@stephenc
Copy link
Member

🐝

@jtnord
Copy link
Member

jtnord commented May 18, 2016

No stephen - you had it wrong. THere is also a reace condition here as the
method is not synchronized and the same user can attempt to login from
multipe locations at the same time (e.g. automation scripts)

and only when there is a cache miss will the jenkins user be created and
updated. however this is done after the cache has populated - so there is
a risk that the second login attempt will see the info in the cache and use
that - but the before the User has been properly initialized. Not sure
what the issue would be with this - but it is a possibility and I would
rather debug a reentry issue which will be obvious in the stack trace than
some very subtle threadin issue which will have no obvious cause.

On 18 May 2016 at 20:05, Stephen Connolly [email protected] wrote:

[image: 🐛] put the cacheMiss stuff back, when it is a method scoped
variable there is no race condition as the closure does not escape the life
of the method call. James was objecting to the use of an instance field


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#34 (comment)

@jtnord
Copy link
Member

jtnord commented May 18, 2016

🐛 as there is a race condition on retreiveUser with when the user details are set when the same user logs in from 2 different machines at the same time.

@jtnord
Copy link
Member

jtnord commented May 18, 2016

@stephenc @fbelzunc @andresrc perhaps we want a hangout to discuss this (and anyone else that is interested!)

@fbelzunc
Copy link
Contributor Author

@jtnord Happy to do it - I can ping you tomorrow morning.

@stephenc
Copy link
Member

If there re two threads they will both have the same object and one of them will have had a cache miss and hence do the update. Since a user "could" be updating their user record in parallel with a slow page render for that same user from a different browser, this is an exactly analogous case.

Yes it looks like a race, but Jenkins doesn't care about or provide a framework to prevent concurrent modification, so it should not be an issue

@jtnord
Copy link
Member

jtnord commented May 18, 2016

I don't really care about concurrent modification per-say (last wins so long as there is one) - e.g. the case where something gets an unpopulated User such that the build fails to send an email, (which is an issue) but more as where else the User will be created, so it may not be created such that you get null elsewhere which is definitely a bad thing as the second login attempt completes due to the cache lookup but before updateUserInfo which creates the User object.. (the core is explicit when you want to create a User vs retrieve an existing one - such that you could possible get null for User.get() when the user has just successfully logged in)

@stephenc
Copy link
Member

So the case you are concerned about requires:

  • a multi-core server
  • heavy thread contention (ie under above capacity load)
  • a user trying to login in two separate browser sessions at the same time

And even then it requires a pre-emptive interrupt of the first thread (as it's not going to yield on releasing the lock)

Two of those are, IMHO, exceedingly difficult. The likely case for the second thread's page render is a glitch, or worst a stack trace that gets cleared on a page reload or worst case a logout/login again

The other side is a definite recursion issue that has been encountered.

If we want to "do the completely right thing" then we need to come at this from a different line of attack. You have not convinced me that the recursion (which will likely repeat for that user as they were unable to login and hence will re-trigger on subsequent attempts) is the lesser evil

@jtnord
Copy link
Member

jtnord commented May 18, 2016

does anyone run on a single core server these days :-)

heavy thread contention (ie under above capacity load)

not really - you just need to have more threads than CPU cores in a runnable state and that is not so hard. When the JVM switches to a different thread is non deterministic, so long as there is a single thread that is waiting to run any other thread could be paused...

The likely case for the second thread's page render is a glitch

It may not be rendering a web page - it could be performing a a REST operation - a HTTP/503 response should not be retried by a tool.

The other side is a definite recursion issue that has been encountered.

It is - but only because we actively changed core to call into the authentication realm on user.get() and we where calling user.get inside this code that core called into.

Up to now there has not been any other recursion issues (it would have caused a spinning thread or a stack trace - and there are no reports of this as far as I am aware)

The other side is a definite recursion issue that has been encountered.

Which has been fixed by using the new API user.getById(id).

You have not convinced me that the recursion (which will likely repeat for that user as they were unable to login and hence will re-trigger on subsequent attempts) is the lesser evil

it's not a lesser evil - but with the fix we should have squashed the recursive call - so there should not be a possibility of recursion.

Yes someone in the future could change something again - but we can then change the code again, but right now I see the chance of recursion as zero and the chance of the threading issue as very very very tiny. Very very very tiny is bigger than zero - hence my concern that what we are doing here is wrong. So the lesser evil to me is to need to make the code change again in the future to fix a bug that does not exist today vs accepting a bug that exists today but is extremely hard to reproduce - let alone understand from any stack trace you may get..

@stephenc
Copy link
Member

So the contract for AbstractPasswordBasedSecurityRealm.authenticate(String,String) does not mandate the creation of the hudson.model.User rather it is all about the UserDetails.

There are other security realms that return a UserDetails without doing any checks for the hudson.model.User. Thus it is perfectly valid for us to return a UserDetails when the corresponding User has not even been created. In fact for some security realms we can guarantee that the User would be null on first login...

So what difference does it make if we return the ActiveDirectoryUserDetail from the cache before the ActiveDirectoryUserDetail that was instantiated has done it's update?

The only difference is that other Jenkins code may race to instantiate the hudson.model.User object... but User.getOrCreate() and User.getById() should both be thread safe if written correctly... In other words, if there is a threading issue then that is a bug in core not in this plugin.

@jtnord
Copy link
Member

jtnord commented May 19, 2016

🐝

@fbelzunc
Copy link
Contributor Author

@reviewbybees done

@fbelzunc fbelzunc force-pushed the JENKINS-34426-v3 branch from 704e317 to ed81365 Compare May 19, 2016 12:22
@fbelzunc fbelzunc merged commit 57f4d97 into jenkinsci:master May 19, 2016
yaroslavafenkin pushed a commit to yaroslavafenkin/active-directory-plugin that referenced this pull request Nov 1, 2024
[JENKINS-62317]: Upgrade dependencies pre 2.3 release
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants