All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cacheonix.impl.cache.distributed.partitioned.AggregatingAnnouncement Maven / Gradle / Ivy

Go to download

Cacheonix is an open source distributed cache for Java that allows its users to scale Java applications in a cluster while preserving the simplicity of design and coding in a single Java VM.

The newest version!
/*
 * Cacheonix Systems licenses this file to You under the LGPL 2.1
 * (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.cacheonix.org/products/cacheonix/license-lgpl-2.1.htm
 *
 * 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.cacheonix.impl.cache.distributed.partitioned;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import org.cacheonix.impl.net.cluster.ClusterProcessor;
import org.cacheonix.impl.net.cluster.ReplicatedStateProcessorKey;
import org.cacheonix.impl.net.processor.InvalidMessageException;
import org.cacheonix.impl.net.processor.PrepareResult;
import org.cacheonix.impl.net.processor.Prepareable;
import org.cacheonix.impl.net.processor.ProcessorKey;
import org.cacheonix.impl.net.processor.Request;
import org.cacheonix.impl.net.processor.Response;
import org.cacheonix.impl.net.serializer.SerializerUtils;
import org.cacheonix.impl.net.serializer.Wireable;
import org.cacheonix.impl.util.Assert;
import org.cacheonix.impl.util.array.HashSet;
import org.cacheonix.impl.util.logging.Logger;

/**
 * An abstract implementation of scatter-gather reliable multicast pattern.
 * 

* AggregatingAnnouncement is a foundation for a category of announcements that require processing * replicated data while delaying response to the sender of the announcement until a CacheProcessor that owns the data * responds. *

* AggregatingAnnouncement encapsulates common actions and delegates the implementation of actions specific * to a particular request. In this regard AggregatingAnnouncement is an implementation of the template * method pattern. * * @author Slava Imeshev */ @SuppressWarnings({"RedundantIfStatement", "WeakerAccess"}) public abstract class AggregatingAnnouncement extends Request implements Prepareable { /** * Logger. * * @noinspection UNUSED_SYMBOL, UnusedDeclaration */ private static final Logger LOG = Logger.getLogger(AggregatingAnnouncement.class); // NOPMD /** * True if the request was prepared. */ private boolean prepared = false; /** * Storage number to that the request is addressed. The storage number can be null which means that this is a root * request. It can be zero, which means that this is a request to a primary bucket owner. It can be between one and * the number of replicas which means that this is a request to a replica owner. */ private Integer storageNumber = null; private String cacheName = null; /** * Required to support Wireable. */ protected AggregatingAnnouncement() { } /** * Creates an AggregatingAnnouncement using given wireable type and cache name. * * @param wireableType unique wireable type. The wireable type should have {@link Wireable#DESTINATION_REPLICATED_STATE}. * @param cacheName cache name */ protected AggregatingAnnouncement(final int wireableType, final String cacheName) { super(wireableType); setResponseRequired(true); this.cacheName = cacheName; } /** * {@inheritDoc} */ protected final ProcessorKey getProcessorKey() { return ReplicatedStateProcessorKey.getInstance(); } /** * {@inheritDoc} */ public final boolean isPrepared() { return prepared; } /** * {@inheritDoc} */ public final void markPrepared() { prepared = true; } /** * {@inheritDoc} */ public void validate() throws InvalidMessageException { super.validate(); // Assert that receivers are empty if (isReceiverSet()) { throw new InvalidMessageException("Announcement cannot have a destination"); } } protected final String getCacheName() { return cacheName; } /** * Sets a storage number. * * @param storageNumber the storage number to set. The storage number can be null which means that this is a root * request. It can be zero, which means that this is a requires to a primary bucket owner. It * can be between one and the number of replicas which means that this is a request to a replica * owner. * @see #getStorageNumber */ public final void setStorageNumber(final int storageNumber) { this.storageNumber = storageNumber; } /** * Returns the storage number. The storage number can be null which means that this is a root request. It can be * zero, which means that this is a requires to a primary bucket owner. It can be between one and the number of * replicas which means that this is a request to a replica owner. * * @return the storage number. The storage number can be null which means that this is a root request. It can be * zero, which means that this is a requires to a primary bucket owner. It can be between one and the number * of replicas which means that this is a request to a replica owner. */ public final Integer getStorageNumber() { return storageNumber; } /** * Returns true if this request is a root request. * * @return true if this request is a root request. A root request is the one that collects results on * behalf of the client thread. */ protected final boolean isRootRequest() { return storageNumber == null; } /** * {@inheritDoc} *

* If this is a root request, it posts sub-requests and return false indicating that this request should * not be added to the execution queue. Otherwise returns false. */ public PrepareResult prepare() { if (isRootRequest()) { // This is a root, submit subrequests instead of executing final Collection subAnnouncements = split(0); // NOTE: [email protected] - 2010-01-12 - If there are no subrequests, // there is nothing to wait for. This may happen if no data was provided. // Finish now. The aggregate() method must be prepared to deal with an // empty result. For the majority it seems that an empty success means // "no action was performed". See CACHEONIX-254 for more information. if (subAnnouncements.isEmpty()) { getWaiter().finish(); } postSubrequests(subAnnouncements); return PrepareResult.BREAK; } else { // Not a root request, execute in run() return PrepareResult.ROUTE; } } /** * Posts a collection of sub-requests. *

* Before being posted each sub-request is assigned an owner waiter and the owner response. The owner waiter is set * to this request's waiter. *

* Sub-request's waiter is registered in this request waiter's list of partial waiters. Sub-requests use it to detect * completion - the list is empty. Once the completion is detected, the owner * * @param subRequests the Collection of sub-requests. */ protected void postSubrequests(final Collection subRequests) { // Minor optimization if (subRequests == null || subRequests.isEmpty()) { return; } for (final AggregatingAnnouncement subRequest : subRequests) { // Register and pass owner response final Waiter ownerWaiter = (Waiter) this.getWaiter(); final Waiter subRequestWaiter = (Waiter) subRequest.getWaiter(); subRequestWaiter.setOwnerWaiter(ownerWaiter); ownerWaiter.getPartialWaiters().add(subRequestWaiter); // Post getProcessor().post(subRequest); } } /** * Returns true if the list of partial waiters is empty. Otherwise returns false. * * @return true if the list of partial waiters is empty. Otherwise returns false. */ protected final boolean isWaitingForSubrequests() { return !((Waiter) getWaiter()).isPartialWaitersEmpty(); } /** * Splits data carried by an implementation of AggregatingRequest into a collection of requests * according to the ownership of the data at the given storage number. * * @param storageNumber storage number for that to check for data ownership. * @return a Collection of requests, each carrying parts of data per owner. * @see #prepare() */ protected abstract Collection split(final int storageNumber); /** * Aggregates responses from subrequests that were sent by a parent request. This template is called when a * parent request finishes. *

*

* Root requests (requests from the root to primary buckets owners) must transform partial responses from primary * bucket owners to a results usable by the client thread. *

*

* Requests from primary bucket owners to replicas if such are required (all write requests do update replicas) at * least must collect errors if any. * * @param partialResponses list of responses from subrequests. * @return a resulting object that can be understood by the caller or an exception object. This is the object * returned by Waiter.waitForResult(). * @see Waiter#notifyFinished() */ protected abstract Object aggregate(final List partialResponses); /** * Clears unprocessed data that this request still holds. */ abstract void clear(); public final Response createResponse(final int resultCode) { final AggregatingAnnouncementResponse response = new AggregatingAnnouncementResponse(); response.setResponseToClass(getClass()); response.setResponseToUUID(getUuid()); response.setResultCode(resultCode); response.setReceiver(getSender()); return response; } protected final ClusterProcessor getClusterProcessor() { return (ClusterProcessor) getProcessor(); } /** * {@inheritDoc} */ public void readWire(final DataInputStream in) throws IOException, ClassNotFoundException { super.readWire(in); storageNumber = SerializerUtils.readInteger(in); cacheName = SerializerUtils.readString(in); prepared = in.readBoolean(); } /** * {@inheritDoc} */ public void writeWire(final DataOutputStream out) throws IOException { super.writeWire(out); SerializerUtils.writeInteger(out, storageNumber); SerializerUtils.writeString(cacheName, out); out.writeBoolean(prepared); } public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } if (!super.equals(o)) { return false; } final AggregatingAnnouncement that = (AggregatingAnnouncement) o; if (cacheName != null ? !cacheName.equals(that.cacheName) : that.cacheName != null) { return false; } if (storageNumber != null ? !storageNumber.equals(that.storageNumber) : that.storageNumber != null) { return false; } return true; } public int hashCode() { int result = super.hashCode(); result = 31 * result + (storageNumber != null ? storageNumber.hashCode() : 0); result = 31 * result + (cacheName != null ? cacheName.hashCode() : 0); return result; } public String toString() { return "AggregatingAnnouncement{" + "cacheName='" + cacheName + '\'' + ", storageNumber=" + storageNumber + "} " + super.toString(); } // ================================================================================================================== // // Waiter // // ================================================================================================================== /** * {@inheritDoc} *

* In addition to the usual waiting for a response, AggregatingRequest's waiter holds objects and * provides methods that support scattering sub-requests and gathering responses. */ @SuppressWarnings({"ClassNameSameAsAncestorName", "CanBeFinal", "ReturnOfCollectionOrArrayField"}) abstract static class Waiter extends org.cacheonix.impl.net.processor.Waiter { /** * Waiter of the owner of this request. Makes sense only for a sub-request. ownerWaiter be null if * this is a root request. ownerWaiter is always null if the request is a stub. */ private final AtomicReference ownerWaiter = new AtomicReference(null); /** * Collector for partial results. Holds responses from sub-requests. Makes sense only for a parent request. */ private List partialResponses = null; /** * Partial wait list. Tracks completion of initial subrequests and their self-resends. Makes sense only for a * parent request. */ private Set partialWaiters = null; /** * Creates waiter. * * @param request request this owner belongs to. */ Waiter(final Request request) { super(request); } /** * Sets owner of this waiter. * * @param ownerWaiter the owner to set. Makes sense only for a sub-request. */ public final void setOwnerWaiter(final Waiter ownerWaiter) { this.ownerWaiter.set(ownerWaiter); } /** * Returns a waiter of the owner of this request. ownerWaiter be null if this is a root request. * ownerWaiter is always null if the request is a stub. * * @return the waiter of the owner of this request. ownerWaiter be null if this is a root request. * ownerWaiter is always null if the request is a stub. */ public final Waiter getOwnerWaiter() { return ownerWaiter.get(); } /** * Returns a list of partial responses. If the list is not set, initializes it to an empty list before returning. * * @return the list of partial responses. */ protected final List getPartialResponses() { if (partialResponses == null) { partialResponses = new LinkedList(); } return partialResponses; } /** * Returns a list of partial waiters. If the list is not set, initializes it to an empty list before returning. *

* The list of partial waiters is used by subrequests to decide if the owner is done. * * @return the list of partial waiters. */ protected final Set getPartialWaiters() { final boolean ownerNull = ownerWaiter.get() == null || ((AggregatingAnnouncement) ownerWaiter.get().getRequest()).isRootRequest(); Assert.assertTrue(ownerNull, "This method can be called only if owner is null: {0}", ownerWaiter.get()); if (partialWaiters == null) { partialWaiters = new HashSet(1); } return partialWaiters; } /** * Returns true if partial waiter list is empty. * * @return true if partial waiter list is empty. */ protected boolean isPartialWaitersEmpty() { return partialWaiters == null || partialWaiters.isEmpty(); } /** * Called when: *

* 1. This is a root request and all subrequests has finished. The partial responses are aggregated and the result * is set. Call to super unblocks the client thread. The partial responses can only contain results. Rejected * buckets should be empty. *

* 2. This is a subrequest. Re-splits and re-sends incomplete requests or finishes if nothing to re-send */ protected synchronized void notifyFinished() { final AggregatingAnnouncement request = (AggregatingAnnouncement) getRequest(); // If owner request is null, this means that this is a // waiter for a root request. // // If owner request a root request, this means that *this* // is a waiter for a request to a primary owner. // // If owner request is a primary request, this means // that this is a waiter for a request to a replica owner. if (request.isRootRequest()) { // Root is done and should notify the client thread. //noinspection ControlFlowStatementWithoutBraces if (LOG.isDebugEnabled()) LOG.debug("Root is done and should notify the client thread"); // NOPMD Assert.assertTrue(getOwnerWaiter() == null, "Owner should be null", getOwnerWaiter()); // Aggregate final List partialResponses = getPartialResponses(); // Aggregate partial responses final Object aggregatedResult = request.aggregate(partialResponses); // Set result setResult(aggregatedResult); } else { // Re-posting is done by an owner request. // // Note: a root request doesn't have an owner. It's just // a functor that submits requests to primary owners. // Re-post if there were buckets left. After executing this block // if the list of split request is empty, no subrequests will be // posted and getOwnerWaiter().isPartialWaitersEmpty() will be true. final Collection requests = request.split(request.getStorageNumber()); final AggregatingAnnouncement ownerRequest = (AggregatingAnnouncement) getOwnerWaiter().getRequest(); ownerRequest.postSubrequests(requests); // This is a sub request - remove self from the wait list final boolean existed = getOwnerWaiter().getPartialWaiters().remove(this); Assert.assertTrue(existed, "Waiter should have been registered, but it wasn't: {0}", this); // Check if owner waiter is still waiting for responses from subrequests if (getOwnerWaiter().isPartialWaitersEmpty()) { // All sub-requests has finished. //noinspection ControlFlowStatementWithoutBraces if (LOG.isDebugEnabled()) LOG.debug("All sub-requests has finished"); // NOPMD if (ownerRequest.isRootRequest()) { // Owner request is a root. This means that this is a waiter // for a primary request. As root request doesn't receive // an actual response, this last finished primary waiter must // finish the root request explicitly. When finishing, the root // request will aggregate the partial responses from primary owners // and set the result that will be used by a client thread. getOwnerWaiter().finish(); } } } super.notifyFinished(); } public String toString() { return "Waiter{" + "ownerWaiter=" + ownerWaiter + ", partialResults.size()=" + (partialResponses == null ? "null" : partialResponses.size()) + ", partialWaiters.size()=" + (partialWaiters == null ? "null" : partialWaiters.size()) + "} " + super.toString(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy