zipkin2.internal.AggregateCall Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 zipkin2.internal;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import zipkin2.Call;
import zipkin2.Callback;
/**
* A call that blocks on others to complete before invoking a callback or returning from {@link
* #execute()}. The first error will be returned upstream, later ones will be suppressed.
*
* @param the type of returned from {@link Call#execute()}
* @param the type representing the aggregate success value
*/
public abstract class AggregateCall extends Call.Base {
public static Call newVoidCall(List> calls) {
if (calls.isEmpty()) throw new IllegalArgumentException("calls were empty");
if (calls.size() == 1) return calls.get(0);
return new AggregateVoidCall(calls);
}
static final class AggregateVoidCall extends AggregateCall {
AggregateVoidCall(List> calls) {
super(calls);
}
volatile boolean empty = true;
@Override protected Void newOutput() {
return null;
}
@Override protected void append(Void input, Void output) {
empty = false;
}
@Override protected boolean isEmpty(Void output) {
return empty;
}
@Override public AggregateVoidCall clone() {
return new AggregateVoidCall(cloneCalls());
}
}
final Logger log = Logger.getLogger(getClass().getName());
final List extends Call> calls;
protected AggregateCall(List extends Call> calls) {
assert !calls.isEmpty() : "do not create empty aggregate calls";
assert calls.size() > 1 : "do not create single-element aggregates";
this.calls = calls;
}
protected abstract O newOutput();
protected abstract void append(I input, O output);
protected abstract boolean isEmpty(O output);
/** Customizes the aggregated result. For example, summarizing or making immutable. */
protected O finish(O output) {
return output;
}
@Override protected O doExecute() throws IOException {
int length = calls.size();
Throwable firstError = null;
O result = newOutput();
for (int i = 0; i < length; i++) {
Call call = calls.get(i);
try {
append(call.execute(), result);
} catch (IOException | RuntimeException | Error e) {
if (firstError == null) {
firstError = e;
} else if (log.isLoggable(Level.INFO)) {
log.log(Level.INFO, "error from " + call, e);
}
}
}
if (firstError == null) return finish(result);
if (firstError instanceof Error) throw (Error) firstError;
if (firstError instanceof RuntimeException) throw (RuntimeException) firstError;
throw (IOException) firstError;
}
@Override protected void doEnqueue(Callback callback) {
int length = calls.size();
AtomicInteger remaining = new AtomicInteger(length);
AtomicReference firstError = new AtomicReference();
O result = newOutput();
for (int i = 0; i < length; i++) {
Call call = calls.get(i);
call.enqueue(new CountdownCallback(call, remaining, firstError, result, callback));
}
}
@Override protected void doCancel() {
for (int i = 0, length = calls.size(); i < length; i++) {
calls.get(i).cancel();
}
}
class CountdownCallback implements Callback {
final Call call;
final AtomicInteger remaining;
final AtomicReference firstError;
@Nullable final O result;
final Callback callback;
CountdownCallback(Call call, AtomicInteger remaining, AtomicReference firstError,
O result,
Callback callback) {
this.call = call;
this.remaining = remaining;
this.firstError = firstError;
this.result = result;
this.callback = callback;
}
@Override public void onSuccess(I value) {
synchronized (callback) {
append(value, result);
if (remaining.decrementAndGet() > 0) return;
Throwable error = firstError.get();
if (error != null) {
callback.onError(error);
} else {
callback.onSuccess(finish(result));
}
}
}
@Override public synchronized void onError(Throwable throwable) {
if (log.isLoggable(Level.INFO)) {
log.log(Level.INFO, "error from " + call, throwable);
}
synchronized (callback) {
firstError.compareAndSet(null, throwable);
if (remaining.decrementAndGet() > 0) return;
callback.onError(firstError.get());
}
}
}
protected final List> cloneCalls() {
int length = calls.size();
if (length == 1) return Collections.singletonList(calls.get(0).clone());
List> result = new ArrayList<>(length);
for (int i = 0; i < length; i++) {
result.add(calls.get(i).clone());
}
return result;
}
@Override public String toString() {
return "AggregateCall{" + calls + "}";
}
}