@@ -85,7 +85,12 @@ final class ResumableUploadFutureImpl<ResponseT> implements ResumableUploadFutur
8585 private final SettableApiFuture <ResponseT > resultFuture = SettableApiFuture .create ();
8686
8787 private volatile @ Nullable InputStream payload ;
88- private volatile @ Nullable String uploadSessionUrl ;
88+
89+ @ GuardedBy ("lock" )
90+ private @ Nullable String uploadSessionUrl ;
91+
92+ @ GuardedBy ("lock" )
93+ private @ Nullable Integer effectiveChunkSize ;
8994
9095 // Tracks the current operation's Future (start, chunk upload) to propagate cancellation, or
9196 // null once a terminal transition (succeed, fail, cancel) has claimed completion.
@@ -161,13 +166,20 @@ private void start(InputStreamSupplier payloadSupplier) throws IOException {
161166 @ Override
162167 public void onSuccess (ResumableUploadSession session ) {
163168 String sessionUrl = session .getUploadUrl ();
169+ int chunkSize ;
170+ try {
171+ chunkSize = alignChunkSize (options .getChunkSize (), session .getChunkGranularity ());
172+ } catch (Throwable t ) {
173+ fail (t );
174+ return ;
175+ }
164176 ResumableUploadChunkCoordinator <ResponseT > coordinator =
165177 new ResumableUploadChunkCoordinator <>(
166178 uploadChunkCallable ,
167179 queryStatusCallable ,
168180 sessionUrl ,
169181 payload ,
170- options . getChunkSize () ,
182+ chunkSize ,
171183 callContext ,
172184 recoveryAlgorithm ,
173185 executor ,
@@ -177,6 +189,7 @@ public void onSuccess(ResumableUploadSession session) {
177189 if (inFlightFuture == null ) {
178190 return ;
179191 }
192+ effectiveChunkSize = chunkSize ;
180193 uploadSessionUrl = sessionUrl ;
181194 inFlightFuture = uploadFuture ;
182195 }
@@ -213,7 +226,7 @@ public void onFailure(Throwable t) {
213226
214227 private void onTimeout () {
215228 String message ;
216- if (uploadSessionUrl != null ) {
229+ if (getUploadSessionUrl () != null ) {
217230 message = "Resumable upload timed out" ;
218231 } else {
219232 message = "Resumable upload timed out before session initiation completed" ;
@@ -251,14 +264,22 @@ private void fail(Throwable t) {
251264 }
252265
253266 private ApiException toTerminalException (Throwable t ) {
254- if (t instanceof ApiException && uploadSessionUrl == null ) {
267+ String sessionUrl ;
268+ Integer chunkSize ;
269+ synchronized (lock ) {
270+ sessionUrl = uploadSessionUrl ;
271+ chunkSize = effectiveChunkSize ;
272+ }
273+ if (t instanceof ApiException && sessionUrl == null ) {
255274 return (ApiException ) t ;
256275 }
257276 String message = firstNonNull (t .getMessage (), t .getClass ().getSimpleName ());
258- if (uploadSessionUrl != null ) {
259- message = message + " (upload URL: " + uploadSessionUrl + ")" ;
277+ if (sessionUrl != null ) {
278+ message =
279+ String .format (
280+ "%s (upload URL: %s, effective chunk size: %d)" , message , sessionUrl , chunkSize );
260281 }
261- // Rethrow with URL included in message.
282+ // Rethrow with URL and effective chunk size included in message.
262283 if (t instanceof ApiException ) {
263284 ApiException apiException = (ApiException ) t ;
264285 return ApiExceptionFactory .createException (
@@ -272,6 +293,16 @@ private ApiException toTerminalException(Throwable t) {
272293 message , t , statusCodeOf (StatusCode .Code .UNKNOWN ), false );
273294 }
274295
296+ static int alignChunkSize (int configuredChunkSize , int chunkGranularity ) {
297+ checkArgument (configuredChunkSize > 0 , "configuredChunkSize must be > 0" );
298+ checkArgument (chunkGranularity > 0 , "chunkGranularity must be > 0" );
299+ if (configuredChunkSize <= chunkGranularity ) {
300+ return chunkGranularity ;
301+ }
302+ int unalignedRemainder = configuredChunkSize % chunkGranularity ;
303+ return configuredChunkSize - unalignedRemainder ;
304+ }
305+
275306 private void closePayload (@ Nullable Throwable primaryException ) {
276307 if (payload == null ) {
277308 return ;
@@ -287,7 +318,16 @@ private void closePayload(@Nullable Throwable primaryException) {
287318
288319 @ Override
289320 public @ Nullable String getUploadSessionUrl () {
290- return uploadSessionUrl ;
321+ synchronized (lock ) {
322+ return uploadSessionUrl ;
323+ }
324+ }
325+
326+ @ Override
327+ public @ Nullable Integer getEffectiveChunkSize () {
328+ synchronized (lock ) {
329+ return effectiveChunkSize ;
330+ }
291331 }
292332
293333 @ Override
0 commit comments