Tasks subsystem improvements - adjuncts, names, efficiency, GC#835
Tasks subsystem improvements - adjuncts, names, efficiency, GC#835asfgit merged 22 commits intoapache:masterfrom
Conversation
description could be used for more things, exposed more broadly, but this is needed for now to make sense of those tasks
…scriptions quite so aggressively
… methods to take name
…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
d5b326a to
9213f0e
Compare
|
Completed this round of improvements. A few more improvements:
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 |
aledsage
left a comment
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) */ |
There was a problem hiding this comment.
Missing comma in String Runnable.
| return validateAll(); | ||
| ExecutionContext exec = | ||
| getBrooklynObject() instanceof EntityInternal ? ((EntityInternal)getBrooklynObject()).getExecutionContext() : | ||
| // getBrooklynObject() instanceof AbstractEntityAdjunct ? ((AbstractEntityAdjunct)getBrooklynObject()).getExecutionContext() : |
There was a problem hiding this comment.
Strong believer in comments next to commented out code to say when someone would uncomment it!
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
Minor: I prefer splitting it onto multiple lines (this line is now 168 chars long, the first 12 being the indent).
There was a problem hiding this comment.
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(), |
There was a problem hiding this comment.
I'd prefer us to add managementContext.getExecutionContext(entity, adjunct) (so it follows the same pattern as for getSubscriptionContext).
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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)), () -> { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
ooh handy forgot about this!
| new Thread(set).start(); | ||
| } | ||
|
|
||
| Asserts.succeedsEventually(MutableMap.of("timeout", Duration.seconds(5)), () -> { |
There was a problem hiding this comment.
As above, don't bother with bespoke timeouts. (Same below).
|
good comments, and straightforward, all addressed |
9b8d13e to
2dcb0a0
Compare
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