-
-
Notifications
You must be signed in to change notification settings - Fork 139
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
Add insight into dual initialization errors #146
Comments
I can see where extra detail may help, but getting a stack trace is relatively expensive for a use case that happens very rarely. Maybe an environment variable could be added that causes a stack dump to be printed when |
That's a valid concern. From what I can tell, there are two parts to getting a stack trace in Java – obtaining it in some sort of native representation under the hood, and then translating it into Java StackTraceElement objects – and the second piece is the expensive part (ref). If we just create the exception in advance then we avoid the expensive part until and unless that exception actually needs to be set as a cause and thrown. Is that valid? Unfortunately, the environment I'm using this in (Android) doesn't seem to have a way to statically declare environment variables so that they're set before anything happens in the process, and the OS doesn't really give any control over how the app process is launched. There are APIs to set environment variables at runtime, but if I'm already running into an issue where initializing as soon as possible isn't soon enough to preempt implicit initialization, then I'd run into the same issue with setting the environment variable at runtime. Another approach to narrow this down might be to treat the default |
While Back to the issue at hand, though. Whatever the size of your codebase, your Android application will normally start with a call to Failing that, if the SPI-based zone rules initializer runs before you can set a custom initializer, then perhaps provide a dummy ZoneRulesProvider through SPI that throws (or reports) exceptions in each of its methods. That should give you the same information that a filled-in stacktrace would. That would require:
public class TracingZoneRulesProvider extends ZoneRulesProvider {
@Override
protected Set<String> provideZoneIds() {
throw new RuntimeException("using default zone rules initializer");
}
@Override
protected ZoneRules provideRules(String regionId, boolean forCaching) {
throw new RuntimeException("using default zone rules initializer");
}
@Override
protected NavigableMap<String, ZoneRules> provideVersions(String zoneId) {
throw new RuntimeException("using default zone rules initializer");
}
} If you set a custom initializer in time, then the tracing provider is never instantiated. Otherwise, the default initializer will create the tracing provider and will crash with an For more information on SPI on Android, see https://developer.android.com/reference/java/util/ServiceLoader |
Good insight on the stack trace performance concerns. TracingZoneRulesProvider is a great idea, I'll give that a try. Thank you! |
@sschaap - have you had success using ServiceLoader on Android? It appears that most build systems strip out META-INF from the final binary. For what it's worth, my app builds with Buck. |
The Android projects that I work on specifically avoid use of ServiceLoader for performance reasons. When we started using TTBP it turned out that use of ServiceLoader made Android load all dex files and scan the entire classpath, which took too much time during app startup. With the introduction of That said, I'm not aware of any inherent problems in using SPI architecture on Android. As far as I know, TTBP "just works" when included as a dependency on Android. Given that TTBP uses Additionally, I validated the If you want, I can share the sandbox project for reference. |
That's good to know, sounds like not including META-INF is a Buck-specific issue then. If you could share that sandbox project that would be great. Agreed on the performance issues with ServiceLoader on Android – I created ZoneRulesInitializer for that reason, as especially with a huge binary like ours going linearly through everything was taking forever. LazyThreeTen also helped us defer loading from resources until later in the startup sequence, although by now ThreeTenBP is used so pervasively throughout our codebase that it's probably not deferred by long anymore. One framework which I just discovered (I've been away from the Android world for a bit) is androidx.startup, which would allow this to go into an Initializer that would run before Application.onCreate. Seems promising, I might try it out and see how peer code reviewers like it :) |
Sample project: https://github.com/sschaap/threetenbp-issue-146 Looking at the buck docs, you may need to add META-INF as resources for your android_library() rule (docs) |
Thanks! I missed that in the Buck docs, I'll have to play around with it to see if it can structure the directories properly. I was thrown off by java_binary having a separate meta_inf_directory argument. |
Closing, as I don't think there is anything sensible for ThreeTenBp to do here. |
Complex applications making use of a custom initializer occasionally run into issues where the implicit static initialization has been called before the explicit one has had a chance to run. Of course the solution is to ensure that the custom initializer is set up before anything else happens in the process, but in large codebases accomplishing this is sometimes more complex than it should be, and references to ThreeTenBP that trigger
ZoneRulesProvider
can sometimes slip through earlier in the startup sequence. When that happens, an "Already initialized" crash happens at the intentional explicit setInitializer call, but it is difficult to find the source of the implicit initialization. I propose on any initializer set, implicit or explicit, keep track of the stack trace so that a potential future "Already initialized" error can include the stack trace of the earlier call.My proposal is to update
AtomicReference<ZoneRulesInitializer> INITIALIZER
to store both aZoneRulesInitializer
as well as aStackTraceElement[]
. For completeness, maybe also replaceAtomicBoolean INITIALIZED
with anAtomicReference<StackTraceElement[]>
, to keep track of bothsetInitializer
andinitialize
calls as both can later cause issues. When the IllegalStateExceptions are thrown, add a new exception as its cause with the stack trace set to the appropriate saved one. It might even be best to include both in the case of duplicate initialization, where the IllegalStateException would be caused by the duplicate initialization exception which would be caused by the set initializer stack trace (if it exists).Thoughts? This is something I can put into code form if it seems like the appropriate approach to this problem.
The text was updated successfully, but these errors were encountered: