Remove extra lock and racy assert in class initialization
Change-Id: Idaf68cbf888b5edc5e05877da6a20b86bdfb4762
diff --git a/src/class_linker.cc b/src/class_linker.cc
index 1e0cdb1..aa69c30 100644
--- a/src/class_linker.cc
+++ b/src/class_linker.cc
@@ -1304,84 +1304,52 @@
}
bool ClassLinker::InitializeClass(Class* klass, bool can_run_clinit) {
- CHECK(klass->GetStatus() == Class::kStatusResolved ||
- klass->GetStatus() == Class::kStatusVerified ||
- klass->GetStatus() == Class::kStatusInitializing ||
- klass->GetStatus() == Class::kStatusError)
- << PrettyClass(klass) << " is " << klass->GetStatus();
+ CHECK(klass->IsResolved() || klass->IsErroneous())
+ << PrettyClass(klass) << " is " << klass->GetStatus();
Thread* self = Thread::Current();
Method* clinit = NULL;
{
+ // see JLS 3rd edition, 12.4.2 "Detailed Initialization Procedure" for the locking protocol
ObjectLock lock(klass);
- if (klass->GetStatus() < Class::kStatusVerified) {
- if (klass->IsErroneous()) {
- ThrowEarlierClassFailure(klass);
- return false;
- }
+ if (klass->GetStatus() == Class::kStatusInitialized) {
+ return true;
+ }
+ if (klass->IsErroneous()) {
+ ThrowEarlierClassFailure(klass);
+ return false;
+ }
+
+ if (klass->GetStatus() == Class::kStatusResolved) {
VerifyClass(klass);
if (klass->GetStatus() != Class::kStatusVerified) {
return false;
}
}
- if (klass->GetStatus() == Class::kStatusInitialized) {
- return true;
- }
-
clinit = klass->FindDeclaredDirectMethod("<clinit>", "()V");
if (clinit != NULL && !can_run_clinit) {
- // if the class has a <clinit>, don't bother going to initializing
+ // if the class has a <clinit> but we can't run it during compilation,
+ // don't bother going to kStatusInitializing
return false;
}
- while (klass->GetStatus() == Class::kStatusInitializing) {
+ // If the class is kStatusInitializing, either this thread is
+ // initializing higher up the stack or another thread has beat us
+ // to initializing and we need to wait. Either way, this
+ // invocation of InitializeClass will not be responsible for
+ // running <clinit> and will return.
+ if (klass->GetStatus() == Class::kStatusInitializing) {
// We caught somebody else in the act; was it us?
if (klass->GetClinitThreadId() == self->GetTid()) {
- // Yes. That's fine.
+ // Yes. That's fine. Return so we can continue initializing.
return true;
}
-
- CHECK(!self->IsExceptionPending());
- lock.Wait();
- CHECK(!self->IsExceptionPending());
-
- // When we wake up, repeat the test for init-in-progress. If
- // there's an exception pending (only possible if
- // "interruptShouldThrow" was set), bail out.
- if (self->IsExceptionPending()) {
- self->ThrowNewException("Ljava/lang/ExceptionInInitializerError;",
- "Exception %s thrown while initializing class %s",
- PrettyTypeOf(self->GetException()).c_str(),
- PrettyDescriptor(klass->GetDescriptor()).c_str());
- klass->SetStatus(Class::kStatusError);
- return false;
- }
- if (klass->GetStatus() == Class::kStatusInitializing) {
- continue;
- }
- DCHECK(klass->GetStatus() == Class::kStatusInitialized ||
- klass->GetStatus() == Class::kStatusError);
- if (klass->IsErroneous()) {
- // The caller wants an exception, but it was thrown in a
- // different thread. Synthesize one here.
- self->ThrowNewException("Ljava/lang/UnsatisfiedLinkError;",
- "<clinit> failed for class %s; see exception in other thread",
- PrettyDescriptor(klass->GetDescriptor()).c_str());
- return false;
- }
- return true; // otherwise, initialized
- }
-
- // see if we failed previously
- if (klass->IsErroneous()) {
- // might be wise to unlock before throwing; depends on which class
- // it is that we have locked
- ThrowEarlierClassFailure(klass);
- return false;
+ // No. That's fine. Wait for another thread to finish initializing.
+ return WaitForInitializeClass(klass, self, lock);
}
if (!ValidateSuperClassDescriptors(klass)) {
@@ -1389,7 +1357,7 @@
return false;
}
- DCHECK_LT(klass->GetStatus(), Class::kStatusInitializing);
+ DCHECK_EQ(klass->GetStatus(), Class::kStatusVerified);
klass->SetClinitThreadId(self->GetTid());
klass->SetStatus(Class::kStatusInitializing);
@@ -1409,6 +1377,8 @@
ObjectLock lock(klass);
if (self->IsExceptionPending()) {
+ // TODO: if self->GetException() is not an Error,
+ // wrap in ExceptionInInitializerError
klass->SetStatus(Class::kStatusError);
} else {
++Runtime::Current()->GetStats()->class_init_count;
@@ -1422,6 +1392,43 @@
return true;
}
+bool ClassLinker::WaitForInitializeClass(Class* klass, Thread* self, ObjectLock& lock) {
+ while (true) {
+ CHECK(!self->IsExceptionPending());
+ lock.Wait();
+
+ // When we wake up, repeat the test for init-in-progress. If
+ // there's an exception pending (only possible if
+ // "interruptShouldThrow" was set), bail out.
+ if (self->IsExceptionPending()) {
+ // TODO: set cause of ExceptionInInitializerError to self->GetException()
+ self->ThrowNewException("Ljava/lang/ExceptionInInitializerError;",
+ "Exception %s thrown while initializing class %s",
+ PrettyTypeOf(self->GetException()).c_str(),
+ PrettyDescriptor(klass->GetDescriptor()).c_str());
+ klass->SetStatus(Class::kStatusError);
+ return false;
+ }
+ // Spurious wakeup? Go back to waiting.
+ if (klass->GetStatus() == Class::kStatusInitializing) {
+ continue;
+ }
+ if (klass->IsErroneous()) {
+ // The caller wants an exception, but it was thrown in a
+ // different thread. Synthesize one here.
+ self->ThrowNewException("Ljava/lang/NoClassDefFoundError;",
+ "<clinit> failed for class %s; see exception in other thread",
+ PrettyDescriptor(klass->GetDescriptor()).c_str());
+ return false;
+ }
+ if (klass->IsInitialized()) {
+ return true;
+ }
+ LOG(FATAL) << "Unexpected class status. " << PrettyClass(klass) << " is " << klass->GetStatus();
+ }
+ LOG(FATAL) << "Not Reached" << PrettyClass(klass);
+}
+
bool ClassLinker::ValidateSuperClassDescriptors(const Class* klass) {
if (klass->IsInterface()) {
return true;
@@ -1550,9 +1557,7 @@
Thread* self = Thread::Current();
ScopedThreadStateChange tsc(self, Thread::kRunnable);
- c->MonitorEnter(self);
InitializeClass(c, can_run_clinit);
- c->MonitorExit(self);
return !self->IsExceptionPending();
}