/** * @param threads the number of threads to operate the network with; 0 means single-threaded * operation, 1 starts an asynchronous thread to operate the RETE net, >1 uses multiple RETE * containers. */ public Network(int threads, IPatternMatcherRuntimeContext context) { super(); this.threads = threads; this.context = context; this.inputConnector = new InputConnector(this); this.nodeFactory = new NodeFactory(context); containers = new ArrayList<ReteContainer>(); firstContainer = (threads > 1) ? Options.firstFreeContainer : 0; // NOPMD nextContainer = firstContainer; if (threads > 0) { globalTerminationCriteria = CollectionsFactory.getMap(); // new HashMap<ReteContainer, Long>(); reportedClocks = CollectionsFactory.getMap(); // new HashMap<ReteContainer, Long>(); ReadWriteLock rwl = new ReentrantReadWriteLock(); updateLock = rwl.readLock(); structuralChangeLock = rwl.writeLock(); for (int i = 0; i < threads; ++i) containers.add(new ReteContainer(this, true)); } else containers.add(new ReteContainer(this, false)); headContainer = containers.get(0); }
/** * Stores the internal parts of a rete network. Nodes are stored according to type and parameters. * * @author Gabor Bergmann */ public class NodeProvisioner { // boolean activeStorage = true; ReteContainer reteContainer; NodeFactory nodeFactory; ConnectionFactory connectionFactory; InputConnector inputConnector; // TODO as recipe? Map<Supplier, RemoteReceiver> remoteReceivers = CollectionsFactory.getMap(); // new HashMap<Supplier, RemoteReceiver>(); Map<Address<? extends Supplier>, RemoteSupplier> remoteSuppliers = CollectionsFactory.getMap(); // new HashMap<Address<? extends Supplier>, RemoteSupplier>(); /** * PRE: NodeFactory, ConnectionFactory must exist * * @param reteContainer the ReteNet whose interior is to be mapped. */ public NodeProvisioner(ReteContainer reteContainer) { super(); this.reteContainer = reteContainer; this.nodeFactory = reteContainer.getNodeFactory(); this.connectionFactory = reteContainer.getConnectionFactory(); this.inputConnector = reteContainer.getInputConnectionFactory(); } public synchronized Address<? extends Node> getOrCreateNodeByRecipe(RecipeTraceInfo recipeTrace) { final ReteNodeRecipe recipe = recipeTrace.getRecipe(); Address<? extends Node> result = getNodesByRecipe().get(recipe); if (result != null) { // NODE ALREADY CONSTRUCTED FOR RECIPE, only needs to add trace if (getRecipeTraces().add(recipeTrace)) result.getNodeCache().assignTraceInfo(recipeTrace); } else { // No node for this recipe object - but equivalent recipes still reusable Collection<ReteNodeRecipe> sameClassRecipes = getSameClassRecipes(recipe); for (ReteNodeRecipe knownRecipe : sameClassRecipes) { if (equivalentRecipes(recipe, knownRecipe)) { // FOUND EQUIVALENT RECIPE result = getNodesByRecipe().get(knownRecipe); getNodesByRecipe().put(recipe, result); result.getNodeCache().assignTraceInfo(recipeTrace); break; } } if (result == null) { // MUST INSTANTIATE NEW NODE FOR RECIPE final Node freshNode = instantiateNodeForRecipe(recipeTrace, recipe, sameClassRecipes); result = reteContainer.makeAddress(freshNode); } } return result; } private Set<RecipeTraceInfo> getRecipeTraces() { return reteContainer.network.recipeTraces; } private Node instantiateNodeForRecipe( RecipeTraceInfo recipeTrace, final ReteNodeRecipe recipe, Collection<ReteNodeRecipe> sameClassRecipes) { if (recipe instanceof IndexerRecipe) { // INSTANTIATE AND HOOK UP // (cannot delay hooking up, because parent determines indexer implementation) ensureParents(recipeTrace); final ReteNodeRecipe parentRecipe = recipeTrace.getParentRecipeTraces().iterator().next().getRecipe(); final Indexer result = nodeFactory.createIndexer( reteContainer, (IndexerRecipe) recipe, asSupplier( (Address<? extends Supplier>) reteContainer.network.getExistingNodeByRecipe(parentRecipe)), recipeTrace); // REMEMBER if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) { getNodesByRecipe().put(recipe, reteContainer.makeAddress(result)); sameClassRecipes.add(recipe); } return result; } else { // INSTANTIATE Node result = nodeFactory.createNode(reteContainer, recipe, recipeTrace); // REMEMBER if (Options.nodeSharingOption == Options.NodeSharingOption.ALL) { getNodesByRecipe().put(recipe, reteContainer.makeAddress(result)); sameClassRecipes.add(recipe); } // HOOK UP // (recursion-tolerant due to this delayed order of initialization) ensureParents(recipeTrace); if (recipe instanceof InputRecipe) inputConnector.connectInput((InputRecipe) recipe, result); else connectionFactory.connectToParents(recipeTrace, result); return result; } } private Map<ReteNodeRecipe, Address<? extends Node>> getNodesByRecipe() { return reteContainer.network.nodesByRecipe; } private void ensureParents(RecipeTraceInfo recipeTrace) { for (RecipeTraceInfo parentTrace : recipeTrace.getParentRecipeTraces()) { getOrCreateNodeByRecipe(parentTrace); } } private boolean equivalentRecipes(ReteNodeRecipe recipe, ReteNodeRecipe knownRecipe) { // TODO reuse in more cases later, e.g. switching join node parents, etc. return EcoreUtil.equals(recipe, knownRecipe); } private Collection<ReteNodeRecipe> getSameClassRecipes(final ReteNodeRecipe recipe) { Collection<ReteNodeRecipe> sameClassRecipes = reteContainer.network.primaryRecipesByClass.get(recipe.eClass()); if (sameClassRecipes == null) { sameClassRecipes = CollectionsFactory.getSet(); reteContainer.network.primaryRecipesByClass.put(recipe.eClass(), sameClassRecipes); } return sameClassRecipes; } //// Remoting - TODO eliminate? synchronized RemoteReceiver accessRemoteReceiver(Address<? extends Supplier> address) { throw new UnsupportedOperationException("Multi-container Rete not supported yet"); // if (!reteContainer.isLocal(address)) // return address.getContainer().getProvisioner().accessRemoteReceiver(address); // Supplier localSupplier = reteContainer.resolveLocal(address); // RemoteReceiver result = remoteReceivers.get(localSupplier); // if (result == null) { // result = new RemoteReceiver(reteContainer); // reteContainer.connect(localSupplier, result); // stateless node, no // // synch required // // if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) // remoteReceivers.put(localSupplier, result); // } // return result; } /** @pre: address is NOT local */ synchronized RemoteSupplier accessRemoteSupplier(Address<? extends Supplier> address) { throw new UnsupportedOperationException("Multi-container Rete not supported yet"); // RemoteSupplier result = remoteSuppliers.get(address); // if (result == null) { // result = new RemoteSupplier(reteContainer, address.getContainer().getProvisioner() // .accessRemoteReceiver(address)); // // network.connectAndSynchronize(supplier, result); // // if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) // remoteSuppliers.put(address, result); // } // return result; } /** The powerful method for accessing any (supplier) Address as a local supplier. */ public Supplier asSupplier(Address<? extends Supplier> address) { if (!reteContainer.isLocal(address)) return accessRemoteSupplier(address); else return reteContainer.resolveLocal(address); } // local version // TODO remove? public synchronized ProjectionIndexer accessProjectionIndexer( RecipeTraceInfo supplierTrace, TupleMask mask) { final org.eclipse.incquery.runtime.rete.recipes.ProjectionIndexerRecipe projectionIndexerRecipe = projectionIndexerRecipe(supplierTrace, mask); final UserRequestTrace indexerTrace = new UserRequestTrace(projectionIndexerRecipe, supplierTrace); final Address<? extends Node> address = getOrCreateNodeByRecipe(indexerTrace); return (ProjectionIndexer) reteContainer.resolveLocal(address); } // local version public synchronized ProjectionIndexer accessProjectionIndexerOnetime( RecipeTraceInfo supplierTrace, TupleMask mask) { if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) return accessProjectionIndexer(supplierTrace, mask); final Address<? extends Node> supplierAddress = getOrCreateNodeByRecipe(supplierTrace); Supplier supplier = (Supplier) reteContainer.resolveLocal(supplierAddress); reteContainer.flushUpdates(); OnetimeIndexer result = new OnetimeIndexer(reteContainer, mask); reteContainer.sendConstructionUpdates( result, Direction.INSERT, reteContainer.pullContents(supplier)); reteContainer.flushUpdates(); return result; } // local, read-only version public synchronized ProjectionIndexer peekProjectionIndexer( RecipeTraceInfo supplierTrace, TupleMask mask) { final Address<? extends Node> address = getNodesByRecipe().get(projectionIndexerRecipe(supplierTrace, mask)); return address == null ? null : (ProjectionIndexer) reteContainer.resolveLocal(address); } private org.eclipse.incquery.runtime.rete.recipes.ProjectionIndexerRecipe projectionIndexerRecipe( RecipeTraceInfo parentTrace, TupleMask mask) { return RecipesHelper.projectionIndexerRecipe( parentTrace.getRecipe(), RecipesHelper.mask(mask.sourceWidth, mask.indices)); } // public synchronized Address<? extends Supplier> accessValueBinderFilterNode( // Address<? extends Supplier> supplierAddress, int bindingIndex, Object bindingValue) // { // Supplier supplier = asSupplier(supplierAddress); // Object[] paramsArray = { supplier.getNodeId(), bindingIndex, bindingValue }; // Tuple params = new FlatTuple(paramsArray); // ValueBinderFilterNode result = valueBinderFilters.get(params); // if (result == null) { // result = new ValueBinderFilterNode(reteContainer, bindingIndex, bindingValue); // reteContainer.connect(supplier, result); // stateless node, no synch // // required // // if (Options.nodeSharingOption == Options.NodeSharingOption.ALL) // valueBinderFilters.put(params, result); // } // return reteContainer.makeAddress(result); // } /** * Returns a copy of the given indexer that is an active node by itself (created if does not * exist). (Convention: attached with same mask to a transparent node that is attached to parent * node.) Node is created if it does not exist yet. * * @return an identical but active indexer */ // TODO rethink traceability RecipeTraceInfo accessActiveIndexer(RecipeTraceInfo inactiveIndexerRecipeTrace) { final RecipeTraceInfo parentRecipeTrace = inactiveIndexerRecipeTrace.getParentRecipeTraces().iterator().next(); final org.eclipse.incquery.runtime.rete.recipes.ProjectionIndexerRecipe inactiveIndexerRecipe = (org.eclipse.incquery.runtime.rete.recipes.ProjectionIndexerRecipe) inactiveIndexerRecipeTrace.getRecipe(); final TransparentRecipe transparentRecipe = RecipesFactory.eINSTANCE.createTransparentRecipe(); transparentRecipe.setParent(parentRecipeTrace.getRecipe()); final ActiveNodeConflictTrace transparentRecipeTrace = new ActiveNodeConflictTrace( transparentRecipe, parentRecipeTrace, inactiveIndexerRecipeTrace); final org.eclipse.incquery.runtime.rete.recipes.ProjectionIndexerRecipe activeIndexerRecipe = RecipesFactory.eINSTANCE.createProjectionIndexerRecipe(); activeIndexerRecipe.setParent(transparentRecipe); activeIndexerRecipe.setMask(inactiveIndexerRecipe.getMask()); final ActiveNodeConflictTrace activeIndexerRecipeTrace = new ActiveNodeConflictTrace( activeIndexerRecipe, transparentRecipeTrace, inactiveIndexerRecipeTrace); return activeIndexerRecipeTrace; } // /** // * @param parent // * @return // */ // private TransparentNode accessTransparentNodeInternal(Supplier parent) { // nodeFactory. // return null; // } // public synchronized void registerSpecializedProjectionIndexer(Node node, ProjectionIndexer // indexer) { // if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) { // Object[] paramsArray = { node.getNodeId(), indexer.getMask() }; // Tuple params = new FlatTuple(paramsArray); // projectionIndexers.put(params, indexer); // } // } }
/** @author Gabor Bergmann */ public class Network { int threads; protected ArrayList<ReteContainer> containers; ReteContainer headContainer; private int firstContainer = 0; private int nextContainer = 0; // the following fields exist only if threads > 0 protected Map<ReteContainer, Long> globalTerminationCriteria = null; protected Map<ReteContainer, Long> reportedClocks = null; protected Lock updateLock = null; // grab during normal update operations protected Lock structuralChangeLock = null; // grab if the network structure is to // be changed // Knowledge of the outside world protected IPatternMatcherRuntimeContext context; protected NodeFactory nodeFactory; protected InputConnector inputConnector; // Node and recipe administration // incl. addresses for existing nodes by recipe (where available) // Maintained by NodeProvisioner of each container Map<ReteNodeRecipe, Address<? extends Node>> nodesByRecipe = CollectionsFactory.getMap(); /** if EcoreUtil.equals(recipe1, recipe2), only one of them will be included here */ Map<EClass, Collection<ReteNodeRecipe>> primaryRecipesByClass = CollectionsFactory.getMap(); Set<RecipeTraceInfo> recipeTraces = CollectionsFactory.getSet(); /** @throws IllegalStateException if no node has been constructed for the recipe */ public synchronized Address<? extends Node> getExistingNodeByRecipe(ReteNodeRecipe recipe) { final Address<? extends Node> node = nodesByRecipe.get(recipe); if (node == null) throw new IllegalStateException( String.format("Rete node for recipe %s not constructed yet.", recipe)); return node; } /** @return null if no node has been constructed for the recipe */ public synchronized Address<? extends Node> getNodeByRecipeIfExists(ReteNodeRecipe recipe) { final Address<? extends Node> node = nodesByRecipe.get(recipe); return node; } /** * @param threads the number of threads to operate the network with; 0 means single-threaded * operation, 1 starts an asynchronous thread to operate the RETE net, >1 uses multiple RETE * containers. */ public Network(int threads, IPatternMatcherRuntimeContext context) { super(); this.threads = threads; this.context = context; this.inputConnector = new InputConnector(this); this.nodeFactory = new NodeFactory(context); containers = new ArrayList<ReteContainer>(); firstContainer = (threads > 1) ? Options.firstFreeContainer : 0; // NOPMD nextContainer = firstContainer; if (threads > 0) { globalTerminationCriteria = CollectionsFactory.getMap(); // new HashMap<ReteContainer, Long>(); reportedClocks = CollectionsFactory.getMap(); // new HashMap<ReteContainer, Long>(); ReadWriteLock rwl = new ReentrantReadWriteLock(); updateLock = rwl.readLock(); structuralChangeLock = rwl.writeLock(); for (int i = 0; i < threads; ++i) containers.add(new ReteContainer(this, true)); } else containers.add(new ReteContainer(this, false)); headContainer = containers.get(0); } /** Kills this Network along with all containers and message consumption cycles. */ public void kill() { for (ReteContainer container : containers) { container.kill(); } containers.clear(); } /** * Returns the head container, that is guaranteed to reside in the same JVM as the Network object. * * @return */ public ReteContainer getHeadContainer() { return headContainer; } /** * Returns the next container in round-robin fashion. Configurable not to yield head container. */ public ReteContainer getNextContainer() { if (nextContainer >= containers.size()) nextContainer = firstContainer; return containers.get(nextContainer++); } /** * Internal message delivery method. * * @pre threads > 0 */ private void sendUpdate( Address<? extends Receiver> receiver, Direction direction, Tuple updateElement) { ReteContainer affectedContainer = receiver.getContainer(); synchronized (globalTerminationCriteria) { long newCriterion = affectedContainer.sendUpdateToLocalAddress(receiver, direction, updateElement); terminationCriterion(affectedContainer, newCriterion); } } /** * Internal message delivery method for single-threaded operation * * @pre threads == 0 */ private void sendUpdateSingleThreaded( Address<? extends Receiver> receiver, Direction direction, Tuple updateElement) { ReteContainer affectedContainer = receiver.getContainer(); affectedContainer.sendUpdateToLocalAddressSingleThreaded(receiver, direction, updateElement); } /** * Internal message delivery method. * * @pre threads > 0 */ private void sendUpdates( Address<? extends Receiver> receiver, Direction direction, Collection<Tuple> updateElements) { if (updateElements.isEmpty()) return; ReteContainer affectedContainer = receiver.getContainer(); synchronized (globalTerminationCriteria) { long newCriterion = affectedContainer.sendUpdatesToLocalAddress(receiver, direction, updateElements); terminationCriterion(affectedContainer, newCriterion); } } /** * Sends an update message to the receiver node, indicating a newly found or lost partial * matching. The node may reside in any of the containers associated with this network. To be * called from a user thread during normal operation, NOT during construction. * * @return the value of the target container's clock at the time when the message was accepted * into its message queue */ public void sendExternalUpdate( Address<? extends Receiver> receiver, Direction direction, Tuple updateElement) { if (threads > 0) { try { updateLock.lock(); sendUpdate(receiver, direction, updateElement); } finally { updateLock.unlock(); } } else { sendUpdateSingleThreaded(receiver, direction, updateElement); // getHeadContainer(). } } /** * Sends an update message to the receiver node, indicating a newly found or lost partial * matching. The node may reside in any of the containers associated with this network. To be * called from a user thread during construction. * * @pre: structuralChangeLock MUST be grabbed by the sequence (but not necessarily this thread, as * the sequence may span through network calls, that's why it's not enforced here ) * @return the value of the target container's clock at the time when the message was accepted * into its message queue */ public void sendConstructionUpdate( Address<? extends Receiver> receiver, Direction direction, Tuple updateElement) { // structuralChangeLock.lock(); if (threads > 0) sendUpdate(receiver, direction, updateElement); else receiver .getContainer() .sendUpdateToLocalAddressSingleThreaded(receiver, direction, updateElement); // structuralChangeLock.unlock(); } /** * Sends multiple update messages atomically to the receiver node, indicating a newly found or * lost partial matching. The node may reside in any of the containers associated with this * network. To be called from a user thread during construction. * * @pre: structuralChangeLock MUST be grabbed by the sequence (but not necessarily this thread, as * the sequence may span through network calls, that's why it's not enforced here ) * @return the value of the target container's clock at the time when the message was accepted * into its message queue */ public void sendConstructionUpdates( Address<? extends Receiver> receiver, Direction direction, Collection<Tuple> updateElements) { // structuralChangeLock.lock(); if (threads > 0) sendUpdates(receiver, direction, updateElements); else receiver .getContainer() .sendUpdatesToLocalAddressSingleThreaded(receiver, direction, updateElements); // structuralChangeLock.unlock(); } /** * Establishes connection between a supplier and a receiver node, regardless which container they * are in. Not to be called remotely, because this method enforces the structural lock. * * @param supplier * @param receiver * @param synchronise indicates whether the receiver should be synchronised to the current * contents of the supplier */ public void connectRemoteNodes( Address<? extends Supplier> supplier, Address<? extends Receiver> receiver, boolean synchronise) { try { if (threads > 0) structuralChangeLock.lock(); receiver.getContainer().connectRemoteNodes(supplier, receiver, synchronise); } finally { if (threads > 0) structuralChangeLock.unlock(); } } /** * Severs connection between a supplier and a receiver node, regardless which container they are * in. Not to be called remotely, because this method enforces the structural lock. * * @param supplier * @param receiver * @param desynchronise indicates whether the current contents of the supplier should be * subtracted from the receiver */ public void disconnectRemoteNodes( Address<? extends Supplier> supplier, Address<? extends Receiver> receiver, boolean desynchronise) { try { if (threads > 0) structuralChangeLock.lock(); receiver.getContainer().disconnectRemoteNodes(supplier, receiver, desynchronise); } finally { if (threads > 0) structuralChangeLock.unlock(); } } /** * Containers use this method to report whenever they run out of messages in their queue. * * <p>To be called from the thread of the reporting container. * * @pre threads > 0. * @param reportingContainer the container reporting the emptiness of its message queue. * @param clock the value of the container's clock when reporting. * @param localTerminationCriteria the latest clock values this container has received from other * containers since the last time it reported termination. */ void reportLocalUpdateTermination( ReteContainer reportingContainer, long clock, Map<ReteContainer, Long> localTerminationCriteria) { synchronized (globalTerminationCriteria) { for (Entry<ReteContainer, Long> criterion : localTerminationCriteria.entrySet()) { terminationCriterion(criterion.getKey(), criterion.getValue()); } reportedClocks.put(reportingContainer, clock); Long criterion = globalTerminationCriteria.get(reportingContainer); if (criterion != null && criterion < clock) globalTerminationCriteria.remove(reportingContainer); if (globalTerminationCriteria.isEmpty()) globalTerminationCriteria.notifyAll(); } } /** @pre threads > 0 */ private void terminationCriterion(ReteContainer affectedContainer, long newCriterion) { synchronized (globalTerminationCriteria) { Long oldCriterion = globalTerminationCriteria.get(affectedContainer); Long oldClock = reportedClocks.get(affectedContainer); long relevantClock = oldClock == null ? 0 : oldClock; if ((relevantClock <= newCriterion) && (oldCriterion == null || oldCriterion < newCriterion)) { globalTerminationCriteria.put(affectedContainer, newCriterion); } } } /** * Waits until all rete update operations are settled in all containers. Returns immediately, if * no updates are pending. * * <p>To be called from any user thread. */ public void waitForReteTermination() { if (threads > 0) { synchronized (globalTerminationCriteria) { while (!globalTerminationCriteria.isEmpty()) { try { globalTerminationCriteria.wait(); } catch (InterruptedException e) { } } } } else headContainer.messageConsumptionSingleThreaded(); } /** * Waits to execute action until all rete update operations are settled in all containers. Runs * action and returns immediately, if no updates are pending. The given action is guaranteed to be * run when the terminated state still persists. * * @param action the action to be run when reaching the steady-state. * <p>To be called from any user thread. */ public void waitForReteTermination(Runnable action) { if (threads > 0) { synchronized (globalTerminationCriteria) { while (!globalTerminationCriteria.isEmpty()) { try { globalTerminationCriteria.wait(); } catch (InterruptedException e) { } } action.run(); } } else { headContainer.messageConsumptionSingleThreaded(); action.run(); } } /** @return the structuralChangeLock */ public Lock getStructuralChangeLock() { return structuralChangeLock; } public IPatternMatcherRuntimeContext getContext() { return context; } public NodeFactory getNodeFactory() { return nodeFactory; } /** @return */ public InputConnector getInputConnector() { return inputConnector; } }