1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 package org.mozilla.gecko.sync.repositories.uploaders; 6 7 import android.support.annotation.CallSuper; 8 import android.support.annotation.CheckResult; 9 10 /** 11 * Implements functionality shared by BatchMeta and Payload objects, namely: 12 * - keeping track of byte and record counts 13 * - incrementing those counts when records are added 14 * - checking if a record can fit 15 */ 16 /* @ThreadSafe */ 17 public abstract class BufferSizeTracker { 18 protected final Object accessLock; 19 20 /* @GuardedBy("accessLock") */ private long byteCount = BatchingUploader.PER_PAYLOAD_OVERHEAD_BYTE_COUNT; 21 /* @GuardedBy("accessLock") */ private long recordCount = 0; 22 /* @GuardedBy("accessLock") */ protected Long smallestRecordByteCount; 23 24 protected final long maxBytes; 25 protected final long maxRecords; 26 BufferSizeTracker(Object accessLock, long maxBytes, long maxRecords)27 public BufferSizeTracker(Object accessLock, long maxBytes, long maxRecords) { 28 this.accessLock = accessLock; 29 this.maxBytes = maxBytes; 30 this.maxRecords = maxRecords; 31 } 32 33 @CallSuper canFit(long recordDeltaByteCount)34 protected boolean canFit(long recordDeltaByteCount) { 35 synchronized (accessLock) { 36 return canFitRecordByteDelta(recordDeltaByteCount, recordCount, byteCount); 37 } 38 } 39 isEmpty()40 protected boolean isEmpty() { 41 synchronized (accessLock) { 42 return recordCount == 0; 43 } 44 } 45 46 /** 47 * Adds a record and returns a boolean indicating whether batch is estimated to be full afterwards. 48 */ 49 @CheckResult addAndEstimateIfFull(long recordDeltaByteCount)50 protected boolean addAndEstimateIfFull(long recordDeltaByteCount) { 51 synchronized (accessLock) { 52 // Sanity check. Calling this method when buffer won't fit the record is an error. 53 if (!canFitRecordByteDelta(recordDeltaByteCount, recordCount, byteCount)) { 54 throw new IllegalStateException("Buffer size exceeded"); 55 } 56 57 byteCount += recordDeltaByteCount; 58 recordCount += 1; 59 60 if (smallestRecordByteCount == null || smallestRecordByteCount > recordDeltaByteCount) { 61 smallestRecordByteCount = recordDeltaByteCount; 62 } 63 64 // See if we're full or nearly full after adding a record. 65 // We're halving smallestRecordByteCount because we're erring 66 // on the side of "can hopefully fit". We're trying to upload as soon as we know we 67 // should, but we also need to be mindful of minimizing total number of uploads we make. 68 return !canFitRecordByteDelta(smallestRecordByteCount / 2, recordCount, byteCount); 69 } 70 } 71 getByteCount()72 protected long getByteCount() { 73 synchronized (accessLock) { 74 // Ensure we account for payload overhead twice when the batch is empty. 75 // Payload overhead is either RECORDS_START ("[") or RECORDS_END ("]"), 76 // and for an empty payload we need account for both ("[]"). 77 if (recordCount == 0) { 78 return byteCount + BatchingUploader.PER_PAYLOAD_OVERHEAD_BYTE_COUNT; 79 } 80 return byteCount; 81 } 82 } 83 getRecordCount()84 protected long getRecordCount() { 85 synchronized (accessLock) { 86 return recordCount; 87 } 88 } 89 90 @CallSuper reset()91 protected void reset() { 92 synchronized (accessLock) { 93 byteCount = BatchingUploader.PER_PAYLOAD_OVERHEAD_BYTE_COUNT; 94 recordCount = 0; 95 } 96 } 97 98 @CallSuper canFitRecordByteDelta(long byteDelta, long recordCount, long byteCount)99 protected boolean canFitRecordByteDelta(long byteDelta, long recordCount, long byteCount) { 100 return recordCount < maxRecords 101 && (byteCount + byteDelta) <= maxBytes; 102 } 103 }