/**
* 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.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import org.drools.RuleBaseConfiguration;
import org.drools.RuntimeDroolsException;
import org.drools.common.InternalFactHandle;
import org.drools.common.InternalKnowledgeRuntime;
import org.drools.common.InternalWorkingMemory;
import org.drools.common.NodeMemory;
import org.drools.common.WorkingMemoryAction;
import org.drools.impl.StatefulKnowledgeSessionImpl;
import org.drools.marshalling.impl.MarshallerReaderContext;
import org.drools.marshalling.impl.MarshallerWriteContext;
import org.drools.reteoo.builder.BuildContext;
import org.drools.spi.PropagationContext;
/**
* A node that will add the propagation to the working memory actions queue,
* in order to allow multiple threads to concurrently assert objects to multiple
* entry points.
*
* @author etirelli
*/
public class PropagationQueuingNode extends ObjectSource
implements
ObjectSinkNode,
NodeMemory {
private static final long serialVersionUID = 510l;
// should we make this one configurable?
private static final int PROPAGATION_SLICE_LIMIT = 1000;
private ObjectSinkNode previousObjectSinkNode;
private ObjectSinkNode nextObjectSinkNode;
private PropagateAction action;
public PropagationQueuingNode() {
}
/**
* Construct a <code>PropagationQueuingNode</code> that will queue up
* propagations until it the engine reaches a safe propagation point,
* when all the queued facts are propagated.
*
* @param id Node's ID
* @param objectSource Node's object source
* @param context
*/
public PropagationQueuingNode(final int id,
final ObjectSource objectSource,
final BuildContext context) {
super( id,
context.getPartitionId(),
context.getRuleBase().getConfiguration().isMultithreadEvaluation(),
objectSource,
context.getRuleBase().getConfiguration().getAlphaNodeHashingThreshold() );
this.action = new PropagateAction( this );
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
super.readExternal( in );
previousObjectSinkNode = (ObjectSinkNode) in.readObject();
nextObjectSinkNode = (ObjectSinkNode) in.readObject();
action = (PropagateAction) in.readObject();
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal( out );
out.writeObject( previousObjectSinkNode );
out.writeObject( nextObjectSinkNode );
out.writeObject( action );
}
/**
* @see org.drools.reteoo.ObjectSource#updateSink(org.drools.reteoo.ObjectSink, org.drools.spi.PropagationContext, org.drools.common.InternalWorkingMemory)
*/
public void updateSink(ObjectSink sink,
PropagationContext context,
InternalWorkingMemory workingMemory) {
final PropagationQueueingNodeMemory memory = (PropagationQueueingNodeMemory) workingMemory.getNodeMemory( this );
// this is just sanity code. We may remove it in the future, but keeping it for now.
if ( !memory.isEmpty() ) {
throw new RuntimeDroolsException( "Error updating sink. Not safe to update sink as the PropagatingQueueingNode memory is not empty at node: " + this.toString() );
}
// as this node is simply a queue, ask object source to update the child sink directly
this.source.updateSink( sink,
context,
workingMemory );
}
/**
* @see org.drools.common.BaseNode#attach()
*/
public void attach() {
this.source.addObjectSink( this );
}
/**
* @see org.drools.common.BaseNode#attach(org.drools.common.InternalWorkingMemory[])
*/
public void attach(InternalWorkingMemory[] workingMemories) {
attach();
// this node does not require update, so nothing else to do.
}
/**
* @see org.drools.reteoo.ObjectSinkNode#getNextObjectSinkNode()
*/
public ObjectSinkNode getNextObjectSinkNode() {
return this.nextObjectSinkNode;
}
/**
* @see org.drools.reteoo.ObjectSinkNode#getPreviousObjectSinkNode()
*/
public ObjectSinkNode getPreviousObjectSinkNode() {
return this.previousObjectSinkNode;
}
/**
* @see org.drools.reteoo.ObjectSinkNode#setNextObjectSinkNode(org.drools.reteoo.ObjectSinkNode)
*/
public void setNextObjectSinkNode(ObjectSinkNode next) {
this.nextObjectSinkNode = next;
}
/**
* @see org.drools.reteoo.ObjectSinkNode#setPreviousObjectSinkNode(org.drools.reteoo.ObjectSinkNode)
*/
public void setPreviousObjectSinkNode(ObjectSinkNode previous) {
this.previousObjectSinkNode = previous;
}
public boolean isObjectMemoryEnabled() {
return true;
}
/**
* @see org.drools.reteoo.ObjectSink#assertObject(InternalFactHandle, org.drools.spi.PropagationContext, org.drools.common.InternalWorkingMemory)
*/
public void assertObject(InternalFactHandle factHandle,
PropagationContext context,
InternalWorkingMemory workingMemory) {
final PropagationQueueingNodeMemory memory = (PropagationQueueingNodeMemory) workingMemory.getNodeMemory( this );
memory.addAction( new AssertAction( factHandle,
context ) );
// if not queued yet, we need to queue it up
if ( memory.isQueued().compareAndSet( false,
true ) ) {
workingMemory.queueWorkingMemoryAction( this.action );
}
}
public void retractObject(InternalFactHandle handle,
PropagationContext context,
InternalWorkingMemory workingMemory) {
final PropagationQueueingNodeMemory memory = (PropagationQueueingNodeMemory) workingMemory.getNodeMemory( this );
memory.addAction( new RetractAction( handle,
context ) );
// if not queued yet, we need to queue it up
if ( memory.isQueued().compareAndSet( false,
true ) ) {
workingMemory.queueWorkingMemoryAction( this.action );
}
}
public void modifyObject(InternalFactHandle factHandle,
ModifyPreviousTuples modifyPreviousTuples,
PropagationContext context,
InternalWorkingMemory workingMemory) {
final PropagationQueueingNodeMemory memory = (PropagationQueueingNodeMemory) workingMemory.getNodeMemory( this );
memory.addAction( new ModifyAction( factHandle,
modifyPreviousTuples,
context ) );
// if not queued yet, we need to queue it up
if ( memory.isQueued().compareAndSet( false,
true ) ) {
workingMemory.queueWorkingMemoryAction( this.action );
}
}
/**
* Propagate all queued actions (asserts and retracts).
* <p/>
* This method implementation is based on optimistic behavior to avoid the
* use of locks. There may eventually be a minimum wasted effort, but overall
* it will be better than paying for the lock's cost.
*
* @param workingMemory
*/
public void propagateActions(InternalWorkingMemory workingMemory) {
final PropagationQueueingNodeMemory memory = (PropagationQueueingNodeMemory) workingMemory.getNodeMemory( this );
// first we clear up the action queued flag
memory.isQueued().compareAndSet( true,
false );
// we limit the propagation to avoid a hang when this queue is never empty
Action next = memory.getNext();
for ( int counter = 0; next != null && counter < PROPAGATION_SLICE_LIMIT; next = memory.getNext(), counter++ ) {
next.execute( this.sink,
workingMemory );
}
if ( memory.hasNext() && memory.isQueued().compareAndSet( false,
true ) ) {
// add action to the queue again.
workingMemory.queueWorkingMemoryAction( this.action );
}
}
public void setObjectMemoryEnabled(boolean objectMemoryOn) {
throw new UnsupportedOperationException( "PropagationQueueingNode must have its node memory enabled." );
}
public Object createMemory(RuleBaseConfiguration config) {
return new PropagationQueueingNodeMemory();
}
/**
* Memory implementation for the node
*
* @author etirelli
*/
public static class PropagationQueueingNodeMemory
implements
Externalizable {
private static final long serialVersionUID = 7372028632974484023L;
private ConcurrentLinkedQueue<Action> queue;
// "singleton" action - there is one of this for each node in each working memory
private AtomicBoolean isQueued;
public PropagationQueueingNodeMemory() {
super();
this.queue = new ConcurrentLinkedQueue<Action>();
this.isQueued = new AtomicBoolean( false );
}
@SuppressWarnings("unchecked")
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
queue = (ConcurrentLinkedQueue<Action>) in.readObject();
isQueued = (AtomicBoolean) in.readObject();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject( queue );
out.writeObject( isQueued );
}
public boolean isEmpty() {
return this.queue.isEmpty();
}
public void addAction(Action action) {
this.queue.add( action );
}
public Action getNext() {
return this.queue.poll();
}
public boolean hasNext() {
return this.queue.peek() != null;
}
public AtomicBoolean isQueued() {
return isQueued;
}
public long getSize() {
return this.queue.size();
}
}
private static abstract class Action
implements
Externalizable {
protected InternalFactHandle handle;
protected PropagationContext context;
public Action(InternalFactHandle handle,
PropagationContext context) {
super();
this.handle = handle;
this.context = context;
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
handle = (InternalFactHandle) in.readObject();
context = (PropagationContext) in.readObject();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject( handle );
out.writeObject( context );
}
public abstract void execute(final ObjectSinkPropagator sink,
final InternalWorkingMemory workingMemory);
}
private static class AssertAction extends Action {
private static final long serialVersionUID = -8478488926430845209L;
public AssertAction(final InternalFactHandle handle,
final PropagationContext context) {
super( handle,
context );
}
public void execute(final ObjectSinkPropagator sink,
final InternalWorkingMemory workingMemory) {
sink.propagateAssertObject( this.handle,
this.context,
workingMemory );
}
}
private static class RetractAction extends Action {
private static final long serialVersionUID = -84784886430845209L;
public RetractAction(final InternalFactHandle handle,
final PropagationContext context) {
super( handle,
context );
}
public void execute(final ObjectSinkPropagator sink,
final InternalWorkingMemory workingMemory) {
for ( RightTuple rightTuple = this.handle.getFirstRightTuple(); rightTuple != null; rightTuple = (RightTuple) rightTuple.getHandleNext() ) {
rightTuple.getRightTupleSink().retractRightTuple( rightTuple,
context,
workingMemory );
}
this.handle.setFirstRightTuple( null );
for ( LeftTuple leftTuple = this.handle.getLastLeftTuple(); leftTuple != null; leftTuple = (LeftTuple) leftTuple.getLeftParentNext() ) {
leftTuple.getLeftTupleSink().retractLeftTuple( leftTuple,
context,
workingMemory );
}
this.handle.setFirstLeftTuple( null );
}
}
private static class ModifyAction extends Action {
private static final long serialVersionUID = -8478488926430845209L;
private ModifyPreviousTuples modifyPreviousTuples;
public ModifyAction(final InternalFactHandle handle,
final ModifyPreviousTuples modifyPreviousTuples,
final PropagationContext context) {
super( handle,
context );
this.modifyPreviousTuples = modifyPreviousTuples;
}
public void execute(final ObjectSinkPropagator sink,
final InternalWorkingMemory workingMemory) {
sink.propagateModifyObject( handle,
modifyPreviousTuples,
context,
workingMemory );
}
}
/**
* This is the action that is added to the working memory actions queue, so that
* this node propagation can be triggered at a safe point
*
* @author etirelli
*/
public static class PropagateAction
implements
WorkingMemoryAction {
private static final long serialVersionUID = 6765029029501617115L;
private PropagationQueuingNode node;
public PropagateAction() {
}
public PropagateAction(PropagationQueuingNode node) {
this.node = node;
}
public PropagateAction(MarshallerReaderContext context) throws IOException {
this.node = (PropagationQueuingNode) context.sinks.get( context.readInt() );
}
public void write(MarshallerWriteContext context) throws IOException {
context.writeInt( WorkingMemoryAction.PropagateAction );
context.write( node.getId() );
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
node = (PropagationQueuingNode) in.readObject();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject( node );
}
public void execute(InternalWorkingMemory workingMemory) {
this.node.propagateActions( workingMemory );
}
public void execute(InternalKnowledgeRuntime kruntime) {
execute(((StatefulKnowledgeSessionImpl) kruntime).getInternalWorkingMemory());
}
}
}