Skip to content

Tasks subsystem improvements - adjuncts, names, efficiency, GC#835

Merged
asfgit merged 22 commits intoapache:masterfrom
ahgittin:tasks-better-tree
Oct 6, 2017
Merged

Tasks subsystem improvements - adjuncts, names, efficiency, GC#835
asfgit merged 22 commits intoapache:masterfrom
ahgittin:tasks-better-tree

Conversation

@ahgittin
Copy link
Copy Markdown
Contributor

  • Adjuncts' tasks run in their context with tag so can be found more easily
  • Better naming of tasks across the board (so the look nicer in the UI)
  • More tidies to same-thread execution and run more things in same thread
  • Tidied task GC code, and changed to GC less aggressively
  • Fix race in initialization tasks

This increases the number of tasks kept around because sensor events are actually interesting (previously they were always garbage collected). However this may impact performance at scale: that deserves some investigation. There may be a balance to be had re which tasks to keep around and for how long.

NB this builds on #816, review that first

…ynamicTasks.get.

but note some things fail getImmediately if they are running a queueing context eg EffectorSayHiTest,
until fixed in the next PR.
means more things work in immediate mode.
could be un-done if DynamicSequentialTask removes the DST manager thread.
fixes a few failing tests in the previous commit.
both for initial subscriptions and in-life - two changes, synching in AttributesInternal for the in-life delivery, and in subscribe/publish for the initial
@ahgittin
Copy link
Copy Markdown
Contributor Author

ahgittin commented Oct 4, 2017

Completed this round of improvements. A few more improvements:

  • many dynamic tasks run in same thread where possible, reducing overhead and supporting immediate execution
  • fix bug where subscriptions could be delivered out of order, where last subscription might not correspond to last value

The last item, in 9213f0e, does a bit more synching so is slightly risky, but I've minimized this and I think it is the correct thing to do, consistent at least with other code paths. Testing so far is good with it and there are ample notes on why.

What I have not done here is deprecation or warning in getImmediately(Task), as suggested in #816. That's turning in to a big job so I'll do it in another PR from which I'll reference this (so you should see a message about it below shortly.)

Copy link
Copy Markdown
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.

LGTM; only minor comments.

// won't be in this block. an issue with _subscribe-and-get-initial_
// should be resolved by initial subscription queueing the publication
// to a context where locks are not held.
synchronized (values) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

TL;DR: I agree this is fine. Below are some notes from digging around in the synchronization code.

Looking at the other synchronization blocks, emitInternal will synchronize on EntityManagementSupport instance (when it calls getSubscriptionContext(). It will then call publish which will synchronize on LocalSubscriptionManager instance (in getSubscriptionsForEntitySensor).

However, I think we can trust both the EntityManagementSupport and the LocalSubscriptionManager to not call out to alien code while holding a lock on itself. Therefore we should be safe in that respect.

The code in AbstractEntity and AbstractGroupImpl looks a bit scary, where it first gets the lock on this values object and then on either abstractGroup.members or abstractEntity.children (but it's kind-of understandable why it does that and how that pattern avoids it; it would just be nicer if instead we didn't call out to alient code while holding the lock on values - but that's a bigger discussion than for this PR):

        synchronized (getAttributesSynchObjectInternal()) {
            synchronized (members) {

@Deprecated
<T> Task<T> submit(Callable<T> callable);

/** {@link ExecutionManager#submit(String Runnable) */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Missing comma in String Runnable.

return validateAll();
ExecutionContext exec =
getBrooklynObject() instanceof EntityInternal ? ((EntityInternal)getBrooklynObject()).getExecutionContext() :
// getBrooklynObject() instanceof AbstractEntityAdjunct ? ((AbstractEntityAdjunct)getBrooklynObject()).getExecutionContext() :
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Strong believer in comments next to commented out code to say when someone would uncomment it!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yep, in this case it should have been uncommented as part of this PR

return subscriptionContext;
// see also AbstractManagementContext.getSubscriptionContext - needed for callbacks, to ensure the correct entity context is set
Map<String, ?> subscriptionFlags = ImmutableMap.of("tags", ImmutableList.of(BrooklynTaskTags.tagForContextEntity(entity)));
return new BasicSubscriptionContext(subscriptionFlags, qsm, entity);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why instantiate this in the getSubscriptionContext rather than the constructor?
Do you really want a new instance every time it is called, rather than lazy-instantiation and returning the same one?

For the record, I don't think it should really matter (i.e. fine as-is) - at some point we should be able to delete lots of this code. It was originally written when folk could call entity constructors directly and then call methods on it (so the entity would not have a usable management context).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

it's a thin shim so doesn't matter if we cache or recreate

would be nice if we could remove the whole non-deployment mgmt context stage. suspect there are things like initializers and maybe rebind steps that we do want to run before other things or be queued to be the first things run when management context is attached so it may be that this complexity is still necessary.

} catch (Exception e) {
throw Exceptions.propagate(e);
}
return ec.get(Tasks.<List<Application>>builder().displayName("rebind").dynamic(false).body(() -> rebindImpl(classLoader, exceptionHandler, mode)).build());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Minor: I prefer splitting it onto multiple lines (this line is now 168 chars long, the first 12 being the indent).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

definitely, line getting too long to see what it really does

if (destroyed.get()) throw new IllegalStateException("Cannot set entity on a destroyed entity adjunct");
this.entity = entity;
this.execution = ((EntityInternal) entity).getExecutionContext();
this.execution = new BasicExecutionContext( getManagementContext().getExecutionManager(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd prefer us to add managementContext.getExecutionContext(entity, adjunct) (so it follows the same pattern as for getSubscriptionContext).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes, that's cleaner

.description("Details of the original task have been forgotten.")
.body(Callables.returning((T)null)).build();
((BasicTask<T>)t).ignoreIfNotRun();
((BasicTask<T>)t).cancelled = true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Worth a comment saying why you're setting cancelled = true. I presume it's because we don't really want anyone executing the "gone" task, and more importantly that if we are GC'ing tasks then marking the "gone" task as cancelled will help with cleanup of sub-tasks that have lost their submitted-by-task reference?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

never occurred to me that marking cancelled might help with GC. have added comments.

the only thing i was optimizing was the surprising expense of soft references (noted 30 lines earlier)

EntityAsserts.assertAttributeEqualsEventually(entity, TestEntity.NAME, after);
}
});
Task<?> assertValue = entity.getExecutionContext().submit("assert attr equals", () -> EntityAsserts.assertAttributeEqualsEventually(entity, TestEntity.NAME, after));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Preference breaking a long line across multiple lines.

// delivery should be in subscription order, so 123 then 456
entity.subscriptions().subscribe(ImmutableMap.of("notifyOfInitialValue", true), entity, TestEntity.SEQUENCE, listener);
// wait for the above delivery - otherwise it might get dropped
Asserts.succeedsEventually(MutableMap.of("timeout", Duration.seconds(5)), () -> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Don't bother setting timeouts - just rely on the defaults. If you want things to run faster locally, then set the Java system property -Dbrooklyn.test.defaultTimeout=5s.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ooh handy forgot about this!

new Thread(set).start();
}

Asserts.succeedsEventually(MutableMap.of("timeout", Duration.seconds(5)), () -> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As above, don't bother with bespoke timeouts. (Same below).

@ahgittin
Copy link
Copy Markdown
Contributor Author

ahgittin commented Oct 5, 2017

thanks @aledsage - great comments, will adress then merge this and #836

@ahgittin
Copy link
Copy Markdown
Contributor Author

ahgittin commented Oct 6, 2017

good comments, and straightforward, all addressed

@asfgit asfgit merged commit 2dcb0a0 into apache:master Oct 6, 2017
asfgit pushed a commit that referenced this pull request Oct 6, 2017
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