mercredi 5 décembre 2018

Stopping scheduled task with SIGINT

Background

I am trying to stop periodic tasks when user interrupts process with SIGINT. I have based my periodic task scheduler on this answer.

To accomplish this I tried passing PeriodicScheduler instance pointer to my InterruptHandler and calling ps->stop().

Periodic Task Scheduler header:

#ifndef __PERIODICSCHEDULER_H__
#define __PERIODICSCHEDULER_H__

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/noncopyable.hpp>

namespace APP{
    class PeriodicTask : boost::noncopyable {
        public:
            typedef std::function<void()> handler_fn;

            PeriodicTask(boost::asio::io_service& ioService
                , std::string const& name
                , int interval
                , handler_fn task);
            void execute(boost::system::error_code const& e);
            void start();

        private:
            void start_wait();

            boost::asio::io_service& ioService;
            boost::asio::deadline_timer timer;
            handler_fn task;
            std::string name;
            int interval;

    }; /* class PeriodicTask */

    class PeriodicScheduler : boost::noncopyable
    {
        public:
            template<typename T, typename... Args>
            std::unique_ptr<T> make_unique(Args&&... args) {
                return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
            }

            void run();
            void stop();
            void addTask(std::string const& name
                                            , PeriodicTask::handler_fn const& task
                                            , int interval);

        private:
            boost::asio::io_service io_service;
            std::vector<std::unique_ptr<PeriodicTask>> tasks;
    }; /* PeriodicScheduler */
} /* namespace Resto */

#endif /* __PERIODICSCHEDULER_H__ */

Periodic Task Scheduler source:

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/noncopyable.hpp>

#include "periodicScheduler.h"

APP::PeriodicTask::PeriodicTask(boost::asio::io_service& ioService
            , std::string const& name
            , int interval
            , handler_fn task)
            : ioService(ioService)
            , interval(interval)
            , task(task)
            , name(name)
            , timer(ioService){
            // Schedule start to be ran by the io_service
            ioService.post(boost::bind(&PeriodicTask::start, this));
        }

void APP::PeriodicTask::execute(boost::system::error_code const& e){
    if (e != boost::asio::error::operation_aborted) {

        task();

        timer.expires_at(timer.expires_at() + boost::posix_time::seconds(interval));
        start_wait();
    }
}

void APP::PeriodicTask::start(){

    // Uncomment if you want to call the handler on startup (i.e. at time 0)
    // task();

    timer.expires_from_now(boost::posix_time::seconds(interval));
    start_wait();
}

void APP::PeriodicTask::start_wait(){
    timer.async_wait(boost::bind(&PeriodicTask::execute
        , this
        , boost::asio::placeholders::error));
}



void APP::PeriodicScheduler::run(){
    io_service.run();
}

void APP::PeriodicScheduler::stop(){
    io_service.stop();
}

void APP::PeriodicScheduler::addTask(std::string const& name
    , PeriodicTask::handler_fn const& task
    , int interval){
    tasks.push_back(make_unique<PeriodicTask>(std::ref(io_service)
        , name, interval, task));
}

The following is InterruptHandler:

#include <csignal>
#include <condition_variable>
#include <mutex>
#include <iostream>
#include <boost/asio.hpp>

#include "periodicScheduler.h"

static std::condition_variable _condition;
static std::mutex _mutex;

namespace APP {
    class InterruptHandler {
    public:
        static void hookSIGINT() {
            signal(SIGINT, handleUserInterrupt);        
        }

        static void handleUserInterrupt(int signal){
            if (signal == SIGINT) {
                std::cout << "SIGINT trapped ..." << '\n';
                _condition.notify_one();
            }
        }

        static void waitForUserInterrupt(Resto::PeriodicScheduler *ps) {
            std::unique_lock<std::mutex> lock { _mutex };
            _condition.wait(lock);
            ps->stop();
            std::cout << "user has signaled to interrup program..." << '\n';
            lock.unlock();
        }
    };
}

My main()

int main(int ac, const char * av[]) {

    InterruptHandler::hookSIGINT();

    APP::PeriodicScheduler ps;

    APP::WorkerClass wc;

    // WorkerClass::someTask and WorkerClass:someOtherTask are dummy functions only with sleep(5); inside them

    ps.addTask("someTask", boost::bind( &APP::WorkerClass::someTask, wc ), 60);
    ps.addTask("someOtherTask", boost::bind( &APP::WorkerClass::someOtherTask, wc ), 60);

    ps.run();

    InterruptHandler::waitForUserInterrupt(&ps);

    return 0;
}

Issue

After running my app in terminal I pressed CTRL+C to trigger interrupt. I can see SIGINT trapped ... in the terminal but, application continues to run.

If I comment out ps.run(); statement, upon pressing CTRL+C I can see SIGINT trapped ... followed by user has signaled to interrup program... and application exits.

Questions

Is my approach correct? How can I effectively stop scheduled tasks and exit application?

Did I miss something?

Aucun commentaire:

Enregistrer un commentaire