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();
}
}
}