river-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Peter Firmstone <j...@zeus.net.au>
Subject Re: A new implementation of TaskManager
Date Wed, 07 Jul 2010 03:02:33 GMT
So it would seem that the runAfter() method is superfluous and 
unnecessary in most, if not all cases?  Especially if the caller is 
making the decision about when tasks are placed on the queue.

We do need to confirm that in our current implementations, the callers 
are taking responsibility for the queue placement.  Then try to 
discovery why we needed to have a dependency check by the TaskManager.

It sounds like for absolute concurrency the design needs to be totally 
revised, perhaps the responsibility of determining if the Task is ready 
to be executed should be placed solely on the Task implementation 
itself, but not by passing in the list of all Tasks on the queue, but 
with a simple:

interface Task extends Runnable {
    boolean ready();

Or perhaps it should be an Event like system.   The Task could be 
notified by all the immediate Tasks it depends on (direct dependency 
links, not an entire chain) when they have completed. When the Task 
receives all completion notifications for required dependencies, it 
places itself on the TaskManager queue, when it completes it notifies 
all it's dependants.

Tasks could register dependencies with each other (as could anything 
that had a reference to a Task), then if Tasks are arriving from remote 
machines, and they have dependency links, prior to being serialized, 
then deserialization will assist with sequencing.

The incumbent TaskManager could be retrieved from a static factory method.

interface Task extends Runnable {
    void runFirst(Task t) throws CircularReferenceException;
    void runAfter(Task t) throws CircularReferenceException; // If 
complete notify completion should be called back immediately.
    void notifyCompletion(Task t);
    void poke(); // Responsible for placing Task on queue if not done so 
already and only when it has no dependencies.  Must be called at least once.

When a Task is notified that some other Task must be run first, it 
notifies the Task it receives, that it must run after it.  A task keeps 
an internal Set of dependencies, every time a notifyCompletion is 
recieved, it removes that Task from the Set and every time it receives a 
runFirst, it adds a Task, once the Set isEmpty(), the Task places itself 
on the TaskManager queue.  A Task also keeps a set of dependant's to 
notify completion. This looks after Garbage Collection and the interface 
is idempotent.  This also might mean that some Tasks are executed on 
more than one machine, but that wouldn't matter, the Task is guaranteed 
to execute at least once, after poke() has been called.  The poke() 
method might call the poke() method on all it's dependencies (but not 
dependants) to guarantee execution.

You would only need to know one Task in the tree to cause execution to 
proceed.  Any Implementation coordinating the Tasks only need to know 
the other Tasks that immediately proceed it.

References look after the rest.  I wonder if it would need a stop 
button, it would be sort of like a chain reaction, maybe a cancel() 
method, which would call cancel() on all dependants, causing all run() 
methods to return immediately.

Just a thought.



Gregg Wonderly wrote:
> As you note below, a sequence number or some other ordering mechanism 
> is the most common way that is used to order tasks.  The task objects 
> are added to the queue in the order that the sequence number 
> increases, so it's not possible for an task to appear in the queue 
> until after its dependents (those that execute before it) are already 
> there.
> This type of logic is essential to making this type of thing work.
> Gregg
>> You know, task implementation objects could communicate using a 
>> static atomic integer sequence id, all task objects could use it to 
>> determine if they are ready to execute, if a task has an object 
>> sequence number, equal to the sequence id, then it is ready to 
>> execute, if less, it could throw a sequence exception, if greater, it 
>> isn't ready.  The same behaviour could be managed by a Group 
>> implementation.
>> Just an idea, don't know if it is relevant to any implementations.
>>>> This approach has its highest cost if the TaskManager has a lot of 
>>>> tasks, and we are adding a Task depends on none of them but is of a 
>>>> type that might depends on another Task, so it has to scan the 
>>>> entire Iterable.

View raw message