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") */ private 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
canFitRecordByteDelta(long byteDelta, long recordCount, long byteCount)91     protected boolean canFitRecordByteDelta(long byteDelta, long recordCount, long byteCount) {
92         return recordCount < maxRecords
93                 && (byteCount + byteDelta) <= maxBytes;
94     }
95 }