/**
* Copyright 2010 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.drools.reteoo;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import org.drools.common.InternalFactHandle;
import org.drools.common.InternalWorkingMemory;
import org.drools.spi.PropagationContext;
/**
* A class to control the tasks for a given rulebase partition.
* It requires a thread pool that is created in the working
* memory and injected in here.
*
* @author <a href="mailto:tirelli@post.com">Edson Tirelli</a>
*/
public class PartitionTaskManager {
// we use a fly weight implementation of the partition tasks to ensure no more
// than one task is executed concurrently for each partition
private PartitionTask task = null;
public PartitionTaskManager(final PartitionManager manager,
final InternalWorkingMemory workingMemory) {
this.task = new PartitionTask( manager,
workingMemory );
}
/**
* Adds the given action to the processing queue
*
* @param action the action to be processed
* @return true if the action was successfully added to the processing queue. false otherwise.
*/
public boolean enqueue(final Action action) {
return this.task.enqueue( action );
}
/**
* A worker task that keeps processing the nodes queue.
* The task uses a non-blocking queue and is re-submitted
* for execution for each element that is added to the queue.
*/
public static class PartitionTask
implements
Runnable,
Comparable<PartitionTask> {
// the priority of this task
private int priority;
// the executor service (thread pool) reference
private PartitionManager manager;
// the shared priority queue with the nodes that need to be processed
private BlockingQueue<Action> queue;
// the working memory reference
private InternalWorkingMemory workingMemory;
// true if this task is already enqueued
private AtomicBoolean enqueued;
// true if YieldAction already added to queue
private AtomicBoolean isYieldAdded;
/**
* Constructor
*
* @param workingMemory the working memory reference that is used for node processing
*/
public PartitionTask(final PartitionManager manager,
final InternalWorkingMemory workingMemory) {
this.queue = new PriorityBlockingQueue<Action>();
this.manager = manager;
this.workingMemory = workingMemory;
this.priority = Action.PRIORITY_NORMAL;
this.enqueued = new AtomicBoolean( false );
this.isYieldAdded = new AtomicBoolean( false );
}
public boolean enqueue(Action action) {
boolean result = queue.add( action );
addToExecutorQueue();
return result;
}
/**
* Default execution method.
*
* @see Runnable
*/
public void run() {
try {
Action action = queue.poll();
if ( action != null ) {
action.execute( workingMemory );
}
enqueued.set( false );
addToExecutorQueue();
} catch ( Exception e ) {
System.err.println( "*******************************************************************************************************" );
System.err.println( "Partition task manager caught an unexpected exception: " + e.getMessage() );
System.err.println( "Drools is capturing the exception to avoid thread death. Please report stack trace to development team." );
e.printStackTrace();
}
}
public void addToExecutorQueue() {
synchronized ( isYieldAdded ) {
if ( this.manager.isOnHold() && (!queue.isEmpty()) && isYieldAdded.compareAndSet( false,
true ) ) {
//System.out.println( "Adding yield " + System.identityHashCode( this ) );
queue.add( YieldAction.INSTANCE );
}
}
if ( !queue.isEmpty() && enqueued.compareAndSet( false,
true ) ) {
Action head = queue.peek();
int priority = Action.PRIORITY_HIGH;
while ( head != null && head instanceof YieldAction ) {
isYieldAdded.compareAndSet( true,
false );
//System.out.println( "Yield consumed " + System.identityHashCode( this ) );
priority = Action.PRIORITY_NORMAL;
queue.remove();
head = queue.peek();
}
if ( head != null ) {
this.setPriority( priority );
manager.execute( this );
} else {
enqueued.compareAndSet( true,
false );
}
}
}
public int getPriority() {
return priority;
}
public boolean isEnqueued() {
return enqueued.get();
}
private void setPriority(int priority) {
this.priority = priority;
}
public int compareTo(PartitionTask o) {
return this.getPriority() - o.getPriority();
}
@Override
public String toString() {
return "PartitionTask( priority=" + priority + " action=" + queue.peek() + " )";
}
}
/**
* An interface for all actions to be executed by the PartitionTask
*/
public static interface Action
extends
Externalizable,
Comparable<Action> {
public static final int PRIORITY_HIGH = 10;
public static final int PRIORITY_NORMAL = 0;
public static final int PRIORITY_LOW = -10;
public int getPriority();
public void execute(final InternalWorkingMemory workingMemory);
}
/**
* An abstract super class for all handle-related actions
*/
public static abstract class FactAction
implements
Action,
Externalizable {
protected InternalFactHandle handle;
protected PropagationContext context;
protected ObjectSink sink;
protected int priority;
public FactAction() {
priority = PRIORITY_NORMAL;
}
public FactAction(final InternalFactHandle handle,
final PropagationContext context,
final ObjectSink sink,
final int priority) {
super();
this.handle = handle;
this.context = context;
this.sink = sink;
this.priority = priority;
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
handle = (InternalFactHandle) in.readObject();
context = (PropagationContext) in.readObject();
sink = (ObjectSink) in.readObject();
priority = in.readInt();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject( handle );
out.writeObject( context );
out.writeObject( sink );
out.writeInt( priority );
}
public int getPriority() {
return priority;
}
public int compareTo(Action o) {
return this.getPriority() - o.getPriority();
}
public abstract void execute(final InternalWorkingMemory workingMemory);
@Override
public String toString() {
return getClass().getSimpleName() + "( part=" + sink.getPartitionId() + " sink=" + sink + " )";
}
}
public static class FactAssertAction extends FactAction {
private static final long serialVersionUID = 510l;
FactAssertAction() {
}
public FactAssertAction(final InternalFactHandle handle,
final PropagationContext context,
final ObjectSink sink,
final int priority) {
super( handle,
context,
sink,
priority );
}
public void execute(final InternalWorkingMemory workingMemory) {
sink.assertObject( this.handle,
this.context,
workingMemory );
}
}
/**
* An abstract super class for all leftTuple-related actions
*/
public static abstract class LeftTupleAction
implements
Action,
Externalizable {
protected LeftTuple leftTuple;
protected PropagationContext context;
protected LeftTupleSink sink;
protected int priority;
public LeftTupleAction() {
priority = PRIORITY_NORMAL;
}
public LeftTupleAction(final LeftTuple leftTuple,
final PropagationContext context,
final LeftTupleSink sink,
final int priority) {
super();
this.leftTuple = leftTuple;
this.context = context;
this.sink = sink;
this.priority = priority;
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
leftTuple = (LeftTuple) in.readObject();
context = (PropagationContext) in.readObject();
sink = (LeftTupleSink) in.readObject();
priority = in.readInt();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject( leftTuple );
out.writeObject( context );
out.writeObject( sink );
out.writeInt( priority );
}
public int getPriority() {
return priority;
}
public int compareTo(Action o) {
return this.getPriority() - o.getPriority();
}
public abstract void execute(final InternalWorkingMemory workingMemory);
}
public static class LeftTupleAssertAction extends LeftTupleAction {
public LeftTupleAssertAction() {
}
public LeftTupleAssertAction(final LeftTuple leftTuple,
final PropagationContext context,
final LeftTupleSink sink,
final int priority) {
super( leftTuple,
context,
sink,
priority );
}
public void execute(InternalWorkingMemory workingMemory) {
this.sink.assertLeftTuple( leftTuple,
context,
workingMemory );
}
}
public static class LeftTupleRetractAction extends LeftTupleAction {
public LeftTupleRetractAction() {
}
public LeftTupleRetractAction(final LeftTuple leftTuple,
final PropagationContext context,
final LeftTupleSink sink,
final int priority) {
super( leftTuple,
context,
sink,
priority );
}
public void execute(InternalWorkingMemory workingMemory) {
this.sink.assertLeftTuple( leftTuple,
context,
workingMemory );
}
}
/**
* A markup action used to mark spots in the queue where
* the next action must be executed at normal priority
*
* @author etirelli
*/
private static class YieldAction
implements
Action {
public static final YieldAction INSTANCE = new YieldAction();
private YieldAction() {
}
public void execute(InternalWorkingMemory workingMemory) {
}
public int getPriority() {
return PRIORITY_NORMAL;
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
}
public void writeExternal(ObjectOutput out) throws IOException {
}
public int compareTo(Action o) {
return this.getPriority() - o.getPriority();
}
}
}