Skip to content

Commit afe274f

Browse files
committed
This closes #565
2 parents a0f8840 + f4dc19d commit afe274f

15 files changed

Lines changed: 240 additions & 180 deletions

File tree

camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.BrooklynDslCommon;
2828
import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslToStringHelpers;
2929
import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
30+
import org.apache.brooklyn.util.core.task.ImmediateSupplier;
3031
import org.apache.brooklyn.util.core.task.Tasks;
3132
import org.apache.brooklyn.util.exceptions.Exceptions;
3233
import org.apache.brooklyn.util.guava.Maybe;
@@ -94,7 +95,7 @@ protected Maybe<Object> invokeOnDeferred(Object obj, boolean immediate) {
9495
return invokeOn(instance);
9596
} else {
9697
if (immediate) {
97-
return Maybe.absent("Could not evaluate immediately: " + obj);
98+
return Maybe.absent(new ImmediateSupplier.ImmediateValueNotAvailableException("Could not evaluate immediately: " + obj));
9899
} else {
99100
return Maybe.absent(Maybe.getException(resolvedMaybe));
100101
}

camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -596,18 +596,11 @@ public Maybe<Object> getImmediately() {
596596
final Class<?> clazz = getOrLoadType();
597597
final ExecutionContext executionContext = entity().getExecutionContext();
598598

599-
// Marker exception that one of our component-parts cannot yet be resolved -
600-
// throwing and catching this allows us to abort fast.
601-
// A bit messy to use exceptions in normal control flow, but this allows the Maps util methods to be used.
602-
@SuppressWarnings("serial")
603-
class UnavailableException extends RuntimeException {
604-
}
605-
606599
final Function<Object, Object> resolver = new Function<Object, Object>() {
607600
@Override public Object apply(Object value) {
608601
Maybe<Object> result = Tasks.resolving(value, Object.class).context(executionContext).deep(true).immediately(true).getMaybe();
609602
if (result.isAbsent()) {
610-
throw new UnavailableException();
603+
throw new ImmediateValueNotAvailableException();
611604
} else {
612605
return result.get();
613606
}
@@ -627,8 +620,8 @@ class UnavailableException extends RuntimeException {
627620
result = create(clazz, factoryMethodName, resolvedFactoryMethodArgs, resolvedFields, resolvedConfig);
628621
}
629622
return Maybe.of(result);
630-
} catch (UnavailableException e) {
631-
return Maybe.absent();
623+
} catch (ImmediateValueNotAvailableException e) {
624+
return ImmediateValueNotAvailableException.newAbsentWithExceptionSupplier();
632625
}
633626
}
634627

@@ -881,7 +874,7 @@ public EntitySupplier(String entityId) {
881874
public Maybe<Entity> getImmediately() {
882875
EntityInternal entity = entity();
883876
if (entity == null) {
884-
return Maybe.absent();
877+
return Maybe.absent("No entity available");
885878
}
886879
Entity targetEntity = entity.getManagementContext().getEntityManager().getEntity(entityId);
887880
return Maybe.of(targetEntity);

camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ protected Maybe<Entity> callImpl(boolean immediate) throws Exception {
290290

291291
if (immediate) {
292292
if (maybeComponentId.isAbsent()) {
293-
return Maybe.absent(Maybe.getException(maybeComponentId));
293+
return ImmediateValueNotAvailableException.newAbsentWrapping("Cannot find component ID", maybeComponentId);
294294
}
295295
}
296296

@@ -420,7 +420,7 @@ public EntityId(DslComponent component) {
420420
@Override
421421
public Maybe<Object> getImmediately() {
422422
Maybe<Entity> targetEntityMaybe = component.getImmediately();
423-
if (targetEntityMaybe.isAbsent()) return Maybe.absent("Target entity not available");
423+
if (targetEntityMaybe.isAbsent()) return ImmediateValueNotAvailableException.newAbsentWrapping("Target entity is not available: "+component, targetEntityMaybe);
424424
Entity targetEntity = targetEntityMaybe.get();
425425

426426
return Maybe.<Object>of(targetEntity.getId());
@@ -480,7 +480,7 @@ protected String resolveSensorName(boolean immediately) {
480480
@Override
481481
public final Maybe<Object> getImmediately() {
482482
Maybe<Entity> targetEntityMaybe = component.getImmediately();
483-
if (targetEntityMaybe.isAbsent()) return Maybe.absent("Target entity not available");
483+
if (targetEntityMaybe.isAbsent()) return ImmediateValueNotAvailableException.newAbsentWrapping("Target entity not available: "+component, targetEntityMaybe);
484484
Entity targetEntity = targetEntityMaybe.get();
485485

486486
String sensorNameS = resolveSensorName(true);
@@ -489,7 +489,7 @@ public final Maybe<Object> getImmediately() {
489489
targetSensor = Sensors.newSensor(Object.class, sensorNameS);
490490
}
491491
Object result = targetEntity.sensors().get(targetSensor);
492-
return GroovyJavaMethods.truth(result) ? Maybe.of(result) : Maybe.absent();
492+
return GroovyJavaMethods.truth(result) ? Maybe.of(result) : ImmediateValueNotAvailableException.newAbsentWithExceptionSupplier();
493493
}
494494

495495
@SuppressWarnings("unchecked")
@@ -665,7 +665,7 @@ protected Maybe<Sensor<?>> getImmediately(Object si, boolean resolved) {
665665
return Maybe.<Sensor<?>>of((Sensor<?>)si);
666666
} else if (si instanceof String) {
667667
Maybe<Entity> targetEntityMaybe = component.getImmediately();
668-
if (targetEntityMaybe.isAbsent()) return Maybe.absent("Target entity not available");
668+
if (targetEntityMaybe.isAbsent()) return ImmediateValueNotAvailableException.newAbsentWrapping("Target entity is not available: "+component, targetEntityMaybe);
669669
Entity targetEntity = targetEntityMaybe.get();
670670

671671
Sensor<?> result = null;

camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigYamlTest.java

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -307,16 +307,7 @@ public void testDeferredSupplierToConfig() throws Exception {
307307
assertEquals(entity.config().get(TestEntity.CONF_SET_PLAIN), ImmutableSet.of("myOther"));
308308
}
309309

310-
/**
311-
* TODO The {@code entity.config().getNonBlocking()} can return absent. When it's called with
312-
* a deferred supplier value, it will kick off a task and then wait just a few millis for that
313-
* task to execute deferredSupplier.get(). If it times out, then it returns Maybe.absent.
314-
* However, on apache jenkins the machine is often slow so the task doesn't complete in the
315-
* given number of millis (even though deferredSupplier.get() doesn't need to block for anything).
316-
* Same for {@link #testDeferredSupplierToAttributeWhenReadyInSpecialTypes()}.
317-
* See https://issues.apache.org/jira/browse/BROOKLYN-272.
318-
*/
319-
@Test(groups="Broken")
310+
@Test
320311
public void testDeferredSupplierToAttributeWhenReady() throws Exception {
321312
String yaml = Joiner.on("\n").join(
322313
"services:",
@@ -344,17 +335,9 @@ public Object call() {
344335

345336
/**
346337
* This tests config keys of type {@link org.apache.brooklyn.core.config.MapConfigKey}, etc.
347-
* For plain maps, see {@link #testDeferredSupplierToAttributeWhenReadyInPlainCollections()}.
348-
*
349-
* TODO The {@code entity.config().getNonBlocking()} can return absent. When it's called with
350-
* a deferred supplier value, it will kick off a task and then wait just a few millis for that
351-
* task to execute deferredSupplier.get(). If it times out, then it returns Maybe.absent.
352-
* However, on apache jenkins the machine is often slow so the task doesn't complete in the
353-
* given number of millis (even though deferredSupplier.get() doesn't need to block for anything).
354-
* Same for {@link #testDeferredSupplierToAttributeWhenReady()}.
355-
* See https://issues.apache.org/jira/browse/BROOKLYN-272.
338+
* For plain maps, see {@link #testDeferredSupplierToAttributeWhenReadyInPlainCollections()}
356339
*/
357-
@Test(groups="Broken")
340+
@Test
358341
public void testDeferredSupplierToAttributeWhenReadyInSpecialTypes() throws Exception {
359342
String yaml = Joiner.on("\n").join(
360343
"services:",
@@ -372,10 +355,10 @@ public void testDeferredSupplierToAttributeWhenReadyInSpecialTypes() throws Exce
372355
final TestEntity entity = (TestEntity) Iterables.getOnlyElement(app.getChildren());
373356

374357
// Attribute not yet set; non-blocking will return promptly without the value
375-
assertTrue(entity.config().getNonBlocking(TestEntity.CONF_NAME).isAbsent());
376-
assertTrue(entity.config().getNonBlocking(TestEntity.CONF_MAP_THING).isAbsent());
377-
assertTrue(entity.config().getNonBlocking(TestEntity.CONF_LIST_THING).isAbsent());
378-
assertTrue(entity.config().getNonBlocking(TestEntity.CONF_SET_THING).isAbsent());
358+
Asserts.assertNotPresent(entity.config().getNonBlocking(TestEntity.CONF_NAME));
359+
Asserts.assertNotPresent(entity.config().getNonBlocking(TestEntity.CONF_MAP_THING));
360+
Asserts.assertNotPresent(entity.config().getNonBlocking(TestEntity.CONF_LIST_THING));
361+
Asserts.assertNotPresent(entity.config().getNonBlocking(TestEntity.CONF_SET_THING));
379362

380363
// Now set the attribute: get will return once that has happened
381364
executor.submit(new Callable<Object>() {
@@ -399,15 +382,8 @@ public Object call() {
399382
* This tests config keys of type {@link java.util.Map}, etc.
400383
* For special types (e.g. {@link org.apache.brooklyn.core.config.MapConfigKey}), see
401384
* {@link #testDeferredSupplierToAttributeWhenReadyInPlainCollections()}.
402-
*
403-
* TODO test doesn't work because getNonBlocking returns even when no value.
404-
* For example, we get back: Present[value={mykey=attributeWhenReady("myOtherSensor")}].
405-
* However, the `config().get()` does behave as desired.
406-
*
407-
* Including the "WIP" group because this test would presumably have never worked!
408-
* Added to demonstrate the short-coming.
409385
*/
410-
@Test(groups={"Broken", "WIP"})
386+
@Test
411387
public void testDeferredSupplierToAttributeWhenReadyInPlainCollections() throws Exception {
412388
String yaml = Joiner.on("\n").join(
413389
"services:",

core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractConfigMapImpl.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,9 @@ protected Maybe<Object> resolveRawValueFromContainer(TContainer container, Confi
313313
// wasteful to make a copy to look up; maybe try once opportunistically?
314314
ownCopy = MutableMap.copyOf(oc);
315315
}
316+
// would be cleaner here to have an extractValueMaybe but semantics can get confusing whether absent
317+
// means no value can be extracted (getRaw semantics) and immediate mode is on but blocking is needed (ImmediateSupplier semantics);
318+
// simpler not to support maybe, in which case here null means the former, and the latter throws something (which the caller catches)
316319
Maybe<Object> result = Maybe.of((Object) ((ConfigKeySelfExtracting<?>) key).extractValue(ownCopy, getExecutionContext(container)) );
317320
postLocalEvaluate(key, bo, value, result);
318321
return result;

core/src/main/java/org/apache/brooklyn/core/objs/AbstractConfigurationSupportInternal.java

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
import java.util.Map;
2323
import java.util.Set;
2424
import java.util.concurrent.Callable;
25-
import java.util.concurrent.ExecutionException;
26-
import java.util.concurrent.TimeoutException;
2725

2826
import javax.annotation.Nullable;
2927

@@ -41,8 +39,9 @@
4139
import org.apache.brooklyn.util.collections.MutableMap;
4240
import org.apache.brooklyn.util.core.config.ConfigBag;
4341
import org.apache.brooklyn.util.core.flags.TypeCoercions;
42+
import org.apache.brooklyn.util.core.task.ImmediateSupplier.ImmediateUnsupportedException;
43+
import org.apache.brooklyn.util.core.task.ImmediateSupplier.ImmediateValueNotAvailableException;
4444
import org.apache.brooklyn.util.core.task.Tasks;
45-
import org.apache.brooklyn.util.core.task.ValueResolver;
4645
import org.apache.brooklyn.util.exceptions.Exceptions;
4746
import org.apache.brooklyn.util.exceptions.RuntimeInterruptedException;
4847
import org.apache.brooklyn.util.guava.Maybe;
@@ -53,6 +52,7 @@
5352

5453
public abstract class AbstractConfigurationSupportInternal implements BrooklynObjectInternal.ConfigurationSupportInternal {
5554

55+
@SuppressWarnings("unused")
5656
private static final Logger LOG = LoggerFactory.getLogger(AbstractConfigurationSupportInternal.class);
5757

5858
@Override
@@ -77,10 +77,16 @@ public <T> Maybe<T> getNonBlocking(HasConfigKey<T> key) {
7777

7878
@Override
7979
public <T> Maybe<T> getNonBlocking(final ConfigKey<T> key) {
80-
if (key instanceof StructuredConfigKey || key instanceof SubElementConfigKey) {
81-
return getNonBlockingResolvingStructuredKey(key);
82-
} else {
83-
return getNonBlockingResolvingSimple(key);
80+
try {
81+
if (key instanceof StructuredConfigKey || key instanceof SubElementConfigKey) {
82+
return getNonBlockingResolvingStructuredKey(key);
83+
} else {
84+
return getNonBlockingResolvingSimple(key);
85+
}
86+
} catch (ImmediateValueNotAvailableException e) {
87+
return Maybe.absent(e);
88+
} catch (ImmediateUnsupportedException e) {
89+
return Maybe.absent(e);
8490
}
8591
}
8692

@@ -89,12 +95,6 @@ public <T> Maybe<T> getNonBlocking(final ConfigKey<T> key) {
8995
* execute the custom logic, as is done by {@link #get(ConfigKey)}, but non-blocking!
9096
*/
9197
protected <T> Maybe<T> getNonBlockingResolvingStructuredKey(final ConfigKey<T> key) {
92-
// TODO This is a poor implementation. We risk timing out when it's just doing its
93-
// normal work (e.g. because job's thread was starved), rather than when it's truly
94-
// blocked. Really we'd need to dig into the implementation of get(key), so that the
95-
// underlying work can be configured with a timeout, for when it finally calls
96-
// ValueResolver.
97-
9898
Callable<T> job = new Callable<T>() {
9999
@Override
100100
public T call() {
@@ -106,22 +106,15 @@ public T call() {
106106
}
107107
};
108108

109-
Task<T> t = getContext().submit(Tasks.<T>builder().body(job)
109+
Task<T> t = Tasks.<T>builder().body(job)
110110
.displayName("Resolving dependent value")
111111
.description("Resolving "+key.getName())
112112
.tag(BrooklynTaskTags.TRANSIENT_TASK_TAG)
113-
.build());
113+
.build();
114114
try {
115-
T result = t.get(ValueResolver.NON_BLOCKING_WAIT);
116-
return Maybe.of(result);
117-
} catch (TimeoutException e) {
118-
t.cancel(true);
119-
return Maybe.<T>absent();
120-
} catch (ExecutionException e) {
121-
LOG.debug("Problem resolving "+key.getName()+", returning <absent>", e);
122-
return Maybe.<T>absent();
123-
} catch (InterruptedException e) {
124-
throw Exceptions.propagate(e);
115+
return getContext().getImmediately(t);
116+
} catch (ImmediateUnsupportedException e) {
117+
return Maybe.absent();
125118
}
126119
}
127120

@@ -138,18 +131,17 @@ protected <T> Maybe<T> getNonBlockingResolvingSimple(ConfigKey<T> key) {
138131
// getRaw returns Maybe(val) if the key was explicitly set (where val can be null)
139132
// or Absent if the config key was unset.
140133
Object unresolved = getRaw(key).or(key.getDefaultValue());
141-
final Object marker = new Object();
142-
// Give tasks a short grace period to resolve.
143-
Object resolved = Tasks.resolving(unresolved)
134+
Maybe<Object> resolved = Tasks.resolving(unresolved)
144135
.as(Object.class)
145-
.defaultValue(marker)
146136
.immediately(true)
147137
.deep(true)
148138
.context(getContext())
149-
.get();
150-
return (resolved != marker)
151-
? TypeCoercions.tryCoerce(resolved, key.getTypeToken())
152-
: Maybe.<T>absent();
139+
.getMaybe();
140+
if (resolved.isAbsent()) return Maybe.Absent.<T>castAbsent(resolved);
141+
142+
// likely we don't need this coercion if we set as(key.getType()) above,
143+
// but that needs confirmation and quite a lot of testing
144+
return TypeCoercions.tryCoerce(resolved.get(), key.getTypeToken());
153145
}
154146

155147
@Override

core/src/main/java/org/apache/brooklyn/core/objs/BrooklynObjectInternal.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
2929
import org.apache.brooklyn.config.ConfigMap.ConfigMapWithInheritance;
3030
import org.apache.brooklyn.util.core.config.ConfigBag;
31+
import org.apache.brooklyn.util.core.task.ImmediateSupplier;
32+
import org.apache.brooklyn.util.core.task.ImmediateSupplier.ImmediateUnsupportedException;
3133
import org.apache.brooklyn.util.guava.Maybe;
3234

3335
import com.google.common.annotations.Beta;
@@ -113,13 +115,20 @@ public interface ConfigurationSupportInternal extends Configurable.Configuration
113115

114116
/**
115117
* Attempts to coerce the value for this config key, if available,
116-
* taking a default and {@link Maybe#absent absent} if the uncoerced
117-
* cannot be resolved within a short timeframe.
118+
* including returning a default if the config key is unset,
119+
* returning a {@link Maybe#absent absent} if the uncoerced
120+
* does not support immediate resolution.
118121
* <p>
119122
* Note: if no value for the key is available, not even as a default,
120123
* this returns a {@link Maybe#isPresent()} containing <code>null</code>
121124
* (following the semantics of {@link #get(ConfigKey)}
122125
* rather than {@link #getRaw(ConfigKey)}).
126+
* Thus a {@link Maybe#absent()} definitively indicates that
127+
* the absence is due to the request to evaluate immediately.
128+
* <p>
129+
* This will include catching {@link ImmediateUnsupportedException}
130+
* and returning it as an absence, thus making the semantics here slightly
131+
* "safer" than that of {@link ImmediateSupplier#getImmediately()}.
123132
*/
124133
@Beta
125134
<T> Maybe<T> getNonBlocking(ConfigKey<T> key);

0 commit comments

Comments
 (0)