Skip to content

Conversation

@ahgittin
Copy link
Contributor

@ahgittin ahgittin commented Dec 7, 2016

builds on #462 fixing the remaining issues discussed in BROOKLYN-329

immediate eval could be improved yet further but this fixes scope/resolution issues and in general makes immediate things much more reliable, as well as fixing recursive references in config settings, and helping to debug bad task traces

@geomacy geomacy mentioned this pull request Dec 13, 2016
@aledsage
Copy link
Contributor

@ahgittin This is way too hard to review until #462 is merged (which requires rebasing and a bunch of PR comments to be addressed; as I asked there, will you have time to address those or would you like one of us to pick it up)?

This PR also looks like it has a bunch of other commits that could (and should) be extracted into their own PRs (e.g. "solve problem with map eval" perhaps? Definitely "slow tests moved to integration group"? "cleanup, and allow TaskFactory to be supplied as a config..." perhaps?). I presume they are not all related to the title "Config self reference fix"?

Including many changes in a single PR makes the reviewers job much harder, and makes it take much much longer to get things merged.

@aledsage aledsage mentioned this pull request Feb 10, 2017
asfgit pushed a commit that referenced this pull request Feb 13, 2017
Speed up tests

Starting an embedded OSGi container is slow - over half a second per `setUp()` call (and thus per test method) that uses it. It will likely be considerably slower on the jenkins build machine etc. This PR separates out those classes that include an embedded OSGi container into those tests that actually need it and those that don't. It primarily moves code around (e.g. separating out the osgi-based tests from `CatalogYamlLocationTest` into a new `CatalogOsgiYamlLocationTest`).

It also cherry-pick @ahgittin's commit that was included in #480 (which marks some more slow tests as "integration").

If you're wondering why just moving the code has resulted in a bunch of extra lines (`+1,956 −1,038` according to the "Files changed" summary), there are two primary reasons: first there are 6 new classes which have the apache header and a bunch of imports; second the `CatalogOsgiYamlEntityTest` repeats some tests that are done in `CatalogOsgiEntityTest`. The reason is that there are subtleties in how bundles are used to load catalog items (especially when composing multiple items, and sub-typing). It's therefore worth having slow versions of some of these tests that use actual OSGi bundles.
Alex Heneveld and others added 7 commits February 14, 2017 16:48
address BROOKLYN-329 case (2), where a config key is defined as a function of itself

detect the endless loop that results and fail with a good message

also better handling in general of endless-loop task failures,
including:

* bail-out logic in Exceptions.collapse to prevent crazy long strings and traces
* warning whenever active tasks passes N*1000
most new immediate tests now passing, including a new test which detects recursive config values for immediate;
except we still have:

* cancellations of immediate execution goes too far, and cancels tasks which are set as values
* task factories still not supported for evaluation
sets non-transient if a task is requested for a value-resolver

we might want to deprecate that altogether, instead use TaskFactory
so we can cancel things

don't think there will be much leaking because the ValueResolver isn't used for new tasks,
just for tasks which are set as values -- but we need to keep an eye on that.
such tasks should be cancelled when the entities are cleaned up.
…alueResolver input

the TF will create a task which will then be used for evaluation.
much cleaner semantics than setting tasks as values:

tasks evaluate once and remember their result, whereas task factory spawns a new task each time.
furthermore, the former cannot be interrupted without making the value _never_ resolvable (which was the case prior to the previous commit)
so it is left running if immediate eval is done, whereas the latter can be safely cancelled.
@ahgittin ahgittin force-pushed the config-self-reference-fix branch from dd887dd to 49f0e22 Compare February 14, 2017 17:06
@ahgittin
Copy link
Contributor Author

Rebased, and once #462 is merged this should be easier to review. Cherry-picking my "slow tests" meant it automatically left this PR which was nice.

@ahgittin
Copy link
Contributor Author

Re TaskFactory being a separate PR, that would mean massaging the tests and other places so there is a clean state where TF isn't supported, and a clean state where TF is supported. Agree in general PRs should be split up, but where something is trivial (integration tests), or where things are entangled (TaskFactory) it should be a judgment call. We should avoid hypothetical problem-spotting: "Your PR includes things not 100% related. That's scientifically proven to make it much harder to review." If in the course of a review it is actually hard, then ask for a tidy-up. I might get the balance wrong sometimes but I think here my atrocities are limited to having neglected #462 for months and months.

@ahgittin
Copy link
Contributor Author

Pretty sure the failures reported here are unrelated:

* org.apache.brooklyn.launcher.CleanOrphanedLocationsIntegrationTest.testCleanedCopiedPersistedState
* org.apache.brooklyn.launcher.CleanOrphanedLocationsIntegrationTest.testSelectionWithOrphanedLocationsInData

Both pass for me locally. (Incidentally @aledsage none of the tests in that class are marked "Integration".)

@ahgittin
Copy link
Contributor Author

Unrelated test failures in penultimate jenkins build are discussed at #560

Copy link
Contributor

@aledsage aledsage left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ahgittin - looks great.

Most of my comments are minor - happy for you to use your judgement for what is worth changing.

I'd appreciate a description of how EntityConfigTest.testGetTaskNonBlockingMap() now passes - what prevents the underlying task from being cancelled?


boolean isShutdown();

<T> Maybe<T> getImmediately(Object callableOrSupplier);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth adding javadoc here - e.g. similar to what you've added in the impl BasicExecutionContext. Worth saying when it will return Maybe.absent (e.g. if the task execution requires blocking for other work, and can't complete in a timely fashion).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea. we should maybe move ImmediateSupplier to the utils package, then we could reference its javadoc? we might also change the return type to be ReferenceWithError<Maybe<T>> so the "can't immediately tell if there's a value" problem state can be detected without throwing. have marked @Beta for now.

if (targetEntityMaybe.isAbsent()) return Maybe.absent("Target entity not available");
if (targetEntityMaybe.isAbsent()) return Maybe.<Object>cast(targetEntityMaybe);
EntityInternal targetEntity = (EntityInternal) targetEntityMaybe.get();
checkAndTagForRecursiveReference(targetEntity);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm reading this right... it adds the tag to the current task, but then the tag is not removed at the end of this method - should it be?

Copy link
Contributor Author

@ahgittin ahgittin Feb 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the tag is left on the task. you're right that could cause problems if the calling code isn't in a task (maybe it always will be but safer not to assume).

UPDATE: we now always use a dedicated tag

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually i've done a better strategy -- @aledsage appreciate any thoughts on this:

                // don't check on ourself; only look higher in hierarchy;
                // this assumes impls always spawn new tasks (which they do, just maybe not always in new threads)
                // but it means it does not rely on tag removal to prevent weird errors, 
                // and more importantly it makes the strategy idempotent

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but actually this won't work either will it --

consider P1 needs to evaluate key C1, then key C2, and C2 refers to C1. if it isn't a dedicated subtask the second check will fail.

guess we need to ensure a dedicated subtask. :( . i'll see whether we can do that, probably with an "official" task tag type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did it, but without an official task type -- easy enough to get the routines to share code and trigger a dedicated (non-thread) task

doTestRecursiveConfigFailsGracefully(false);
}

// TODO this test fails because entities aren't available when evaluating immediately
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test passes for me - when/why does it fail, or does this comment need deleted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stale comment, good catch

// error, loop wasn't interrupted or detected
LOG.warn("Timeout elapsed, destroying items; usage: "+
((LocalManagementContext)mgmt()).getGarbageCollector().getUsageString());
//Entities.destroy(app);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete commented out code, or add additional comment to say when one would uncomment it.

//Entities.destroy(app);
} catch (RuntimeInterruptedException e) {
// expected on normal execution
Thread.interrupted();
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly, comment added

}

public static boolean hasTag(Task<?> task, Object tag) {
if (task==null) return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On balance I'm fine with this because we use the same pattern in lots of places in Brooklyn. But I think it's a bad pattern - it hides programming errors where null shouldn't have been passed in, in exchange for a small number of callers getting to save a few characters by skipping their null check. The former are so much more effort to fix that it's worth putting up with the latter.

// TODO if evaluating immediately should use a new ExecutionContext.submitImmediate(...)
// and sets a timeout but which wraps a task but does not spawn a new thread

if ((v instanceof TaskFactory<?>) && !(v instanceof DeferredSupplier)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add something like the following to ValueResolverTest:

    public void testTaskFactoryGet() {
        TaskFactory<TaskAdaptable<String>> taskFactory = new TaskFactory<TaskAdaptable<String>>() {
            @Override public TaskAdaptable<String> newTask() {
                return new BasicTask<>(Callables.returning("myval"));
            }
        };
        String result = Tasks.resolving(taskFactory).as(String.class).context(app).get();
        assertEquals(result, "myval");
    }
    
    public void testTaskFactoryGetImmediately() {
        TaskFactory<TaskAdaptable<String>> taskFactory = new TaskFactory<TaskAdaptable<String>>() {
            @Override public TaskAdaptable<String> newTask() {
                return new BasicTask<>(Callables.returning("myval"));
            }
        };
        String result = Tasks.resolving(taskFactory).as(String.class).context(app).immediately(true).get();
        assertEquals(result, "myval");
    }
    
    public void testTaskFactoryGetImmediatelyDoesNotBlock() {
        final AtomicBoolean executing = new AtomicBoolean();
        TaskFactory<TaskAdaptable<String>> taskFactory = new TaskFactory<TaskAdaptable<String>>() {
            @Override public TaskAdaptable<String> newTask() {
                return new BasicTask<>(new Callable<String>() {
                    public String call() {
                        executing.set(true);
                        try {
                            Time.sleep(Duration.ONE_MINUTE);
                            return "myval";
                        } finally {
                            executing.set(false);
                        }
                    }});
            }
        };
        Maybe<String> result = Tasks.resolving(taskFactory).as(String.class).context(app).immediately(true).getMaybe();
        assertTrue(result.isAbsent(), "result="+result);
        Asserts.succeedsEventually(new Runnable() {
            public void run() {
                assertFalse(executing.get());
            }
        });
    }

However, the last assertion fails - using immediately() with TaskFactory is dangerous because the task is left running. It is particularly dangerous because immediately() is most often used when it's being called regularly (e.g. the rest api wants to list all the config values).

Whose responsibility is it to cancel the task? (It feels like the task instance is buried inside the ValueResolver, so it would be hard for the caller to get hold of it to cancel it). Should the ValueResolver cancel the task it created, if using immediately()?

What are the use-cases for passing a TaskFactory into the ValueResolver?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO think about this

final CountDownLatch latch = new CountDownLatch(1);
Task<String> task = Tasks.<String>builder().body(
//
// If starting clean I (Alex) would agree, we should use TaskFactory. However the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect our DependentConfiguration class could do with a re-write, but not sure off-hand exactly what it should look like. And doesn't feel like it's worth doing right now.

It's trick because WaitInTaskForAttributeReady extends Callable expects to subscribe to (multiple) sensors, and then block for a matching value. We could most likely re-implement that (see below).

Using a TaskFactory would be tricky, because we only want to do the subscriptions once. It is conceptually a single "task" (i.e. we don't want to do the subscriptions multiple times).

My gut-feel is therefore we could implement it as a DeferredSupplier. When first called (or maybe when created), it would create the subscriptions. The thread executing the get would just be blocking on a CountDownLatch, which would be set by the subscription callbacks when the value was available (or when the entity had failed). Multiple calls to get() would share the same instance, so would all be blocking on the same thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting idea that it is a single (shared) task. I feel like each caller gets its own instance/subscription hence TaskFactory. But for another day... (and in any case I'd love us to move to a JS promise/then model)

public void testGetTaskFactoryNonBlockingKey() throws Exception {
new ConfigNonBlockingFixture().usingTaskFactory().runGetConfigNonBlockingInKey(); }
@Test(groups="Integration") // because takes 1s+
public void testGetTaskFactoryNonBlockingMap() throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this now work? We still have the call to task.cancel(true) in AbstractConfigurationSupportInternal.getNonBlockingResolvingStructuredKey(), so during the first call to getNonBlocking we'll cancel the task. Is it because that is a different task that does not pass the cancel() call down to the actual config-key-value task that we are trying to evaluate?

Copy link
Contributor Author

@ahgittin ahgittin Feb 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alex TODO investigate this, and testGetTaskNonBlockingMap(), and also there is still one map test failing, investigate it also

}

/** As {@link Throwables#getCausalChain(Throwable)} but safe in the face of perverse classes which return themselves as their cause or otherwise have a recursive causal chain. */
public static List<Throwable> getCausalChain(Throwable t) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add to ExceptionsTest:

    @Test
    public void testGetCausalChain() throws Exception {
        Exception e1 = new Exception("e1");
        Exception e2 = new Exception("e2", e1);
        assertEquals(Exceptions.getCausalChain(e2), ImmutableList.of(e2, e1));
    }
    
    @Test
    public void testGetCausalChainRecursive() throws Exception {
        Exception e1 = new Exception("e1") {
            public synchronized Throwable getCause() {
                return this;
            }
        };
        Exception e2 = new Exception("e2", e1);
        assertEquals(Exceptions.getCausalChain(e2), ImmutableList.of(e2, e1));
    }

@ahgittin
Copy link
Contributor Author

thanks @aledsage -- have done most of them. a few more will require more thought from me, marked them so i can find them easily.

…n't need to be removed

the previous attempt at idempotency could set the tag too broadly, e.g. when evaluating K1,
then subsequently looking at a K2 that refers to K1, the latter would think it's recursed inside the former
@ahgittin
Copy link
Contributor Author

All should be fixed now @aledsage

99ccc0f adds comments and assertions for the cancellation behaviour. TL;DR, TaskFactory config values should have their tasks cancelled, but Task values should not. The last commit fixes one thing which had been causing a failure in integration tests.

The remaining messy/broken tests seem to be mainly around immediate not having any guarantees due to timing. I'd like to try to switch most/all to use the interrupting-immediate pattern, but that'll be another PR :) .

Copy link
Contributor

@aledsage aledsage left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good - I've written a few more tests that fail (pasted into the comments). This is complicated stuff!

How do you want to proceed, @ahgittin? Do we need those new tests added and fixed before we merge this? Or should we merge this (with the new tests added and marked @Broken) and come back to it in a separate PR?

.body(newCallable(false)).build();
}

private Callable<Object> newCallable(final boolean immediate) {
Copy link
Contributor

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 !immediate then the return value of the callable will be the actual Object. 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).

Copy link
Contributor Author

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.

public <T> Maybe<T> getImmediately(Object callableOrSupplier) {
BasicTask<?> fakeTaskForContext;
if (callableOrSupplier instanceof BasicTask) {
fakeTaskForContext = (BasicTask<?>)callableOrSupplier;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Under what code path do we get here? Looking at ValueResolver.getMaybeInternal(...), allowImmediateExecution will be false if the object is a task (rather than a TaskFactory or DeferredSupplier).

For example, the test below fails (added to ValueResolverTest). It leaves a single instance of the task executing.

    public void testTaskGetImmediatelyDoesNotBlock() {
        final AtomicInteger executingCount = new AtomicInteger();
        
        final Task<String> task = new BasicTask<>(new Callable<String>() {
            public String call() {
                executingCount.incrementAndGet();
                try {
                    Time.sleep(Duration.ONE_MINUTE);
                    return "myval";
                } finally {
                    executingCount.decrementAndGet();
                }
            }});
        
        for (int i = 0; i < 3; i++) {
            Maybe<String> result = Tasks.resolving(task).as(String.class).context(app).immediately(true).getMaybe();
            Asserts.assertTrue(result.isAbsent(), "result="+result);
        }

        Asserts.assertFalse(task.isSubmitted());
        
        // The call below default times out after 30s while the task above is still running
        // Expect the task to not be left running.
        Asserts.succeedsEventually(new Runnable() {
            public void run() {
                Asserts.assertEquals(executingCount.get(), 0);
            }
        });
    }

I'm not convinced that we want to support setting a config key with a value of type Task (long term). But I guess that will require more work, to change how DependentConfiguration is implemented. So we do need to support it short-term :-(

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I change ValueResolver.getMaybeInternal() to set allowImmediateExecution = true; when it is passed a TaskAdaptable, then the above test does pass.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 TaskFactory); in particular EntityConfigTest.testGetTaskNonBlockingKey() fails if allowImmediateExecution=true

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for the particular code block marked here, it is invoked from ValueResolver when you supply a TaskFactory; VR generates the Task and passes it in with allowImmediateExecution=true

throw new ImmediateUnsupportedException("Task is in progress and incomplete: "+fakeTaskForContext);
}
}
callableOrSupplier = fakeTaskForContext.getJob();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very clever, getting the underlying Callable to execute from the job!

However, I wonder if this just reduces the chance of leaving tasks behind (rather than preventing it).

The test below fails when added to ValueResolverTest (when also including the change to ValueResolver.getMaybeInternal() to set allowImmediateExecution = true so that this code path is taken).

    public void testTaskGetImmediatelyDoesNotBlockWithNestedTasks() {
        final AtomicInteger executingCount = new AtomicInteger();
        
        final SequentialTask<?> outerTask = new SequentialTask<>(ImmutableList.of(new Callable<String>() {
            public String call() {
                executingCount.incrementAndGet();
                try {
                    Time.sleep(Duration.ONE_MINUTE);
                    return "myval";
                } finally {
                    executingCount.decrementAndGet();
                }
            }}));
        
        for (int i = 0; i < 3; i++) {
            Maybe<String> result = Tasks.resolving(outerTask).as(String.class).context(app).immediately(true).getMaybe();
            Asserts.assertTrue(result.isAbsent(), "result="+result);
        }
        
        Asserts.assertFalse(outerTask.isSubmitted());
        
        // the call below default times out after 30s while the task above is still running
        Asserts.succeedsEventually(new Runnable() {
            public void run() {
                Asserts.assertEquals(executingCount.get(), 0);
            }
        });
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above


//if it's a task or a future, we wait for the task to complete
if (v instanceof TaskAdaptable<?>) {
v = ((TaskAdaptable<?>) v).asTask();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When playing around with the extra tests (see previous comments), do we need to add allowImmediateExecution = true; when v instanceof TaskAdaptable? Or will that have other unwanted? side-effects?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above, definitely not

// more than safe, we have to do it -- or add code here to cancel tasks -- because it spawns new tasks
// (other objects passed through here don't get cancelled, because other things might try again later;
// ie a task or future passed in here might naturally be long-running so cancelling is wrong,
// but with a task factory generated task it would leak if we submitted and didn't cancel!)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another failing test I wrote in ValueResolverTest, which leaks tasks (because the outer task is cancelled, while the inner tasks are not).

I'm a bit surprised that it fails - I had assumed that the SequentialTask would cancel its children when it was cancelled.

    public void testTaskFactoryGetImmediatelyDoesNotBlockWithNestedTasks() {
        final int NUM_CALLS = 3;
        final AtomicInteger executingCount = new AtomicInteger();
        final List<SequentialTask<?>> outerTasks = Lists.newArrayList();
        
        TaskFactory<Task<?>> taskFactory = new TaskFactory<Task<?>>() {
            @Override public Task<?> newTask() {
                SequentialTask<?> result = new SequentialTask<>(ImmutableList.of(new Callable<String>() {
                    public String call() {
                        executingCount.incrementAndGet();
                        try {
                            Time.sleep(Duration.ONE_MINUTE);
                            return "myval";
                        } finally {
                            executingCount.decrementAndGet();
                        }
                    }}));
                outerTasks.add(result);
                return result;
            }
        };
        for (int i = 0; i < NUM_CALLS; i++) {
            Maybe<String> result = Tasks.resolving(taskFactory).as(String.class).context(app).immediately(true).getMaybe();
            Asserts.assertTrue(result.isAbsent(), "result="+result);
        }
        // the call below default times out after 30s while the task above is still running
        Asserts.succeedsEventually(new Runnable() {
            public void run() {
                Asserts.assertEquals(outerTasks.size(), NUM_CALLS);
                for (Task<?> task : outerTasks) {
                    Asserts.assertTrue(task.isDone());
                    Asserts.assertTrue(task.isCancelled());
                }
            }
        });
        Asserts.succeedsEventually(new Runnable() {
            public void run() {
                Asserts.assertEquals(executingCount.get(), 0);
            }
        });
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DST should cancel -- this looks like a bug

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aha, DST which we mostly use is fine, but its doCancel method was missing from the lighter-weight CompoundTask; have added it there and test passes

return null;
}
// let InterruptingImmediateSupplier.InterruptingImmediateSupplierNotSupportedForObject
// bet thrown, and caller who cares will catch that to know it can continue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: be thrown.
And could add javadoc:

    /**
     * @throws InterruptingImmediateSupplier.InterruptingImmediateSupplierNotSupportedForObject if ...
     */

try {
result = exec.getImmediately(immediateSupplierOrImmediateTask);
} catch (ImmediateSupplier.ImmediateUnsupportedException e) {
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer this to propagate the exception, rather than return null. I don't like the use of null to indicate that it couldn't be evaluated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@ahgittin
Copy link
Contributor Author

@aledsage Thanks for thorough comments. I'll review this afternoon and revert with whether to merge or not.

One high level comment: Task instances should be left running, even if immediate execution is requested, because someone else might subsequently ask for that value in a non-immediate context. If we cancelled after immediate execution then the subsequent request would fail. This isn't a leak but an implication of supporting Task evaluation (and for that reason I think we want to prefer TaskFactory evaluation). I think this addresses many of your comments; will look closer after lunch.

@ahgittin
Copy link
Contributor Author

@aledsage I think I've addressed these comments, TY; but now merge conflicts with #155 are hurting; as noted there I think we should revert that.

@ahgittin
Copy link
Contributor Author

ahgittin commented Mar 1, 2017

@aledsage minor changes addressing your comments, and fixed conflicts; would like your confirmation that an immediate Task "leaking" is the right behaviour (and longer term moving away from tasks as these values!) and anything else on your mind, but feels like it may be good to merge now :)

@aledsage
Copy link
Contributor

aledsage commented Mar 1, 2017

@ahgittin agreed - we should not cancel the task, so "leaking" it is right behaviour.

The build failure is real, until we commit to switching to Java 8 - please give your opinion on that email thread :-)

[ERROR] /home/jenkins/jenkins-slave/workspace/brooklyn-server-pull-requests/core/src/main/java/org/apache/brooklyn/util/core/task/BasicExecutionContext.java:[31,25] error: package java.util.function does not exist

That package was added in Java 8.

I think it's just the wrong import (should be guava's; it's only used for the javadoc).

Can you fix that, and then merge?

@ahgittin
Copy link
Contributor Author

ahgittin commented Mar 2, 2017

thanks @aledsage - fixed, if these tests pass, then good to merge

@aledsage
Copy link
Contributor

aledsage commented Mar 2, 2017

Excellent, thanks @ahgittin - merging now.

@asfgit asfgit merged commit e6fd10c into apache:master Mar 2, 2017
asfgit pushed a commit that referenced this pull request Mar 2, 2017
aledsage added a commit to aledsage/brooklyn-server that referenced this pull request May 24, 2017
As per previous TODO about PR apache#480, we don’t need to retrieve the config
in the context of the given entity.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants