-
Notifications
You must be signed in to change notification settings - Fork 51
Config self reference fix #480
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
Changes from 15 commits
d7c6036
4121554
5324f82
a679682
faeeb1b
f84d886
49f0e22
72eff85
3f3e3d6
7476d3b
b073349
0aa29ef
cd3d486
99ccc0f
2e6f11f
4602114
a3f42d3
e6fd10c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,8 +28,14 @@ | |
|
|
||
| import org.apache.brooklyn.api.entity.Entity; | ||
| import org.apache.brooklyn.core.config.ConfigKeys; | ||
| import org.apache.brooklyn.core.entity.Entities; | ||
| import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; | ||
| import org.apache.brooklyn.core.sensor.Sensors; | ||
| import org.apache.brooklyn.core.test.entity.TestEntity; | ||
| import org.apache.brooklyn.test.Asserts; | ||
| import org.apache.brooklyn.util.exceptions.RuntimeInterruptedException; | ||
| import org.apache.brooklyn.util.time.Duration; | ||
| import org.apache.brooklyn.util.time.Time; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import org.testng.annotations.AfterMethod; | ||
|
|
@@ -44,7 +50,6 @@ | |
|
|
||
| public class ConfigYamlTest extends AbstractYamlTest { | ||
|
|
||
| @SuppressWarnings("unused") | ||
| private static final Logger LOG = LoggerFactory.getLogger(ConfigYamlTest.class); | ||
|
|
||
| private ExecutorService executor; | ||
|
|
@@ -91,6 +96,61 @@ public void testConfigInConfigBlock() throws Exception { | |
| assertNull(entity.getMyField()); // field with @SetFromFlag | ||
| assertNull(entity.getMyField2()); // field with @SetFromFlag("myField2Alias"), set using alias | ||
| } | ||
|
|
||
|
|
||
| @Test | ||
| public void testRecursiveConfigFailsGracefully() throws Exception { | ||
| doTestRecursiveConfigFailsGracefully(false); | ||
| } | ||
|
|
||
| @Test | ||
| public void testRecursiveConfigImmediateFailsGracefully() throws Exception { | ||
| doTestRecursiveConfigFailsGracefully(true); | ||
| } | ||
|
|
||
| protected void doTestRecursiveConfigFailsGracefully(boolean immediate) throws Exception { | ||
| String yaml = Joiner.on("\n").join( | ||
| "services:", | ||
| "- type: org.apache.brooklyn.core.test.entity.TestEntity", | ||
| " brooklyn.config:", | ||
| " infinite_loop: $brooklyn:config(\"infinite_loop\")"); | ||
|
|
||
| final Entity app = createStartWaitAndLogApplication(yaml); | ||
| TestEntity entity = (TestEntity) Iterables.getOnlyElement(app.getChildren()); | ||
|
|
||
| Thread t = new Thread(new Runnable() { | ||
| @Override | ||
| public void run() { | ||
| try { | ||
| Time.sleep(Duration.FIVE_SECONDS); | ||
| // error, loop wasn't interrupted or detected | ||
| LOG.warn("Timeout elapsed, destroying items; usage: "+ | ||
| ((LocalManagementContext)mgmt()).getGarbageCollector().getUsageString()); | ||
| Entities.destroy(app); | ||
| } catch (RuntimeInterruptedException e) { | ||
| // expected on normal execution; clear the interrupted flag to prevent ugly further warnings being logged | ||
| Thread.interrupted(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you calling this to clear the interrupted status? Why? Do you get an ugly exception or something if we don't?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. exactly, comment added |
||
| } | ||
| } | ||
| }); | ||
| t.start(); | ||
| try { | ||
| String c; | ||
| if (immediate) { | ||
| // this should throw rather than return "absent", because the error is definitive (absent means couldn't resolve in time) | ||
| c = entity.config().getNonBlocking(ConfigKeys.newStringConfigKey("infinite_loop")).or("FAILED"); | ||
| } else { | ||
| c = entity.config().get(ConfigKeys.newStringConfigKey("infinite_loop")); | ||
| } | ||
| Asserts.shouldHaveFailedPreviously("Expected recursive error, instead got: "+c); | ||
| } catch (Exception e) { | ||
| Asserts.expectedFailureContainsIgnoreCase(e, "infinite_loop", "recursive"); | ||
| } finally { | ||
| if (!Entities.isManaged(app)) { | ||
| t.interrupt(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testConfigAtTopLevel() throws Exception { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -146,7 +146,6 @@ protected <T> Maybe<T> getNonBlockingResolvingSimple(ConfigKey<T> key) { | |
| .immediately(true) | ||
| .deep(true) | ||
| .context(getContext()) | ||
| .swallowExceptions() | ||
| .get(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I probably agree with this change, but don't feel confident about the full implications of it throwing the exception rather than returning the default value versus absent. In your test
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, the immediate stuff is quite new so i'd prefer to try this. if it's a problem we should perhaps look at wrapping in a |
||
| return (resolved != marker) | ||
| ? TypeCoercions.tryCoerce(resolved, key.getTypeToken()) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,6 +41,8 @@ | |
| import org.apache.brooklyn.core.mgmt.BrooklynTaskTags.WrappedEntity; | ||
| import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; | ||
| import org.apache.brooklyn.util.collections.MutableMap; | ||
| import org.apache.brooklyn.util.core.task.ImmediateSupplier.ImmediateUnsupportedException; | ||
| import org.apache.brooklyn.util.guava.Maybe; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
|
|
@@ -96,7 +98,50 @@ public ExecutionManager getExecutionManager() { | |
| /** returns tasks started by this context (or tasks which have all the tags on this object) */ | ||
| @Override | ||
| public Set<Task<?>> getTasks() { return executionManager.getTasksWithAllTags(tags); } | ||
|
|
||
|
|
||
| /** performs execution without spawning a new task thread, though it does temporarily set a fake task for the purpose of getting context; | ||
| * currently supports suppliers or callables */ | ||
| @SuppressWarnings("unchecked") | ||
| @Override | ||
| public <T> Maybe<T> getImmediately(Object callableOrSupplier) { | ||
| BasicTask<?> fakeTaskForContext; | ||
| if (callableOrSupplier instanceof BasicTask) { | ||
| fakeTaskForContext = (BasicTask<?>)callableOrSupplier; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Under what code path do we get here? Looking at For example, the test below fails (added to I'm not convinced that we want to support setting a config key with a value of type
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as noted above leaving the task running in background (or possibly not submitting it) is the right thing to do for immediate execution of a task; cancelling it is risky unless we know the task is dedicated to this one usage (hence preferring
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As for the particular code block marked here, it is invoked from |
||
| if (fakeTaskForContext.isQueuedOrSubmitted()) { | ||
| if (fakeTaskForContext.isDone()) { | ||
| return Maybe.of((T)fakeTaskForContext.getUnchecked()); | ||
| } else { | ||
| throw new ImmediateUnsupportedException("Task is in progress and incomplete: "+fakeTaskForContext); | ||
| } | ||
| } | ||
| callableOrSupplier = fakeTaskForContext.getJob(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very clever, getting the underlying However, I wonder if this just reduces the chance of leaving tasks behind (rather than preventing it). The test below fails when added to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as above |
||
| } else { | ||
| fakeTaskForContext = new BasicTask<Object>(MutableMap.of("displayName", "immediate evaluation")); | ||
| } | ||
| fakeTaskForContext.tags.addAll(tags); | ||
| fakeTaskForContext.tags.add(BrooklynTaskTags.IMMEDIATE_TASK_TAG); | ||
| fakeTaskForContext.tags.add(BrooklynTaskTags.TRANSIENT_TASK_TAG); | ||
|
|
||
| Task<?> previousTask = BasicExecutionManager.getPerThreadCurrentTask().get(); | ||
| BasicExecutionContext oldExecutionContext = getCurrentExecutionContext(); | ||
| registerPerThreadExecutionContext(); | ||
|
|
||
| if (previousTask!=null) fakeTaskForContext.setSubmittedByTask(previousTask); | ||
| fakeTaskForContext.cancel(); | ||
| try { | ||
| BasicExecutionManager.getPerThreadCurrentTask().set(fakeTaskForContext); | ||
|
|
||
| if (!(callableOrSupplier instanceof ImmediateSupplier)) { | ||
| callableOrSupplier = InterruptingImmediateSupplier.of(callableOrSupplier); | ||
| } | ||
| return ((ImmediateSupplier<T>)callableOrSupplier).getImmediately(); | ||
|
|
||
| } finally { | ||
| BasicExecutionManager.getPerThreadCurrentTask().set(previousTask); | ||
| perThreadExecutionContext.set(oldExecutionContext); | ||
| } | ||
| } | ||
|
|
||
| @SuppressWarnings({ "unchecked", "rawtypes" }) | ||
| @Override | ||
| protected <T> Task<T> submitInternal(Map<?,?> propertiesQ, final Object task) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -591,7 +591,7 @@ public boolean cancel(TaskCancellationMode mode) { | |
| if (!task.isCancelled()) result |= ((TaskInternal<T>)task).cancel(mode); | ||
| result |= delegate().cancel(mode.isAllowedToInterruptTask()); | ||
|
|
||
| if (mode.isAllowedToInterruptAllSubmittedTasks() || mode.isAllowedToInterruptDependentSubmittedTasks()) { | ||
| if (mode.isAllowedToInterruptDependentSubmittedTasks()) { | ||
| int subtasksFound=0; | ||
| int subtasksReallyCancelled=0; | ||
|
|
||
|
|
@@ -753,7 +753,10 @@ protected void beforeStartAtomicTask(Map<?,?> flags, Task<?> task) { | |
| /** invoked in a task's thread when a task is starting to run (may be some time after submitted), | ||
| * but before doing any of the task's work, so that we can update bookkeeping and notify callbacks */ | ||
| protected void internalBeforeStart(Map<?,?> flags, Task<?> task) { | ||
| activeTaskCount.incrementAndGet(); | ||
| int count = activeTaskCount.incrementAndGet(); | ||
| if (count % 1000==0) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we hover around the 999 to 1001 mark for the number of active tasks, then we'll get this log message lots of times. But I think that's acceptable, in exchange for simpler code. So fine as it is. |
||
| log.warn("High number of active tasks: task #"+count+" is "+task); | ||
| } | ||
|
|
||
| //set thread _before_ start time, so we won't get a null thread when there is a start-time | ||
| if (log.isTraceEnabled()) log.trace(""+this+" beforeStart, task: "+task + " running on thread " + Thread.currentThread().getName()); | ||
|
|
||
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.
Minor: I'd have included a comment on this method to say that if immediate then the return value will be
Maybe<Object>whereas if!immediatethen the return value of the callable will be the actualObject. The difference of those semantics are not obvious in the method, but are relied upon by the caller.It's surprising that the same method is used to return the two different styles of result, but I see why you did it (to reduce duplication).
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.
yeah it's ugly; at least it's private. have renamed.