Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.dasasian.chok.client.ClientResult Maven / Gradle / Ivy
/**
* Copyright (C) 2014 Dasasian ([email protected] )
*
* 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 com.dasasian.chok.client;
import com.dasasian.chok.util.ChokException;
import com.google.common.collect.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* A multithreaded destination for results and/or errors. Results are produced
* by nodes and we pass lists of shards to nodes. But due to replication and
* retries, we associate sets of shards with the results, not nodes.
*
* Multiple NodeInteractions will be writing to this object at the same time. If
* not closed, expect the contents to change. For example isComplete() might
* return false and then a call to getResults() might return a complete set (in
* which case another call to isComplete() would return true). If you need
* complex state information, rather than making multiple calls, you should use
*
* You can get these results from a WorkQueue by polling or blocking. Once you
* have an ClientResult instance you may poll it or block on it. Whenever
* resutls or errors are added notifyAll() is called. The ClientResult can
* report on the number or ratio of shards completed. You can stop the search by
* calling close(). The ClientResult will no longer change, and any outstanding
* threads will be killed (via notification to the provided IClosedListener).
*/
public class ClientResult implements IResultReceiver, Iterable.Entry> {
private static final Logger LOG = LoggerFactory.getLogger(ClientResult.class);
private final Set entries;
private final Map resultMap;
private final List results;
private final List errors;
private final long startTime;
private final ResultReceiverWrapper resultReceiverWrapper;
/**
* Construct a non-closed ClientResult, which waits for addResults() or
* addNodeError() calls until close() is called. After that point, addResults()
* and addNodeError() calls are ignored, and this object becomes immutable.
*
* @param allShards The set of all shards to expect results from.
*/
public ClientResult(ImmutableSet allShards) {
this.resultReceiverWrapper = new ResultReceiverWrapper<>(allShards, this);
if (LOG.isTraceEnabled()) {
LOG.trace(String.format("Created ClientResult(%s)", allShards));
}
startTime = System.currentTimeMillis();
this.entries = Sets.newConcurrentHashSet();
this.resultMap = Maps.newConcurrentMap();
this.results = Collections.synchronizedList(Lists.newArrayList());
this.errors = Collections.synchronizedList(Lists.newArrayList());
}
/**
* Add a result. Will be ignored if closed.
*
* @param result The result to add.
* @param shards The shards used to compute the result.
*/
@Override
public void addResult(T result, Set shards) {
Entry entry = new Entry(result, shards, false);
if (LOG.isTraceEnabled()) {
LOG.trace(String.format("Adding result %s", entry));
}
entries.add(entry);
if (result != null) {
results.add(result);
resultMap.put(result, entry);
}
}
/**
* Add an error. Will be ignored if closed.
*
* @param error The error to add.
* @param shards The shards used when the error happened.
*/
@Override
public void addError(Throwable error, Set shards) {
Entry entry = new Entry(error, shards, true);
if (LOG.isTraceEnabled()) {
LOG.trace(String.format("Adding error %s", entry));
}
entries.add(entry);
if (error != null) {
errors.add(error);
resultMap.put(error, entry);
}
}
/**
* @return the set of all shards we are expecting results from.
*/
public Set getAllShards() {
return resultReceiverWrapper.getAllShards();
}
public boolean isClosed() {
return resultReceiverWrapper.isClosed();
}
/**
* @return true if we have seen either a result or an error for all shards.
*/
public boolean isComplete() {
return resultReceiverWrapper.isComplete();
}
public double getShardCoverage() {
return resultReceiverWrapper.getShardCoverage();
}
public void addNodeResult(T result, Set shards) {
resultReceiverWrapper.addNodeResult(result, shards);
}
public void addNodeError(Throwable error, Set shards) {
resultReceiverWrapper.addNodeError(error, shards);
}
/**
* @return true if any errors were reported.
*/
public synchronized boolean isError() {
return !errors.isEmpty();
}
/**
* @return all of the results seen so far. Does not include errors.
*/
public synchronized Collection getResults() {
return Collections.unmodifiableCollection(isClosed() ? results : new ArrayList<>(results));
}
// /**
// * Either return results or throw an exception. Allows simple one line use of
// * a ClientResult. If no errors occurred, returns same results as
// * getResults(). If any errors occurred, one is chosen via getError() and
// * thrown.
// *
// * @return if no errors occurred, results via getResults().
// * @throws Throwable if any errors occurred, via getError().
// */
// public synchronized Collection getResultsOrThrowException() throws Throwable {
// if (isError()) {
// throw getError();
// } else {
// return getResults();
// }
// }
//
// /**
// * Either return results or throw a ChokException. Allows simple one line use
// * of a ClientResult. If no errors occurred, returns same results as
// * getResults(). If any errors occurred, one is chosen via getChokException()
// * and thrown.
// *
// * @return if no errors occurred, results via getResults().
// * @throws com.dasasian.chok.util.ChokException if any errors occurred, via getError().
// */
// public synchronized Collection getResultsOrThrowChokException() throws ChokException {
// if (isError()) {
// throw getChokException();
// } else {
// return getResults();
// }
// }
/**
* @return all of the errors seen so far.
*/
public synchronized Collection getErrors() {
return Collections.unmodifiableCollection(isClosed() ? errors : new ArrayList<>(errors));
}
/**
* @return a randomly chosen error, or null if none exist.
*/
public synchronized Throwable getError() {
for (Entry e : entries) {
if (e.error != null) {
return e.error;
}
}
return null;
}
/**
* @return a randomly chosen ChokException if one exists, else a
* ChokException wrapped around a randomly chosen error if one
* exists, else null.
*/
public synchronized ChokException getChokException() {
Throwable error = null;
for (Entry e : this) {
if (e.error != null) {
if (e.error instanceof ChokException) {
return (ChokException) e.error;
} else {
error = e.error;
}
}
}
if (error != null) {
return new ChokException("Error", error);
} else {
return null;
}
}
// /**
// * @param result The result to look up.
// * @return What shards produced the result, and when it arrived. Returns null
// * if result not found.
// */
// public synchronized Entry getResultEntry(T result) {
// return resultMap.get(result);
// }
//
// /**
// * @param error The error to look up.
// * @return What shards produced the error, and when it arrived. Returns null
// * if error not found.
// */
// public synchronized Entry getErrorEntry(Throwable error) {
// return resultMap.get(error);
// }
/**
* @return the time when this ClientResult was created.
*/
public long getStartTime() {
return startTime;
}
/**
* @return a snapshot of all the data about the results so far.
*/
public Set entrySet() {
if (isClosed()) {
return Collections.unmodifiableSet(entries);
} else {
synchronized (this) {
// Set will keep changing, make a snapshot.
return Collections.unmodifiableSet(new HashSet<>(entries));
}
}
}
/**
* @return an iterator of our Entries sees so far.
*/
@Override
public Iterator iterator() {
return entrySet().iterator();
}
/**
* @return a list of our results or errors, in the order they arrived.
*/
public List getArrivalTimes() {
List arrivals;
synchronized (this) {
arrivals = new ArrayList<>(entries);
}
Collections.sort(arrivals, (o1, o2) -> {
if (o1.time != o2.time) {
return o1.time < o2.time ? -1 : 1;
} else {
// Break ties in favor of results.
if (o1.result != null && o2.result == null) {
return -1;
} else if (o2.result != null && o1.result == null) {
return 1;
} else {
return 0;
}
}
});
return arrivals;
}
@Override
public synchronized String toString() {
int numResults = 0;
int numErrors = 0;
for (Entry e : this) {
if (e.result != null) {
numResults++;
}
if (e.error != null) {
numErrors++;
}
}
return String.format("ClientResult: %d results, %d errors, %d/%d shards%s%s", numResults, numErrors, getSeenShards().size(), getAllShards().size(), isClosed() ? " (closed)" : "", isComplete() ? " (complete)" : "");
}
public Set getSeenShards() {
return resultReceiverWrapper.getSeenShards();
}
public Set getMissingShards() {
return resultReceiverWrapper.getMissingShards();
}
@Override
public void close() {
resultReceiverWrapper.close();
}
public ResultReceiverWrapper getResultReceiverWrapper() {
return resultReceiverWrapper;
}
/**
* Immutable storage of either a result or an error, which shards produced it,
* and it's arrival time.
*/
public class Entry {
public final T result;
public final Throwable error;
public final Set shards;
public final long time;
@SuppressWarnings("unchecked")
private Entry(Object o, Collection shards, boolean isError) {
this.result = !isError ? (T) o : null;
this.error = isError ? (Throwable) o : null;
this.shards = Collections.unmodifiableSet(new HashSet<>(shards));
this.time = System.currentTimeMillis();
}
@Override
public String toString() {
String resultStr;
if (result != null) {
try {
resultStr = result.toString();
} catch (Throwable t) {
LOG.trace("Error calling toString() on result", t);
resultStr = "(toString() err)";
}
if (resultStr == null) {
resultStr = "(null toString())";
}
} else {
resultStr = error != null ? error.getClass().getSimpleName() : "null";
}
return String.format("%s from %s at %d", resultStr, shards, time);
}
}
}