Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Manually trigger a @Scheduled method

I need advice on the following:

I have a @Scheduled service method which has a fixedDelay of a couple of seconds in which it does scanning of a work queue and processing of apropriate work if it finds any. In the same service I have a method which puts work in the work queue and I would like this method to imediately trigger scanning of the queue after it's done (since I'm sure that there will now be some work to do for the scanner) in order to avoid the delay befor the scheduled kicks in (since this can be seconds, and time is somewhat critical).

An "trigger now" feature of the Task Execution and Scheaduling subsystem would be ideal, one that would also reset the fixedDelay after execution was initiated maually (since I dont want my manual execution to collide with the scheduled one). Note: work in the queue can come from external source, thus the requirement to do periodic scanning.

Any advice is welcome

Edit: The queue is stored in a document-based db so local queue-based solutions are not appropriate.

A solution I am not quite happy with (don't really like the usage of raw threads) would go something like this:

@Service
public class MyProcessingService implements ProcessingService {

    Thread worker;

    @PostCreate
    public void init() {
        worker = new Thread() {
            boolean ready = false;

            private boolean sleep() {
                synchronized(this) {
                    if (ready) {
                        ready = false;
                    } else {
                        try {
                            wait(2000);
                        } catch(InterruptedException) {
                            return false;
                        }
                    }
                }

                return true;
            }

            public void tickle() {
                synchronized(this) {
                    ready = true;
                    notify();
                }
            }

            public void run() {
                while(!interrupted()) {
                    if(!sleep()) continue;

                    scan();
                }
            }
        }

        worker.start();
    }

    @PreDestroy
    public void uninit() {
        worker.interrup();
    }

    public void addWork(Work work) {
        db.store(work);

        worker.tickle();
    }

    public void scan() {
        List<Work> work = db.getMyWork();

        for (Work w : work) {
            process();
        }
    }

    public void process(Work work) {
        // work processing here
    }

}
like image 263
m1h4 Avatar asked May 23 '13 16:05

m1h4


People also ask

Is it possible to call a spring scheduled method manually?

There is no limitation to call a scheduled method manually.

How do I manually run a scheduler?

Go to the Scheduled Tasks applet in Control Panel, right-click the task you want to start immediately, and select Run from the displayed context menu.

How do you trigger a scheduled spring batch job?

Spring Batch Scheduling: Spring Batch Jobs Scheduling You can configure Spring Batch Jobs in two different ways: Using the @EnableScheduling annotation. Creating a method annotated with @Scheduled and providing recurrence details with the job. Then add the job execution logic inside this method.


1 Answers

Since the @Scheduled method wouldn't have any work to do if there are no items in the work-queue, that is, if no one put any work in the queue between the execution cycles. On the same note, if some work-item was inserted into the work-queue (by an external source probably) immediately after the scheduled-execution was complete, the work won't be attended to until the next execution.

In this scenario, what you need is a consumer-producer queue. A queue in which one or more producers put in work-items and a consumer takes items off the queue and processes them. What you want here is a BlockingQueue. They can be used for solving the consumer-producer problem in a thread-safe manner.

You can have one Runnable that performs the tasks performed by your current @Scheduled method.

public class SomeClass {
        private final BlockingQueue<Work> workQueue = new LinkedBlockingQueue<Work>();

    public BlockingQueue<Work> getWorkQueue() {
        return workQueue;
    }

    private final class WorkExecutor implements Runnable {

        @Override
        public void run() {
            while (true) {
                try {
                    // The call to take() retrieves and removes the head of this
                    // queue,
                    // waiting if necessary until an element becomes available.
                    Work work = workQueue.take();
                    // do processing
                } catch (InterruptedException e) {
                    continue;
                }
            }
        }
    }

    // The work-producer may be anything, even a @Scheduled method
    @Scheduled
    public void createWork() {
        Work work = new Work();
        workQueue.offer(work);
    }

}

And some other Runnable or another class might put in items as following:

public class WorkCreator {

    @Autowired 
    private SomeClass workerClass;

    @Override
    public void run() {
        // produce work
        Work work = new Work();
        workerClass.getWorkQueue().offer(work);
    }

}

I guess that's the right way to solve the problem you have at hand. There are several variations/configurations that you can have, just look at the java.util.concurrent package.

Update after question edited

Even if the external source is a db, it is still a producer-consumer problem. You can probably call the scan() method whenever you store data in the db, and the scan() method can put the data retrieved from the db into the BlockingQueue.

To address the actual thing about resetting the fixedDelay

That is not actually possible, wither with Java, or with Spring, unless you handle the scheduling part yourself. There is no trigger-now functionality as well. If you have access to the Runnable that's doing the task, you can probably call the run() method yourself. But that would be the same as calling the processing method yourself from anywhere and you don't really need the Runnable.

Another possible workaround

private Lock queueLock = new ReentrantLock();


@Scheduled
public void findNewWorkAndProcess() {
    if(!queueLock.tryLock()) {
        return;
    }
    try {
        doWork();
    } finally {
        queueLock.unlock();
    }
}

void doWork() {
    List<Work> work = getWorkFromDb();
    // process work
}

// To be called when new data is inserted into the db.
public void newDataInserted() {
    queueLock.lock();
    try {
        doWork();
    } finally {
        queueLock.unlock();
    }
}

the newDataInserted() is called when you insert any new data. If the scheduled execution is in progress, it will wait until it is finished and then do the work. The call to lock() here is blocking since we know that there is some work in the database and the scheduled-call might have been called before the work was inserted. The call to acquire lock in findNewWorkAndProcess() in non-blocking as, if the lock has been acquired by the newDataInserted method, it would mean that the scheduled method shouldn't be executed.

Well, you can fine tune as you like.

like image 176
Bhashit Parikh Avatar answered Sep 29 '22 02:09

Bhashit Parikh