FELIX-5114: Schedule configuration update in Component executor synchronously.
The getExecutor method is now exposed from the ComponentContext interface. Hence, the version for the
org.apache.felix.dm.context has been bumped to 4.1.0 version.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1715707 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/ComponentContext.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/ComponentContext.java
index a62619c..b3aa039 100644
--- a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/ComponentContext.java
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/ComponentContext.java
@@ -27,6 +27,8 @@
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
+import aQute.bnd.annotation.ProviderType;
+
/**
* This interface is the entry point to the Component implementation context.
* It is used by all DependencyManager Dependency implementations.
@@ -34,8 +36,17 @@
* @see DependencyContext interface
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
+@ProviderType
public interface ComponentContext extends Component {
/**
+ * Returns the Component Executor gate that can be used to ensure proper component event serialization.
+ * When you schedule a task in the component executor, your task is executed safely and you do not need
+ * to managed synchronization (other external events, like service dependency events) will be queued
+ * until your task has been executed).
+ */
+ public Executor getExecutor();
+
+ /**
* Returns the logger which can be used by the DependencyManager Dependencies implementations.
*/
public Logger getLogger();
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/packageinfo b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/packageinfo
index 6af07c8..eabbf4e 100644
--- a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/packageinfo
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/packageinfo
@@ -1 +1 @@
-version 4.0.0
+version 4.1.0
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java
index 4016a2c..2a9293b 100644
--- a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java
@@ -341,6 +341,11 @@
}
@Override
+ public Executor getExecutor() {
+ return m_executor;
+ }
+
+ @Override
public Component setDebug(String debugKey) {
// Force debug level in our logger
m_logger.setEnabledLevel(LogService.LOG_DEBUG);
@@ -1607,8 +1612,4 @@
result.append(")");
}
}
-
- private Executor getExecutor() {
- return m_executor;
- }
}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java
index e417283..50f7acf 100644
--- a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java
@@ -21,6 +21,11 @@
import java.lang.reflect.InvocationTargetException;
import java.util.Dictionary;
import java.util.Properties;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.felix.dm.Component;
@@ -52,6 +57,7 @@
private final Logger m_logger;
private final BundleContext m_context;
private boolean m_needsInstance = true;
+ private final static int UPDATE_MAXWAIT = 30000; // max time to wait until a component has handled a configuration change event.
public ConfigurationDependencyImpl() {
this(null, null);
@@ -188,7 +194,7 @@
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
- public void updated(Dictionary settings) throws ConfigurationException {
+ public void updated(final Dictionary settings) throws ConfigurationException {
m_updateInvokedCache.set(false);
Dictionary<String, Object> oldSettings = null;
synchronized (this) {
@@ -203,11 +209,46 @@
// If this is initial settings, or a configuration update, we handle it synchronously.
// We'll conclude that the dependency is available only if invoking updated did not cause
// any ConfigurationException.
+ // However, we still want to schedule the event in the component executor, to make sure that the
+ // callback is invoked safely. So, we use a Callable and a FutureTask that allows to handle the
+ // configuration update through the component executor. We still wait for the result because
+ // in case of any configuration error, we have to return it from the current thread.
+
+ Callable<ConfigurationException> result = new Callable<ConfigurationException>() {
+ @Override
+ public ConfigurationException call() throws Exception {
+ try {
+ invokeUpdated(settings); // either the callback instance or the component instances, if available.
+ } catch (ConfigurationException e) {
+ return e;
+ }
+ return null;
+ }
+ };
+
+ // Schedule the configuration update in the component executor. In Normal case, the task is immediately executed.
+ // But in a highly concurrent system, and if the component is being reconfigured, the component may be currently busy
+ // (handling a service dependency event for example), so the task will be enqueued in the component executor, and
+ // we'll wait for the task execution by using a FutureTask:
+
+ FutureTask<ConfigurationException> ft = new FutureTask<>(result);
+ m_component.getExecutor().execute(ft);
+
try {
- invokeUpdated(settings); // either the callback instance or the component instances, if available.
- } catch (ConfigurationException e) {
- logConfigurationException(e);
- throw e;
+ ConfigurationException confError = ft.get(UPDATE_MAXWAIT, TimeUnit.MILLISECONDS);
+ if (confError != null) {
+ throw confError; // will be logged by the Configuration Admin service;
+ }
+ }
+
+ catch (ExecutionException error) {
+ throw new ConfigurationException(null, "Configuration update error, unexpected exception.", error);
+ } catch (InterruptedException error) {
+ // will be logged by the Configuration Admin service;
+ throw new ConfigurationException(null, "Configuration update interrupted.", error);
+ } catch (TimeoutException error) {
+ // will be logged by the Configuration Admin service;
+ throw new ConfigurationException(null, "Component did not handle configuration update timely.", error);
}
// At this point, we have accepted the configuration.
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FilterComponent.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FilterComponent.java
index 33bf464..a497d40 100644
--- a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FilterComponent.java
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FilterComponent.java
@@ -67,6 +67,11 @@
}
@Override
+ public Executor getExecutor() {
+ return m_component.getExecutor();
+ }
+
+ @Override
public String toString() {
return m_component.toString();
}