StopAfterTimer.java

/*
** Module   : StopAfterTimer.java
** Abstract : Class responsible for stopping of a Progress block after timeout
**            expiration.
**
** Copyright (c) 2008-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
** 001 ES  20240513          Created initial version.
** 002 ES  20240711          Reseting previousTaskBlock in a delete finalizable method.
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.util;

import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;

import com.goldencode.p2j.net.Control;
import com.goldencode.p2j.net.Session;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.security.SecurityManager;
import com.goldencode.p2j.ui.LogicalTerminal;
import com.goldencode.p2j.util.logging.CentralLogger;

/**
 * Provides runtime support for stop-after statement for blocks
 * doBlock, forBlock, repeatBlock.
 */
public class StopAfterTimer 
{
    /** Logger */
    private static final CentralLogger LOG = CentralLogger.get(StopAfterTimer.class.getName());
   
    /** Stores the interruption task for each block. */
    private Map<BlockDefinition, Task> tasks = new HashMap<>();
    
    /** Session for the current execution context */
    private Control control;
    
    /** Timer responsible of execution a interruption tasks. */
    private Timer timer;
    
    /** Previous task created for the last block */
    private Task previousTaskBlock;
    
    /** Blocked stopped by stop-after statement */
    private BlockDefinition stopedBlock;
    
    /** StopCondition was raised by a stop-after timeout */
    private boolean stopConditionRaised;
    
    /** Name of the thread for which the timer is set */
    private String contextName;
    
    /** Semaphore used for avoid race condition between two consecutive interrupts */
    private Semaphore semaphore = new Semaphore(1);
    
    /** Lock used for locking cleanup methods */
    private final ReentrantLock lock = new ReentrantLock();
    
    /**
     * Creates a managing <code>StopAfterTimer</code> object for a given
     * session. 
     *
     * @param    session
     *           The session of the current execution context.
     */
    public StopAfterTimer(String contextName, Control threadControl)
    {
       this.control = threadControl;
       this.contextName = contextName;
    }
    
    /**
     * Checks if the block that was given as an parameter was stopped
     * by a <code>StopCondition</code> raised by a stop-after timeout. 
     *
     * @param    blockDefinition
     *           Progress block for which we check stop-after timeout.
     * 
     * @return   true, if <code>blockDefinition</code> was stopped by an
     *           stop-after timeout, or false otherwise.
     */
    public boolean isStoppedBlock(BlockDefinition blockDefinition)
    {
       return blockDefinition == stopedBlock || tasks.get(blockDefinition) != null;
    }
    
    /**
     * Checks if the a <code>StopCondition</code> was raised by stop-after timeout.
     */
    public boolean isStopAfterConditionRaised()
    {
       return stopConditionRaised;
    }
    
    /**
     * Checks if the block that was given as an parameter is already 
     * present in the timer task
     *
     * @param    blockDefinition
     *           Progress block.
     * 
     * @return   true, if <code>blockDefinition</code> is already present in
     *           timer task queue, or false otherwise.
     */
    public boolean isBlockRegistered(BlockDefinition blockDefinition)
    {
       return tasks.get(blockDefinition) != null;
    }
    
    /**
     * Release of the acquired semaphore and resets <code>stopConditionRaised</code>.
     */
    public void cleanUp()
    {
        stopConditionRaised = false;
        semaphore.release();
    }
    
    /**
     * Adds a new timer task to the <code>timer</code> queue.
     *
     * @param    blockDefinition
     *           Block for which the timeout wants to be set.
     * @param    timeout
     *           Number of seconds to be set as a timeout for
     *           <code>blockDefinition</code>.
     * 
     * @returns  Timer task created for Progress task send as parameter.
     * 
     * @throws   TimeOutException if timeout is equals to zero.
     */
    public Task addTimeOut(BlockDefinition blockDefinition, long timeout)
    {
       if (timer == null)
       {
          timer = new Timer("Timer-" + contextName, true);
       }
       
       Task newTask = new Task(blockDefinition, timeout);
       tasks.put(blockDefinition, newTask);
       newTask.prevTask = previousTaskBlock;
     
       if (previousTaskBlock != null)
       {   
          previousTaskBlock.nextTask = newTask;
       }
       previousTaskBlock = newTask;
    
       timer.schedule(newTask, timeout * 1000);
       
       return newTask;
    }
    
    /**
     * Task class responsible for interruption of the Progress block.
     */
    class Task extends TimerTask implements Finalizable
    {
       /** Block definition related to timeout task */
       private BlockDefinition blockDefinition;
       
       /** Task related to the the previous stop-after block on the stack. */
       private Task prevTask;
       
       /** Task related to the the next stop-after block on the stack. */
       private Task nextTask;
       
       /** Number of seconds for block timeout */
       private long timeout;
  
       /**
        * Constructor.
        * 
        * @param blockDefinition
        *        Progress block for which timeout is set.
        *      
        * @param timeout
        *        Number of seconds which will be set as a timeout.
        *                
        */
       public Task(BlockDefinition blockDefinition, long timeout)
       {
          this.timeout = timeout;
          this.blockDefinition = blockDefinition;
       }
       
       /**
        * Copy constructor.
        * 
        * @param existingTask
        *        Canceled task from which a new timer task will be created.
        */
       public Task(Task existingTask)
       {
          this.blockDefinition = existingTask.blockDefinition;
          this.prevTask = existingTask.prevTask;
          this.nextTask = existingTask.nextTask;
          this.timeout = existingTask.timeout;
       }
       
       /**
        * Method responsible for stopping the thread corresponding 
        * to the <code>session</code> and subsequent cleanup of the 
        * children task's. 
        *
        */
        @Override
       public void run() 
       {
          try 
          {
             semaphore.acquire();
          } 
          catch (InterruptedException e) 
          {
             LOG.log(Level.WARNING, 
                     "Intrerrupt exception while tring to aquire sempahore in StopAfter timer");
          }
          lock.lock();
          Task iter = nextTask;
  
          while (iter != null)
          {
             iter.cancel();
             tasks.remove(iter.blockDefinition);
 
             iter = iter.nextTask;
          }
 
          timer.purge();
          if (prevTask != null)
          { 
             prevTask.nextTask = null;
          }
 
          stopedBlock = blockDefinition;
          stopConditionRaised = true;
          
          try
          {
             SecurityManager.stopAfterInterrupt(control); 
          }          
          catch (RestrictedUseException e)
          {
             LOG.log(Level.WARNING, "Restriced use of Control.setControl()", e);
          }
          
          lock.unlock();
       }

       /**
        * Called by the transaction manager just before a block closes.
        * This is our hook to update the state of semaphore and timer task related 
        * to current block.
        */
       @Override
       public void finished() 
       {
          lock.lock();
          
          this.cancel();
          timer.purge();
          tasks.remove(blockDefinition);
          lock.unlock();
       }

       /**
        * Provides a notification that the external program scope in which the object is registered is
        * is being deleted and the object's reference will be lost after this method is called.
        */
       @Override
       public void deleted() 
       {
          lock.lock();
          if (tasks.size() == 0)
          {
             previousTaskBlock = null;
             timer.cancel();
             timer = null;
          }
          
          lock.unlock();
       }

       /**
        * Called by the transaction manager after each iteration of a repeating
        * block. Cancel the timer task for the previous iteration. Relinquishes the semaphore 
        * and starts a new timer task for next iteration.
        */
       @Override
       public void iterate() 
       {
          lock.lock();
         
          this.cancel();
          timer.purge();
          Task task = new Task(this);
          tasks.put(blockDefinition, task);
          timer.schedule(task, timeout * 1000);
          lock.unlock();
       }

       /**
        * Called by the transaction manager before a block is retried after an
        * error. Cancel the timer task for the previous iteration. Relinquishes the semaphore 
        * and starts a new timer task for next iteration.
        */
       @Override
       public void retry() 
       {
          lock.lock();
          
          this.cancel();
          timer.purge();
          Task task = new Task(this);
          tasks.put(blockDefinition, task);
          timer.schedule(task, timeout * 1000);
          lock.unlock();
       }
    }
}