package com.aliyun.openservices.aliyun.log.producer.internals; import com.aliyun.openservices.aliyun.log.producer.*; import com.aliyun.openservices.aliyun.log.producer.errors.ResultFailedException; import com.aliyun.openservices.log.common.LogItem; import com.google.common.collect.EvictingQueue; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ProducerBatch implements Delayed { private static final Logger LOGGER = LoggerFactory.getLogger(ProducerBatch.class); private final GroupKey groupKey; private final String packageId; private final int batchSizeThresholdInBytes; private final int batchCountThreshold; private final List<LogItem> logItems = new ArrayList<LogItem>(); private final List<Thunk> thunks = new ArrayList<Thunk>(); private final long createdMs; private long nextRetryMs; private int curBatchSizeInBytes; private int curBatchCount; private final EvictingQueue<Attempt> reservedAttempts; private int attemptCount; public ProducerBatch( GroupKey groupKey, String packageId, int batchSizeThresholdInBytes, int batchCountThreshold, int maxReservedAttempts, long nowMs) { this.groupKey = groupKey; this.packageId = packageId; this.createdMs = nowMs; this.batchSizeThresholdInBytes = batchSizeThresholdInBytes; this.batchCountThreshold = batchCountThreshold; this.curBatchCount = 0; this.curBatchSizeInBytes = 0; this.reservedAttempts = EvictingQueue.create(maxReservedAttempts); this.attemptCount = 0; } public ListenableFuture<Result> tryAppend(LogItem item, int sizeInBytes, Callback callback) { if (!hasRoomFor(sizeInBytes, 1)) { return null; } else { SettableFuture<Result> future = SettableFuture.create(); logItems.add(item); thunks.add(new Thunk(callback, future)); curBatchCount++; curBatchSizeInBytes += sizeInBytes; return future; } } public ListenableFuture<Result> tryAppend( List<LogItem> items, int sizeInBytes, Callback callback) { if (!hasRoomFor(sizeInBytes, items.size())) { return null; } else { SettableFuture<Result> future = SettableFuture.create(); logItems.addAll(items); thunks.add(new Thunk(callback, future)); curBatchCount += items.size(); curBatchSizeInBytes += sizeInBytes; return future; } } public void appendAttempt(Attempt attempt) { reservedAttempts.add(attempt); this.attemptCount++; } public boolean isMeetSendCondition() { return curBatchSizeInBytes >= batchSizeThresholdInBytes || curBatchCount >= batchCountThreshold; } public long remainingMs(long nowMs, long lingerMs) { return lingerMs - createdTimeMs(nowMs); } public void fireCallbacksAndSetFutures() { List<Attempt> attempts = new ArrayList<Attempt>(reservedAttempts); Attempt attempt = Iterables.getLast(attempts); Result result = new Result(attempt.isSuccess(), attempts, attemptCount); fireCallbacks(result); setFutures(result); } public GroupKey getGroupKey() { return groupKey; } public String getPackageId() { return packageId; } public List<LogItem> getLogItems() { return logItems; } public long getNextRetryMs() { return nextRetryMs; } public void setNextRetryMs(long nextRetryMs) { this.nextRetryMs = nextRetryMs; } public String getProject() { return groupKey.getProject(); } public String getLogStore() { return groupKey.getLogStore(); } public String getTopic() { return groupKey.getTopic(); } public String getSource() { return groupKey.getSource(); } public String getShardHash() { return groupKey.getShardHash(); } public int getCurBatchSizeInBytes() { return curBatchSizeInBytes; } public int getRetries() { return Math.max(0, attemptCount - 1); } private boolean hasRoomFor(int sizeInBytes, int count) { return curBatchSizeInBytes + sizeInBytes <= ProducerConfig.MAX_BATCH_SIZE_IN_BYTES && curBatchCount + count <= ProducerConfig.MAX_BATCH_COUNT; } private long createdTimeMs(long nowMs) { return Math.max(0, nowMs - createdMs); } private void fireCallbacks(Result result) { for (Thunk thunk : thunks) { try { if (thunk.callback != null) { thunk.callback.onCompletion(result); } } catch (Exception e) { LOGGER.error("Failed to execute user-provided callback, groupKey={}, e=", groupKey, e); } } } private void setFutures(Result result) { for (Thunk thunk : thunks) { try { if (result.isSuccessful()) { thunk.future.set(result); } else { thunk.future.setException(new ResultFailedException(result)); } } catch (Exception e) { LOGGER.error("Failed to set future, groupKey={}, e=", groupKey, e); } } } @Override public long getDelay(@Nonnull TimeUnit unit) { return unit.convert(nextRetryMs - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(@Nonnull Delayed o) { return (int) (nextRetryMs - ((ProducerBatch) o).getNextRetryMs()); } @Override public String toString() { return "ProducerBatch{" + "groupKey=" + groupKey + ", packageId='" + packageId + '\'' + ", batchSizeThresholdInBytes=" + batchSizeThresholdInBytes + ", batchCountThreshold=" + batchCountThreshold + ", logItems=" + logItems + ", thunks=" + thunks + ", createdMs=" + createdMs + ", nextRetryMs=" + nextRetryMs + ", curBatchSizeInBytes=" + curBatchSizeInBytes + ", curBatchCount=" + curBatchCount + ", reservedAttempts=" + reservedAttempts + ", attemptCount=" + attemptCount + '}'; } private static final class Thunk { final Callback callback; final SettableFuture<Result> future; Thunk(Callback callback, SettableFuture<Result> future) { this.callback = callback; this.future = future; } } }