0

The problem

Required is a service, initialized with a pre-set time span (for example 3 seconds), that receives work (objects) over time and each time 3 seconds have passed with no new work, it releases all the accumulated work as a collection.

Each time a collection of items is released, that collection should be processed by a user specified processor (see ElementsProcessor below).

Example

Assume the pre-set time span is 3000ms and the received work is:

1 -- 1000ms -- 2 -- 1000ms -- 3 -- 1000ms -- 4 -- 3500ms -- 5

The result should be items 1,2,3,4 released together 3 seconds after item 4 arrival, and item 5 will be released later, once 3 seconds with no new work have passed.

My temporary solution

I attempted to implement this using ScheduledFuture but encountered a problem because it seems one can tell from it whether a task completed or not but not whether it started or not. Same issue with FutureTask. And one of the constraints I have is that work, once started, shouldn't be interrupted.

So I had to resort to some ugly hack looking at the delay of the ScheduledFuture. So I believe I might be doing something wrong and I'd love to hear about better ideas.

For reference I put my code (2 files) here:

import java.util.Collection;

/**
 * @param <E>   element type.
 * @param <V>   the result type of method {@link #process(Collection)}.
 */
public interface ElementsProcessor<E, V>
{
    public V process(Collection<E> elements);
}

and

import static java.util.concurrent.TimeUnit.MILLISECONDS;

import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Deque;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;

public class WorkAggregator<E, V>
{
    private final ElementsProcessor<E, V> elementsProcessor;
    private final long waitPeriod;
    private Deque<E> elements = new ArrayDeque<>();
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    private ScheduledFuture<V> scheduledFuture;

    public WorkAggregator(ElementsProcessor<E, V> elementsProcessor, long waitPeriod)
    {
        this.elementsProcessor = elementsProcessor;
        this.waitPeriod = waitPeriod;
    }

    public synchronized void add(E element)
    {
        attemptToCancelScheduledWork();
        elements.add(element);
        print("In add(), added element " + element + ", elements=" + elements);
        Handler<E, V> handler = new Handler<E, V>(elementsProcessor, elements);
        scheduledFuture = scheduler.schedule(handler, waitPeriod, MILLISECONDS);
    }

    private void attemptToCancelScheduledWork()
    {
        if (scheduledFuture == null)
            return;
        long delay = scheduledFuture.getDelay(MILLISECONDS);
        print("In attemptToCancelScheduledWork(), scheduledFuture delay=" + delay);
        if (delay < 50 || !scheduledFuture.cancel(false)) // delay < 50 - ugly hack!
            elements = new ArrayDeque<>(); // if we weren't able to cancel work we need to remove it from future processing
    }

    public synchronized void flush()
    {
        attemptToCancelScheduledWork();
        if (!elements.isEmpty())
            elementsProcessor.process(elements);
    }

    public synchronized void close() throws InterruptedException
    {
        print("In close() elements=" + elements);
        flush();
        print("In close() scheduler=" + scheduler);
        scheduler.shutdown();
        scheduler.awaitTermination(1000, MILLISECONDS);
    }

    private static void print(String s)
    {
        System.out.format("%tT  %-16s %s\n", Calendar.getInstance(), Thread.currentThread().getName(), s);
    }

    public static void main(String... args) throws InterruptedException
    {
        long[] sleepTimes = { 1000, 1000, 1000, 3500, 3500, 1000, 3500, 1000, 1000 }; // each value is the sleep AFTER the element is added
        ElementsProcessor<String, Boolean> elementsProcessor = new ElementsProcessor<String, Boolean>()
        {
            @Override
            public Boolean process(Collection<String> elements)
            {
                print("Starting processing " + elements);
                try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
                print("Finished processing " + elements);
                return null;
            }
        };
        WorkAggregator<String, Boolean> workAggregator = new WorkAggregator<>(elementsProcessor, 3000);
        print("Start, sleep-times=" + Arrays.toString(sleepTimes));
        for (int i = 1; i <= sleepTimes.length; i++)
        {
            workAggregator.add(String.valueOf(i));
            try { Thread.sleep(sleepTimes[i - 1]); } catch (InterruptedException e) { e.printStackTrace(); }
        }
        workAggregator.close();
        print("The end");
    }
}

class Handler<E, V> implements Callable<V>
{
    private final ElementsProcessor<E, V> elementsProcessor;
    private final Deque<E> elements;

    Handler(ElementsProcessor<E, V> elementsProcessor, Deque<E> elements)
    {
        this.elementsProcessor = elementsProcessor;
        this.elements = elements;
    }

    @Override
    public V call() throws Exception
    {
        return elementsProcessor.process(elements);
    }
}

The output from main() is:

19:58:01  main             Start, sleep-times=[1000, 1000, 1000, 3500, 3500, 1000, 3500, 1000, 1000]
19:58:01  main             In add(), added element 1, elements=[1]
19:58:02  main             In attemptToCancelScheduledWork(), scheduledFuture delay=1999
19:58:02  main             In add(), added element 2, elements=[1, 2]
19:58:03  main             In attemptToCancelScheduledWork(), scheduledFuture delay=1999
19:58:03  main             In add(), added element 3, elements=[1, 2, 3]
19:58:04  main             In attemptToCancelScheduledWork(), scheduledFuture delay=1999
19:58:04  main             In add(), added element 4, elements=[1, 2, 3, 4]
19:58:07  pool-1-thread-1  Starting processing [1, 2, 3, 4]
19:58:08  main             In attemptToCancelScheduledWork(), scheduledFuture delay=-500
19:58:08  main             In add(), added element 5, elements=[5]
19:58:08  pool-1-thread-1  Finished processing [1, 2, 3, 4]
19:58:11  pool-1-thread-1  Starting processing [5]
19:58:11  main             In attemptToCancelScheduledWork(), scheduledFuture delay=-500
19:58:11  main             In add(), added element 6, elements=[6]
19:58:12  pool-1-thread-1  Finished processing [5]
19:58:12  main             In attemptToCancelScheduledWork(), scheduledFuture delay=1999
19:58:12  main             In add(), added element 7, elements=[6, 7]
19:58:15  pool-1-thread-1  Starting processing [6, 7]
19:58:16  main             In attemptToCancelScheduledWork(), scheduledFuture delay=-500
19:58:16  main             In add(), added element 8, elements=[8]
19:58:16  pool-1-thread-1  Finished processing [6, 7]
19:58:17  main             In attemptToCancelScheduledWork(), scheduledFuture delay=1999
19:58:17  main             In add(), added element 9, elements=[8, 9]
19:58:18  main             In close() elements=[8, 9]
19:58:18  main             In attemptToCancelScheduledWork(), scheduledFuture delay=1999
19:58:18  main             Starting processing [8, 9]
19:58:19  main             Finished processing [8, 9]
19:58:19  main             In close() scheduler=java.util.concurrent.ScheduledThreadPoolExecutor@3d4eac69[Running, pool size = 1, active threads = 0, queued tasks = 1, completed tasks = 8]
19:58:19  main             The end
D. L.
  • 481
  • 4
  • 5
  • Can you write, and post here, a test (or series of tests) that demonstrates the behaviour you want? – Software Engineer Apr 25 '16 at 10:33
  • I didn't read your code, but I re-factored your description for you :) "This is a service, initialized with a pre-set time span (for example 3 seconds), that receives RESULTS of the work. Each time 3 seconds have passed with no new RESULTS, it releases all the accumulated RESULTS as a collection". Now you need something like: "This is a worker, that never stops stacking results of its work (Objects) somewhere." – Palcente Apr 25 '16 at 11:00
  • @Engineer Dollery - the output from main() is the desired output. Just that I get to it using a way that includes a hack. – D. L. Apr 25 '16 at 12:25
  • @Palcente - no, it does not receive results of the work. It receives work elements, aggregates them over time into collections until 3 seconds with no new work have passed, and when that happens those elements are processed together in a thread from the thread pool (unless close() is called and then it's in the 'main' thread...) – D. L. Apr 25 '16 at 12:33

0 Answers0