From eb6d20663337e9ce9e37f60669f14c5b984809bb Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 27 Sep 2026 18:26:50 +0000 Subject: [PATCH 1/2] fix: resolve SonarCloud java:S3077 rule in UploadIdFactory Remove volatile modifier on Pattern reference and synchronize setUploadUri, getUploadUri, and getUploadUriPattern methods to ensure thread safety during pattern compilation and upload URI updates without Sonar warnings. Co-authored-by: tomdesair <14034630+tomdesair@users.noreply.github.com> --- .../tus/server/upload/UploadIdFactory.java | 15 +++--- .../upload/UuidUploadIdFactoryTest.java | 49 +++++++++++++++++++ 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/main/java/me/desair/tus/server/upload/UploadIdFactory.java b/src/main/java/me/desair/tus/server/upload/UploadIdFactory.java index 4a5f21c5..feb9a609 100644 --- a/src/main/java/me/desair/tus/server/upload/UploadIdFactory.java +++ b/src/main/java/me/desair/tus/server/upload/UploadIdFactory.java @@ -15,9 +15,10 @@ public abstract class UploadIdFactory { private String uploadUri = "/"; - // volatile ensures changes made via setUploadUri(..) are immediately visible across - // multiple concurrent request threads without stale caching. - private volatile Pattern uploadUriPattern = null; + // Access and mutation of uploadUri and uploadUriPattern are synchronized on this instance. + // Using synchronized methods avoids Sonar S3077 warnings regarding volatile object references + // while guaranteeing thread-safe lazy compilation and cross-thread memory visibility. + private Pattern uploadUriPattern = null; /** * Set the URI or absolute URL under which the main tus upload endpoint is hosted. Optionally, @@ -27,7 +28,7 @@ public abstract class UploadIdFactory { * * @param uploadUri The URI or URL of the main tus upload endpoint */ - public void setUploadUri(String uploadUri) { + public synchronized void setUploadUri(String uploadUri) { Validate.notBlank(uploadUri, "The upload URI pattern cannot be blank"); Validate.isTrue( Strings.CS.startsWith(uploadUri, "/") @@ -45,7 +46,7 @@ public void setUploadUri(String uploadUri) { * * @return The URI of the main tus upload endpoint. */ - public String getUploadUri() { + public synchronized String getUploadUri() { return uploadUri; } @@ -90,10 +91,10 @@ public UploadId readUploadId(String url) { * * @return A (cached) Pattern to match upload URI's */ - protected Pattern getUploadUriPattern() { + protected synchronized Pattern getUploadUriPattern() { if (uploadUriPattern == null) { // We will extract the upload ID's by removing the upload URI from the start of the - // request URI + // request URI. Synchronization ensures single compilation across concurrent threads. String path = Utils.extractUriPath(uploadUri); uploadUriPattern = Pattern.compile("^.*" + path + (Strings.CS.endsWith(path, "/") ? "" : "/?")); diff --git a/src/test/java/me/desair/tus/server/upload/UuidUploadIdFactoryTest.java b/src/test/java/me/desair/tus/server/upload/UuidUploadIdFactoryTest.java index 5049eb75..1feae4f5 100644 --- a/src/test/java/me/desair/tus/server/upload/UuidUploadIdFactoryTest.java +++ b/src/test/java/me/desair/tus/server/upload/UuidUploadIdFactoryTest.java @@ -6,6 +6,11 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Before; import org.junit.Test; @@ -159,4 +164,48 @@ public void readUploadIdRegexAbsoluteUrl() throws Exception { public void createId() throws Exception { assertThat(idFactory.createId(), not(nullValue())); } + + @Test + public void testConcurrentUploadUriAccessAndUpdates() throws Exception { + int threadCount = 10; + int iterations = 100; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + CountDownLatch startLatch = new CountDownLatch(1); + CountDownLatch doneLatch = new CountDownLatch(threadCount); + AtomicBoolean errorOccurred = new AtomicBoolean(false); + + idFactory.setUploadUri("/test/upload"); + + for (int i = 0; i < threadCount; i++) { + final int threadId = i; + executor.submit( + () -> { + try { + startLatch.await(); + for (int j = 0; j < iterations; j++) { + if (threadId % 2 == 0) { + idFactory.setUploadUri("/test/upload" + (j % 5)); + } else { + UploadId id = + idFactory.readUploadId( + "/test/upload" + (j % 5) + "/1911e8a4-6939-490c-b58b-a5d70f8d91fb"); + if (id != null && !id.toString().equals("1911e8a4-6939-490c-b58b-a5d70f8d91fb")) { + errorOccurred.set(true); + } + } + } + } catch (Exception e) { + errorOccurred.set(true); + } finally { + doneLatch.countDown(); + } + }); + } + + startLatch.countDown(); + assertThat(doneLatch.await(5, TimeUnit.SECONDS), is(true)); + executor.shutdown(); + + assertThat(errorOccurred.get(), is(false)); + } } From 1c8acf31fb9aa3bf0d3d19aa19a5bf679d769927 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2026 07:10:33 +0000 Subject: [PATCH 2/2] fix: resolve SonarCloud java:S3077 using ReentrantReadWriteLock Use ReentrantReadWriteLock in UploadIdFactory to provide lock-free concurrent reads for pattern retrieval while ensuring thread-safe lazy pattern compilation and upload URI updates without triggering Sonar S3077 warnings. Co-authored-by: tomdesair <14034630+tomdesair@users.noreply.github.com> --- .../tus/server/upload/UploadIdFactory.java | 61 ++++++++++++++----- .../upload/UuidUploadIdFactoryTest.java | 9 ++- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/main/java/me/desair/tus/server/upload/UploadIdFactory.java b/src/main/java/me/desair/tus/server/upload/UploadIdFactory.java index feb9a609..274dd735 100644 --- a/src/main/java/me/desair/tus/server/upload/UploadIdFactory.java +++ b/src/main/java/me/desair/tus/server/upload/UploadIdFactory.java @@ -1,6 +1,8 @@ package me.desair.tus.server.upload; import java.io.Serializable; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Matcher; import java.util.regex.Pattern; import me.desair.tus.server.util.Utils; @@ -14,10 +16,12 @@ */ public abstract class UploadIdFactory { + private final ReadWriteLock lock = new ReentrantReadWriteLock(); private String uploadUri = "/"; - // Access and mutation of uploadUri and uploadUriPattern are synchronized on this instance. - // Using synchronized methods avoids Sonar S3077 warnings regarding volatile object references - // while guaranteeing thread-safe lazy compilation and cross-thread memory visibility. + // Read and write operations on uploadUri and uploadUriPattern are guarded by a + // ReentrantReadWriteLock. + // This enables concurrent lock-free reads for high performance while ensuring thread-safe lazy + // pattern compilation and state mutation without triggering Sonar S3077 warnings. private Pattern uploadUriPattern = null; /** @@ -28,7 +32,7 @@ public abstract class UploadIdFactory { * * @param uploadUri The URI or URL of the main tus upload endpoint */ - public synchronized void setUploadUri(String uploadUri) { + public void setUploadUri(String uploadUri) { Validate.notBlank(uploadUri, "The upload URI pattern cannot be blank"); Validate.isTrue( Strings.CS.startsWith(uploadUri, "/") @@ -36,8 +40,14 @@ public synchronized void setUploadUri(String uploadUri) { || Strings.CS.startsWith(uploadUri, "https://"), "The upload URI should start with /, http://, or https://"); Validate.isTrue(!Strings.CS.endsWith(uploadUri, "$"), "The upload URI should not end with $"); - this.uploadUri = uploadUri; - this.uploadUriPattern = null; + + lock.writeLock().lock(); + try { + this.uploadUri = uploadUri; + this.uploadUriPattern = null; + } finally { + lock.writeLock().unlock(); + } } /** @@ -46,8 +56,13 @@ public synchronized void setUploadUri(String uploadUri) { * * @return The URI of the main tus upload endpoint. */ - public synchronized String getUploadUri() { - return uploadUri; + public String getUploadUri() { + lock.readLock().lock(); + try { + return uploadUri; + } finally { + lock.readLock().unlock(); + } } /** @@ -91,14 +106,28 @@ public UploadId readUploadId(String url) { * * @return A (cached) Pattern to match upload URI's */ - protected synchronized Pattern getUploadUriPattern() { - if (uploadUriPattern == null) { - // We will extract the upload ID's by removing the upload URI from the start of the - // request URI. Synchronization ensures single compilation across concurrent threads. - String path = Utils.extractUriPath(uploadUri); - uploadUriPattern = - Pattern.compile("^.*" + path + (Strings.CS.endsWith(path, "/") ? "" : "/?")); + protected Pattern getUploadUriPattern() { + lock.readLock().lock(); + try { + if (uploadUriPattern != null) { + return uploadUriPattern; + } + } finally { + lock.readLock().unlock(); + } + + lock.writeLock().lock(); + try { + if (uploadUriPattern == null) { + // Extract upload IDs by removing upload URI from start of request URI. + // Write lock ensures single pattern compilation across concurrent threads. + String path = Utils.extractUriPath(uploadUri); + uploadUriPattern = + Pattern.compile("^.*" + path + (Strings.CS.endsWith(path, "/") ? "" : "/?")); + } + return uploadUriPattern; + } finally { + lock.writeLock().unlock(); } - return uploadUriPattern; } } diff --git a/src/test/java/me/desair/tus/server/upload/UuidUploadIdFactoryTest.java b/src/test/java/me/desair/tus/server/upload/UuidUploadIdFactoryTest.java index 1feae4f5..8a2eb779 100644 --- a/src/test/java/me/desair/tus/server/upload/UuidUploadIdFactoryTest.java +++ b/src/test/java/me/desair/tus/server/upload/UuidUploadIdFactoryTest.java @@ -167,7 +167,7 @@ public void createId() throws Exception { @Test public void testConcurrentUploadUriAccessAndUpdates() throws Exception { - int threadCount = 10; + int threadCount = 12; int iterations = 100; ExecutorService executor = Executors.newFixedThreadPool(threadCount); CountDownLatch startLatch = new CountDownLatch(1); @@ -183,8 +183,13 @@ public void testConcurrentUploadUriAccessAndUpdates() throws Exception { try { startLatch.await(); for (int j = 0; j < iterations; j++) { - if (threadId % 2 == 0) { + if (threadId % 3 == 0) { idFactory.setUploadUri("/test/upload" + (j % 5)); + } else if (threadId % 3 == 1) { + String uri = idFactory.getUploadUri(); + if (uri == null || !uri.startsWith("/test/upload")) { + errorOccurred.set(true); + } } else { UploadId id = idFactory.readUploadId(