<commit_before>#include "runner/at_handler.hh"

#include <iostream>
#include <list>

#include <boost/bind.hpp>
#include <boost/ptr_container/ptr_list.hpp>

#include "scheduler/tag.hh"

namespace runner
{

  class AtJob
  {
  public:
    AtJob(rObject condition, rObject clause, rObject on_leave,
	  scheduler::tags_type tags);
    bool blocked() const;
    bool frozen() const;
    const rObject& condition_get() const;
    const rObject& clause_get() const;
    const rObject& on_leave_get() const;
    bool triggered_get() const;
    void triggered_set(bool);
    const scheduler::tags_type& tags_get() const;
  private:
    rObject condition_;
    rObject clause_;
    rObject on_leave_;
    bool triggered_;
    scheduler::tags_type tags_;
  };

  class AtHandler : public Interpreter
  {
  public:
    AtHandler(const Interpreter&);
    virtual ~AtHandler();
    virtual void work();
    virtual bool frozen() const;
    virtual bool blocked() const;
    void add_job(AtJob*);
  private:
    void complete_tags(const AtJob&);
    void rebuild_tags();
    typedef boost::ptr_list<AtJob> at_jobs_type;
    at_jobs_type jobs_;
    bool yielding;
    scheduler::tags_type tags_;
  };

  // There is only one at job handler. It will be created if it doesn't exist
  // and destroyed when it has no more jobs to handle.
  static AtHandler* at_job_handler;

  AtHandler::AtHandler(const Interpreter& model)
    : Interpreter(model, 0),
      yielding(false)
  {
  }

  AtHandler::~AtHandler()
  {
    jobs_.release();
  }

  void
  AtHandler::work()
  {
    bool check_for_blocked = true;
    bool tags_need_rebuilding = false;
    side_effect_free_set(true);

    while (true)
    {
      non_interruptible_set(true);
      yielding = false;

      // If we have to check for blocked jobs, do it at the beginning
      // to make sure we do not miss a "stop" because some condition
      // evaluation mistakenly reenters the scheduler. We know that we
      // have to check for blocked jobs because we have had an indication
      // that tags needed to be rebuilt.

      if (tags_need_rebuilding)
	jobs_.erase_if(boost::bind(&AtJob::blocked, _1));

      for (at_jobs_type::iterator job = jobs_.begin();
	   job != jobs_.end();
	   /* Do not increment as we will also use erase() to advance */)
      {
	// If job has been frozen, we will not consider it for the moment.
	if (job->frozen())
	{
	  ++job;
	  continue;
	}

	// Check the job condition and continue if it has not changed.
	bool new_state;
	try
	{
	  new_state =
	    object::is_true(urbi_call(*this, job->condition_get(), SYMBOL(eval)));
	}
	catch (const kernel::exception& ke)
	{
	  std::cerr << "at condition triggered an exception: " << ke.what()
		    << std::endl;
	  tags_need_rebuilding = true;
	  job = jobs_.erase(job);
	  continue;
	}
	catch (...)
	{
	  std::cerr << "at condition triggered an exception\n";
	  job = jobs_.erase(job);
	  continue;
	}
	if (new_state == job->triggered_get())
	{
	  ++job;
	  continue;
	}

	// There has been a change in the condition, act accordingly depending
	// on whether we have seen a rising or a falling edge and save the
	// condition evaluation result.
	const rObject& to_launch =
	  new_state ? job->clause_get() : job->on_leave_get();
	if (to_launch != object::nil_class)
	{
	  // Temporarily install the needed tags as the current tags.
	  tags_set(job->tags_get());

	  // We do not need to check for an exception here as "detach",
	  // which is the function being called, will not throw and any
	  // exception thrown in the detached runner will not be caught
	  // here anyway.
	  urbi_call(*this, to_launch, SYMBOL(eval));
	}
	job->triggered_set(new_state);
	++job;
      }

      // If we have no more jobs, we can destroy ourselves.
      if (jobs_.empty())
      {
	at_job_handler = 0;
	terminate_now();
      }

      non_interruptible_set(false);
      check_for_blocked = false;
      yielding = true;

      // Rebuild tags if our list of jobs has changed.
      if (tags_need_rebuilding)
	rebuild_tags();
      tags_need_rebuilding = false;

      // Go to sleep
      try
      {
	// We want to appear blocked only when explicitly yielding and
	// catching the exception. If, by mistake, a condition evaluation
	// yields and is blocked, we do not want it to get the bogus
        // exception.
	yield_until_things_changed();
      }
      catch (const scheduler::BlockedException& e)
      {
	// We have at least one "at" job which needs to be blocked.
	tags_need_rebuilding = true;
      }
      catch (const kernel::exception& e)
      {
	std::cerr << "at job handler exited with exception " << e.what()
		  << std::endl;
	throw;
      }
      catch (...)
      {<commit_msg>Make at job handler side effect free even after a stop.<commit_after>#include "runner/at_handler.hh"

#include <iostream>
#include <list>

#include <boost/bind.hpp>
#include <boost/ptr_container/ptr_list.hpp>

#include "scheduler/tag.hh"

namespace runner
{

  class AtJob
  {
  public:
    AtJob(rObject condition, rObject clause, rObject on_leave,
	  scheduler::tags_type tags);
    bool blocked() const;
    bool frozen() const;
    const rObject& condition_get() const;
    const rObject& clause_get() const;
    const rObject& on_leave_get() const;
    bool triggered_get() const;
    void triggered_set(bool);
    const scheduler::tags_type& tags_get() const;
  private:
    rObject condition_;
    rObject clause_;
    rObject on_leave_;
    bool triggered_;
    scheduler::tags_type tags_;
  };

  class AtHandler : public Interpreter
  {
  public:
    AtHandler(const Interpreter&);
    virtual ~AtHandler();
    virtual void work();
    virtual bool frozen() const;
    virtual bool blocked() const;
    void add_job(AtJob*);
  private:
    void complete_tags(const AtJob&);
    void rebuild_tags();
    typedef boost::ptr_list<AtJob> at_jobs_type;
    at_jobs_type jobs_;
    bool yielding;
    scheduler::tags_type tags_;
  };

  // There is only one at job handler. It will be created if it doesn't exist
  // and destroyed when it has no more jobs to handle.
  static AtHandler* at_job_handler;

  AtHandler::AtHandler(const Interpreter& model)
    : Interpreter(model, 0),
      yielding(false)
  {
  }

  AtHandler::~AtHandler()
  {
    jobs_.release();
  }

  void
  AtHandler::work()
  {
    bool check_for_blocked = true;
    bool tags_need_rebuilding = false;

    while (true)
    {
      non_interruptible_set(true);
      yielding = false;

      // If we have to check for blocked jobs, do it at the beginning
      // to make sure we do not miss a "stop" because some condition
      // evaluation mistakenly reenters the scheduler. We know that we
      // have to check for blocked jobs because we have had an indication
      // that tags needed to be rebuilt.

      if (tags_need_rebuilding)
	jobs_.erase_if(boost::bind(&AtJob::blocked, _1));

      for (at_jobs_type::iterator job = jobs_.begin();
	   job != jobs_.end();
	   /* Do not increment as we will also use erase() to advance */)
      {
	// If job has been frozen, we will not consider it for the moment.
	if (job->frozen())
	{
	  ++job;
	  continue;
	}

	// Check the job condition and continue if it has not changed.
	bool new_state;
	try
	{
	  new_state =
	    object::is_true(urbi_call(*this, job->condition_get(), SYMBOL(eval)));
	}
	catch (const kernel::exception& ke)
	{
	  std::cerr << "at condition triggered an exception: " << ke.what()
		    << std::endl;
	  tags_need_rebuilding = true;
	  job = jobs_.erase(job);
	  continue;
	}
	catch (...)
	{
	  std::cerr << "at condition triggered an exception\n";
	  job = jobs_.erase(job);
	  continue;
	}
	if (new_state == job->triggered_get())
	{
	  ++job;
	  continue;
	}

	// There has been a change in the condition, act accordingly depending
	// on whether we have seen a rising or a falling edge and save the
	// condition evaluation result.
	const rObject& to_launch =
	  new_state ? job->clause_get() : job->on_leave_get();
	if (to_launch != object::nil_class)
	{
	  // Temporarily install the needed tags as the current tags.
	  tags_set(job->tags_get());

	  // We do not need to check for an exception here as "detach",
	  // which is the function being called, will not throw and any
	  // exception thrown in the detached runner will not be caught
	  // here anyway.
	  urbi_call(*this, to_launch, SYMBOL(eval));
	}
	job->triggered_set(new_state);
	++job;
      }

      // If we have no more jobs, we can destroy ourselves.
      if (jobs_.empty())
      {
	at_job_handler = 0;
	terminate_now();
      }

      non_interruptible_set(false);
      check_for_blocked = false;
      yielding = true;

      // Rebuild tags if our list of jobs has changed.
      if (tags_need_rebuilding)
	rebuild_tags();
      tags_need_rebuilding = false;

      // Go to sleep
      try
      {
	// We want to appear blocked only when explicitly yielding and
	// catching the exception. If, by mistake, a condition evaluation
	// yields and is blocked, we do not want it to get the bogus
        // exception.
	side_effect_free_set(true);
	yield_until_things_changed();
      }
      catch (const scheduler::BlockedException& e)
      {
	// We have at least one "at" job which needs to be blocked.
	tags_need_rebuilding = true;
      }
      catch (const kernel::exception& e)
      {
	std::cerr << "at job handler exited with exception " << e.what()
		  << std::endl;
	throw;
      }
      catch (...)
      {
{'fraction_non_alphanumeric': 0.10620310683715524, 'fraction_numerical': 0.0006340483990277925, 'mean_word_length': 2.9091284593143327, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4859304', 'n_tokens_mistral': 3075, 'n_tokens_neox': 2956, 'n_words': 974}