-
Notifications
You must be signed in to change notification settings - Fork 19
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
Re-factor IdentityServer init. to fix race conditions + tests #55
Re-factor IdentityServer init. to fix race conditions + tests #55
Conversation
Several issues being fixed: - FR was statically initializing the `IDENTITY_SERVER` `AtomicReference` to a placeholder instance that provided different information to other IDM components _during_ initialization than the final instance did _after_ initialization. - FR was using an AtomicBoolean to guard updates to the the `IDENTITY_SERVER` `AtomicReference`, without explicitly using a `synchronized` critical section. So, any calls into the `IdentityService` instance between the time the server was marked "initialized" and the time the instance was set would actually be interacting with the default (placeholder) instance without realizing it. - Because of the behaviors above, code like `SelfService` came to rely on being able to get an `IdentityServer` instance during its static class initializer. This was bad -- depending upon whether the `SelfService` class was loaded before, during, or after `IdentityServer` was fully initialized, it would be getting informaton from either the default (placeholder) instance of `IdentityServer` or the actual instance. - `IdentityServer.getInstance()` was checking for initialization with a `null` check that would never fail, because the value was always initialized to a non-null value. - `IdentitySever.initInstance(IdentityServer)` would not throw a `IllegalStateException` if it was passed a `null` value. Now: - Both the the `IDENTITY_SERVER` `AtomicReference` and the `INITIALISED` `AtomicBoolean` are gone. - There's just one volatile static instance variable that's initialized in a `synchronized` critical section. - All components of IDM that need an instance of `IdentityServer` can only interact with it once it's been initialized; this is now enforced properly by the `getInstance()` method. - `SelfService` uses a shared key alias that is lazily-initialized by a double-checked locking pattern, so that it doesn't need the `IdentityServer` to be initialized at class loading time. - There are tests that actually cover both overloads of `initInstance()`. - A utility class now exists for tests to manipulate the state of `IdentityServer`.
- Fix broken tests by making sure all tests that depend on `IdentityServer` initialize it before or during the test. - Fix test failures in `openidm-security` that were dependent on file order. Here's what was happening: - If `EntryResourceProviderTest` was loaded by the JVM before `PrivateKeyResourceProviderTest`, then all tests would pass. - If `EntryResourceProviderTest` was loaded by the JVM after `PrivateKeyResourceProviderTest`, then tests for `EntryResourceProviderTest`, `KeystoreResourceProviderTest`, and `PrivateKeyResourceProviderTest` would fail.
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.
Haven't build it (deferring the thorough review to @pavelhoral) but the concurrency part looks good.
Just as an FYI and not specifically applicable to this situation but a good pattern to know for lazy initialization is the Initialization-on-demand holder pattern. Allows for safe and lock-free (!) lazy initialization.
Just a quick question before I dive into the changes: What was the observed issue being solved here? Is this about the "strangely failing" unit tests? Because in normal operation |
@pavelhoral I don't know the exact semantics but I would be cautious to rely on the OSGi start order (ie. OSGi run level). The bundle run level only guards the starting of the bundle which in turn guarantees the bundle's context activator has been called and has returned. However I suspect (haven't checked it) that most stuff in IDM gets activated by SCR. And OSGi will be starting bundles with other run levels even though the bundles with a lower run level might still be starting their SCR stuff. |
AFAIK that is not the case of |
As requested, here's all the context about the issue (from Gitter chat):
|
also, @pavelhoral, there's a bit more detail at the end of my PR description:
|
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 just have a few comments which I don't consider to be "blocking the PR":
- I am not a super fan of powermock dependency. I would rather add some additional methods to
IdentityServer
. - I have zero experience with
test-jar
dependencies, so I am a bit cautious about those.
Yeah, and then there are my formatting "sigh" comments.. you can ignore those as I just wanted to express my discomfort reading that code.
* what has been provided. | ||
*/ | ||
public static void initInstanceForTest(final String projectLocation, | ||
final String installLocation) |
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 formatting makes me sad.
Whitebox.invokeConstructor( | ||
IdentityServer.class, | ||
new Class[] { PropertyAccessor.class }, | ||
new Object[] { null }); |
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 formatting makes me sad.
* what has been provided. | ||
*/ | ||
public static void verifyServerInitPaths(final String projectLocation, | ||
final String installLocation) |
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 formatting makes me sad.
String rev = capturedAfter.get(ResourceResponse.FIELD_CONTENT_REVISION).asString(); | ||
assertThat(rev).isEqualTo("2"); | ||
} | ||
finally { |
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 formatting makes me sad.
I'll address a few of the formatting concerns. My goal is still to keep lines < 100 chars when we can (generics tend to be my only exception), since alas > 120 chars makes PR review on GH painful and doesn't fit well on my screens. |
cleans up some issues pointed out during review.
Changes approach to using a package-private method for clearing state, and a new method for checking initialization status.
Removing "\n" on Windows leaves "\r", which causes each line of the certificate to overwrite the previous one. This fix ensures that the test works properly on both environments.
@pavelhoral back to you. my changes:
Test JARs are nothing to worry about -- I'm using it here just so we can re-use the test utility class across projects. Test classpaths are not transitive in Maven, so you have to export any test classes like this in order to use them accross modules. |
cleans up some issues pointed out during review.
Changes approach to using a package-private method for clearing state, and a new method for checking initialization status.
Several issues being fixed:
IDENTITY_SERVER
AtomicReference
to a placeholder instance that provided different information to other IDM components during initialization than the final instance did after initialization.IDENTITY_SERVER
AtomicReference
, without explicitly using asynchronized
critical section. So, any calls into theIdentityService
instance between the time the server was marked "initialized" and the time the instance was set would actually be interacting with the default (placeholder) instance without realizing it.SelfService
came to rely on being able to get anIdentityServer
instance during its static class initializer. This was bad -- depending upon whether theSelfService
class was loaded before, during, or afterIdentityServer
was fully initialized, it would be getting informaton from either the default (placeholder) instance ofIdentityServer
or the actual instance.IdentityServer.getInstance()
was checking for initialization with anull
check that would never fail, because the value was always initialized to a non-null value.IdentitySever.initInstance(IdentityServer)
would not throw aIllegalStateException
if it was passed anull
value.Now:
IDENTITY_SERVER
AtomicReference
and theINITIALISED
AtomicBoolean
are gone.synchronized
critical section.IdentityServer
can only interact with it once it's been initialized; this is now enforced properly by thegetInstance()
method.SelfService
uses a shared key alias that is lazily-initialized by a double-checked locking pattern, so that it doesn't need theIdentityServer
to be initialized at class loading time.initInstance()
.IdentityServer
.IdentityServer
initialize it before or during the test.openidm-security
that were dependent on file order. Here's what was happening:EntryResourceProviderTest
was loaded by the JVM beforePrivateKeyResourceProviderTest
, then all tests would pass.EntryResourceProviderTest
was loaded by the JVM afterPrivateKeyResourceProviderTest
, then tests forEntryResourceProviderTest
,KeystoreResourceProviderTest
, andPrivateKeyResourceProviderTest
would fail.