You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by da...@apache.org on 2003/08/18 23:56:27 UTC
cvs commit: incubator-geronimo/modules/core/src/java/org/apache/geronimo/common AbstractStateManageable.java
dain 2003/08/18 14:56:27
Modified: modules/core/src/java/org/apache/geronimo/common
AbstractStateManageable.java
Log:
Addded synchronization code to fix multi threaded state change issues.
Changed relationship code to assume relationship type is always
registered as the relation service does not send notifications when
a relation type is added or removed.
Revision Changes Path
1.5 +320 -94 incubator-geronimo/modules/core/src/java/org/apache/geronimo/common/AbstractStateManageable.java
Index: AbstractStateManageable.java
===================================================================
RCS file: /home/cvs/incubator-geronimo/modules/core/src/java/org/apache/geronimo/common/AbstractStateManageable.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- AbstractStateManageable.java 18 Aug 2003 13:30:19 -0000 1.4
+++ AbstractStateManageable.java 18 Aug 2003 21:56:27 -0000 1.5
@@ -69,9 +69,11 @@
import javax.management.NotificationFilterSupport;
import javax.management.NotificationListener;
import javax.management.ObjectName;
+import javax.management.ReflectionException;
import javax.management.relation.InvalidRelationIdException;
import javax.management.relation.InvalidRoleValueException;
import javax.management.relation.RelationNotFoundException;
+import javax.management.relation.RelationNotification;
import javax.management.relation.RelationServiceMBean;
import javax.management.relation.RelationServiceNotRegisteredException;
import javax.management.relation.RelationTypeNotFoundException;
@@ -85,9 +87,9 @@
import org.apache.geronimo.deployment.dependency.DependencyServiceMBean;
import org.apache.geronimo.deployment.service.MBeanRelationship;
import org.apache.geronimo.jmx.JMXUtil;
+import org.apache.management.j2ee.NotificationType;
import org.apache.management.j2ee.State;
import org.apache.management.j2ee.StateManageable;
-import org.apache.management.j2ee.NotificationType;
/**
* Abstract implementation of JSR77 StateManageable.
@@ -104,20 +106,57 @@
private DependencyServiceMBean dependencyService;
private RelationServiceMBean relationService;
private long sequenceNumber;
- private State state = State.STOPPED;
private long startTime;
+ // This must be volatile otherwise getState must be synchronized which will result in deadlock as dependent
+ // objects check if each other are in one state or another (i.e., classic A calls B while B calls A)
+ private volatile State state = State.STOPPED;
+
+ /**
+ * Check if component can start. Dependencies in the dependency service have already been
+ * checked at this point.
+ *
+ * Note: this method is called from within a synchronized block, so be careful what you call as you
+ * may create a deadlock.
+ *
+ * @return true if the component can start; otherwise false
+ */
+ protected boolean canStart() {
+ return true;
+ }
+
/**
* Do the start tasks for the component. Called in the STARTING state by
* the start() and startRecursive() methods to perform the tasks required to
* start the component.
+ *
+ * Note: this method is called from within a synchronized block, so be careful what you call as you
+ * may create a deadlock.
+ *
* @throws Exception
*/
protected abstract void doStart() throws Exception;
/**
+ * Check if component can stop. Dependencies in the dependency service have already been
+ * checked at this point.
+ *
+ * Note: this method is called from within a synchronized block, so be careful what you call as you
+ * may create a deadlock.
+ *
+ * @return true if the component can stop; otherwise false
+ */
+ protected boolean canStop() {
+ return true;
+ }
+
+ /**
* Do the stop tasks for the component. Called in the STOPPING state by the stop()
* method to perform the tasks required to stop the component.
+ *
+ * Note: this method is called from within a synchronized block, so be careful what you call as you
+ * may create a deadlock.
+ *
* @throws Exception
*/
protected abstract void doStop() throws Exception;
@@ -136,10 +175,15 @@
dependencyService = JMXUtil.getDependencyService(server);
relationService = JMXUtil.getRelationService(server);
- NotificationFilterSupport filter = new NotificationFilterSupport();
- filter.enableType(MBeanServerNotification.REGISTRATION_NOTIFICATION);
- filter.enableType(MBeanServerNotification.UNREGISTRATION_NOTIFICATION);
- server.addNotificationListener(JMXUtil.DELEGATE_NAME, this, filter, null);
+ NotificationFilterSupport mbeanServerFilter = new NotificationFilterSupport();
+ mbeanServerFilter.enableType(MBeanServerNotification.REGISTRATION_NOTIFICATION);
+ mbeanServerFilter.enableType(MBeanServerNotification.UNREGISTRATION_NOTIFICATION);
+ server.addNotificationListener(JMXUtil.DELEGATE_NAME, this, mbeanServerFilter, null);
+
+ NotificationFilterSupport relationServiceFilter = new NotificationFilterSupport();
+ relationServiceFilter.enableType(RelationNotification.RELATION_BASIC_REMOVAL);
+ relationServiceFilter.enableType(RelationNotification.RELATION_MBEAN_REMOVAL);
+ server.addNotificationListener(JMXUtil.RELATION_SERVICE_NAME, this, relationServiceFilter, null);
return objectName;
}
@@ -168,6 +212,24 @@
} else if (MBeanServerNotification.UNREGISTRATION_NOTIFICATION.equals(type)) {
MBeanServerNotification notification = (MBeanServerNotification) n;
source = notification.getMBeanName();
+ } else if (RelationNotification.RELATION_BASIC_REMOVAL.equals(type) ||
+ RelationNotification.RELATION_MBEAN_REMOVAL.equals(type)) {
+
+ if(getStateInstance() == State.RUNNING) {
+ RelationNotification notification = (RelationNotification) n;
+ String relationType = notification.getRelationTypeName();
+ if(relationType != null) {
+ Set relationships = dependencyService.getRelationships(objectName);
+ for (Iterator i = relationships.iterator(); i.hasNext();) {
+ MBeanRelationship relationship = (MBeanRelationship) i.next();
+ if(relationType.equals(relationship.getType())){
+ checkState();
+ return;
+ }
+ }
+ }
+ }
+ return;
} else {
source = (ObjectName) n.getSource();
}
@@ -185,67 +247,152 @@
return state.toInt();
}
- public final long getStartTime() {
+ public synchronized final long getStartTime() {
return startTime;
}
+ /**
+ * Moves this MBean to the STARTING state and then attempst to move this MBean immedately to the STARTED
+ * state.
+ *
+ * Note: This method can not be call while the current thread holds a syncronized lock on this MBean,
+ * because this method sends JMX notifications. Sending a general notification from a synchronized block
+ * is a bad idea and therefore not allowed.
+ *
+ * @throws Exception If an exception occurs while starting this MBean
+ */
public final void start() throws Exception {
- State state = getStateInstance();
- if (state == State.STARTING || state == State.RUNNING) {
- return;
+ assert !Thread.holdsLock(this): "This method cannot be called while holding a syncrhonized lock on this";
+
+ // Move to the starting state
+ synchronized (this) {
+ State state = getStateInstance();
+ if (state == State.STARTING || state == State.RUNNING) {
+ return;
+ }
+ setState(State.STARTING);
}
+ doNotification(State.STARTING.getEventTypeValue());
+
+ State newState = null;
try {
- setState(State.STARTING);
- if (dependencyService.canStart(objectName)) {
- enrollInRelationships();
- doStart();
- setState(State.RUNNING);
- }
- } catch (Exception e) {
- setState(State.FAILED);
- throw e;
- } catch (Error e) {
- setState(State.FAILED);
- throw e;
+ synchronized (this) {
+ try {
+ // if we are still trying to start and can start now... start
+ if(getStateInstance() == State.STARTING &&
+ dependencyService.canStart(objectName) && canStart()) {
+ enrollInRelationships();
+ doStart();
+ setState(State.RUNNING);
+ newState = State.RUNNING;
+ }
+ } catch (Exception e) {
+ setState(State.FAILED);
+ newState = State.FAILED;
+ throw e;
+ } catch (Error e) {
+ setState(State.FAILED);
+ newState = State.FAILED;
+ throw e;
+ }
+ }
+ } finally {
+ if(newState != null) {
+ doNotification(newState.getEventTypeValue());
+ }
}
}
+ /**
+ * Moves this MBean to the STOPPING state, calls stop on all start dependent children, and then attempt
+ * to move this MBean to the STOPPED state.
+ *
+ * Note: This method can not be call while the current thread holds a syncronized lock on this MBean,
+ * because this method sends JMX notifications. Sending a general notification from a synchronized block
+ * is a bad idea and therefore not allowed.
+ *
+ * @throws Exception If an exception occurs while stoping this MBean or any of the childern
+ */
public final void stop() throws Exception {
- State state = getStateInstance();
- if (state == State.STOPPED || state == State.STOPPING) {
- return;
- } else if (state == State.STARTING) {
- setState(State.STOPPED);
- return;
- }
- try {
+ assert !Thread.holdsLock(this): "This method cannot be called while holding a syncrhonized lock on this";
+
+ // move to the stopping state
+ synchronized (this) {
+ State state = getStateInstance();
+ if (state == State.STOPPED || state == State.STOPPING) {
+ return;
+ }
setState(State.STOPPING);
+ }
+ doNotification(State.STOPPING.getEventTypeValue());
+
+ // Don't try to stop dependents from within a synchronized block... this should reduce deadlocks
- // stop all of my dependent objects
- Set dependents = dependencyService.getStartChildren(objectName);
- for (Iterator iterator = dependents.iterator(); iterator.hasNext();) {
- ObjectName name = (ObjectName) iterator.next();
+ // stop all of my dependent objects
+ Set dependents = dependencyService.getStartChildren(objectName);
+ for (Iterator iterator = dependents.iterator(); iterator.hasNext();) {
+ ObjectName name = (ObjectName) iterator.next();
+ try {
server.invoke(name, "stop", null, null);
+ } catch (ReflectionException e) {
+ if (e.getTargetException() instanceof NoSuchMethodException) {
+ // did not have a stop method - ok
+ } else {
+ throw e;
+ }
}
+ }
- // stop myself
- if (dependencyService.canStop(objectName)) {
- doStop();
- setState(State.STOPPED);
- }
- } catch (Exception e) {
- setState(State.FAILED);
- throw e;
- } catch (Error e) {
- setState(State.FAILED);
- throw e;
+ // Try to fully stop
+ State newState = null;
+ try {
+ synchronized (this) {
+ // if we are still trying to stop...
+ if (getStateInstance() == State.STOPPING) {
+ try {
+ // if we can stop, stop
+ if (dependencyService.canStop(objectName) && canStop()) {
+ unenrollInRelationships();
+ doStop();
+ setState(State.STOPPED);
+ newState = State.STOPPED;
+ }
+ } catch (Exception e) {
+ setState(State.FAILED);
+ newState = State.FAILED;
+ throw e;
+ } catch (Error e) {
+ setState(State.FAILED);
+ newState = State.FAILED;
+ throw e;
+ }
+ }
+ }
+ } finally {
+ if(newState != null) {
+ doNotification(newState.getEventTypeValue());
+ }
}
}
+ /**
+ * Starts this MBean and then attempts to start all of the start dependent children of this MBean.
+ *
+ * Note: This method can not be call while the current thread holds a syncronized lock on this MBean,
+ * because this method sends JMX notifications. Sending a general notification from a synchronized block
+ * is a bad idea and therefore not allowed.
+ *
+ * @throws Exception if a problem occurs will starting this MBean or any child MBean
+ */
public final void startRecursive() throws Exception {
+ assert !Thread.holdsLock(this): "This method cannot be called while holding a syncrhonized lock on this";
+
State state = getStateInstance();
- if (state == State.STOPPING) {
- throw new IllegalArgumentException("Cannot startRecursive while in the stopping state");
+ if (state != State.STOPPED && state != State.FAILED) {
+ // Cannot startRecursive while in the stopping state
+ // Dain: I don't think we can throw an exception here because there is no way for the caller
+ // to lock the instance and check the state before calling
+ return;
}
// get myself starting
@@ -255,15 +402,38 @@
Set dependents = dependencyService.getStartChildren(objectName);
for (Iterator iterator = dependents.iterator(); iterator.hasNext();) {
ObjectName dependent = (ObjectName) iterator.next();
- server.invoke(dependent, "startRecursive", null, null);
+ try {
+ server.invoke(dependent, "startRecursive", null, null);
+ } catch (ReflectionException e) {
+ if (e.getTargetException() instanceof NoSuchMethodException) {
+ // did not have a startRecursive method - ok
+ } else {
+ throw e;
+ }
+ }
}
}
- private final void doNotification(String s) {
- sendNotification(new Notification(s, this, sequenceNumber++));
+ /**
+ * Sends the specified MBean notification.
+ *
+ * Note: This method can not be call while the current thread holds a syncronized lock on this MBean,
+ * because this method sends JMX notifications. Sending a general notification from a synchronized block
+ * is a bad idea and therefore not allowed.
+ *
+ * @param type the notification type to send
+ */
+ private final void doNotification(String type) {
+ assert !Thread.holdsLock(this): "This method cannot be called while holding a syncrhonized lock on this";
+ sendNotification(new Notification(type, this, sequenceNumber++));
}
- private final void enrollInRelationships() throws StartException {
+ /**
+ * Enrolls this MBean is all relationships specified in the dependency service.
+ *
+ * @throws StartException is this MBean can not be enrolled in a relationship
+ */
+ private synchronized final void enrollInRelationships() throws StartException {
String relationshipType = null;
String relationshipRole = null;
String targetRoleName = null;
@@ -297,18 +467,21 @@
} else {
targetRoleName = ((RoleInfo) roles.get(0)).getName();
}
+ relationship.setTargetRole(targetRoleName);
}
roleList.add(new Role(targetRoleName, Collections.singletonList(target)));
}
relationService.createRelation(relationshipName, relationshipType, roleList);
+ relationship.setCreatedRelationship(true);
} else {
// We have an exiting relationship -- just add to the existing role
List members = relationService.getRole(relationshipName, relationshipRole);
- if(!members.contains(objectName)) {
+ if (!members.contains(objectName)) {
members.add(objectName);
relationService.setRole(relationshipName, new Role(relationshipRole, members));
}
+ relationship.setCreatedRelationship(false);
}
}
} catch (RelationTypeNotFoundException e) {
@@ -329,57 +502,108 @@
}
}
- private void checkState() {
- State state = getStateInstance();
- if (state == State.STARTING) {
- if (dependencyService.canStart(objectName)) {
- try {
- doStart();
- setState(State.RUNNING);
- } catch (Exception e) {
- setState(State.FAILED);
- } catch (Error e) {
- setState(State.FAILED);
+ /**
+ * Removes this MBean from all relationships specified in the dependency service.
+ *
+ * @throws StopException is this MBean can not be removed in a relationship
+ */
+ private synchronized final void unenrollInRelationships() throws StopException {
+ String relationshipType = null;
+ String relationshipRole = null;
+ try {
+ Set relationships = dependencyService.getRelationships(objectName);
+ for (Iterator i = relationships.iterator(); i.hasNext();) {
+ MBeanRelationship relationship = (MBeanRelationship) i.next();
+ String relationshipName = relationship.getName();
+ if(relationship.getCreatedRelationship()) {
+ // drop the entire relationship
+ relationService.removeRelation(relationshipName);
+ } else {
+ // just remove myself from the relationship
+ relationshipRole = relationship.getRole();
+ List members = relationService.getRole(relationshipName, relationshipRole);
+ if (members.contains(objectName)) {
+ members.remove(objectName);
+ relationService.setRole(relationshipName, new Role(relationshipRole, members));
+ }
}
}
- } else if (state == State.RUNNING) {
- if (dependencyService.shouldStop(objectName)) {
- // we were running and someone stopped or unregisted without informing us
- // try to stop immedately, or just fail
- if (dependencyService.canStop(objectName)) {
- try {
- setState(State.STOPPING);
- doStop();
- setState(State.STOPPED);
- } catch (Exception e) {
- setState(State.FAILED);
- } catch (Error e) {
- setState(State.FAILED);
+ } catch (RelationTypeNotFoundException e) {
+ throw new StopException("Relationship type is not registered: relationType=" + relationshipType);
+ } catch (RelationServiceNotRegisteredException e) {
+ throw new StopException("RelationshipService is not registered", e);
+ } catch (RoleNotFoundException e) {
+ throw new StopException("RelationshipService is not registered", e);
+ } catch (InvalidRoleValueException e) {
+ throw new StopException("Relationship role state is invalid", e);
+ } catch (RelationNotFoundException e) {
+ throw new StopException("Relation was unregistered while executing", e);
+ }
+ }
+
+ /**
+ * Checks if we need to make a state transition based on our dependencies registered with the dependency service.
+ *
+ * Note: Do not call this from within a synchronized block as it makes may send a JMX notification
+ */
+ protected void checkState() {
+ assert !Thread.holdsLock(this): "This method cannot be called while holding a syncrhonized lock on this";
+
+ State newState = null;
+ try {
+ synchronized (this) {
+ State state = getStateInstance();
+ if (state == State.STARTING) {
+ if (dependencyService.canStart(objectName) && canStart()) {
+ try {
+ doStart();
+ setState(State.RUNNING);
+ newState = State.RUNNING;
+ } catch (Exception e) {
+ setState(State.FAILED);
+ newState = State.FAILED;
+ } catch (Error e) {
+ setState(State.FAILED);
+ newState = State.FAILED;
+ }
+ }
+ } else if (state == State.RUNNING) {
+ // Someone stopping, stopped, failed or unregistered we need to change state
+ State recommendState = dependencyService.shouldChangeState(objectName);
+ if (recommendState != null) {
+ setState(recommendState);
+ newState = recommendState;
+ }
+ } else if (state == State.STOPPING) {
+ if (dependencyService.canStop(objectName) && canStop()) {
+ try {
+ unenrollInRelationships();
+ doStop();
+ setState(State.STOPPED);
+ newState = State.STOPPED;
+ } catch (Exception e) {
+ setState(State.FAILED);
+ newState = State.FAILED;
+ } catch (Error e) {
+ setState(State.FAILED);
+ newState = State.FAILED;
+ }
}
- } else {
- setState(State.FAILED);
}
}
- } else if (state == State.STOPPING) {
- if (dependencyService.canStop(objectName)) {
- try {
- doStop();
- setState(State.STOPPED);
- } catch (Exception e) {
- setState(State.FAILED);
- } catch (Error e) {
- setState(State.FAILED);
- }
+ } finally {
+ if (newState != null) {
+ doNotification(newState.getEventTypeValue());
}
}
}
/**
* Set the Component state.
- * @param newState
- * @throws IllegalStateException Thrown if the transition is not supported by the JSR77 lifecycle.
+ * @param newState the target state to transition
+ * @throws IllegalStateException Thrown if the transition is not supported by the J2EE Management lifecycle.
*/
- private void setState(State newState) throws IllegalStateException {
+ private synchronized void setState(State newState) throws IllegalStateException {
switch (state.toInt()) {
case State.STOPPED_INDEX:
switch (newState.toInt()) {
@@ -447,12 +671,14 @@
}
break;
}
- log.debug("State changed from " + state + " to " + newState);
+ log.debug(objectName.toString() + " State changed from " + state + " to " + newState);
if (newState == State.RUNNING) {
startTime = System.currentTimeMillis();
}
state = newState;
+ }
- doNotification(state.getEventTypeValue());
+ public String toString() {
+ return objectName.toString();
}
}