Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;

@RunWith(AndroidJUnit4.class)
public class OfflineRoomUnitTest {
Expand Down Expand Up @@ -185,5 +191,85 @@ public void RetireRetries() {
}
}
}

@Test
public void ConcurrentPageSizeInitialization() throws InterruptedException {
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
try (OfflineRoom room = new OfflineRoom(appContext, "OfflineRoomConcurrent")) {
room.deleteAllRecords();

// Add a record to ensure the database is not empty
StorageRecord record = new StorageRecord(
0, "Test",
StorageRecord.EventLatency_Normal,
StorageRecord.EventPersistence_Normal,
32,
1,
0,
new byte[]{1, 2, 3});
room.storeRecords(record);

final int threadCount = 10;
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch doneLatch = new CountDownLatch(threadCount);
final List<Long> pageSizes = new ArrayList<>();
final List<Long> totalSizes = new ArrayList<>();
final AtomicInteger errorCount = new AtomicInteger(0);

// Create multiple threads that will call loadPageSize() and totalSize() concurrently
for (int i = 0; i < threadCount; i++) {
final int threadId = i;
new Thread(() -> {
try {
// Wait for all threads to be ready
startLatch.await();

// Call both methods to test concurrent initialization
long pageSize = room.loadPageSize();
long totalSize = room.totalSize();

synchronized (pageSizes) {
pageSizes.add(pageSize);
totalSizes.add(totalSize);
}
} catch (Exception e) {
errorCount.incrementAndGet();
e.printStackTrace();
} finally {
doneLatch.countDown();
}
}).start();
}

// Start all threads simultaneously
startLatch.countDown();

// Wait for all threads to complete
doneLatch.await();

// Verify no errors occurred
assertEquals("No errors should occur during concurrent access", 0, errorCount.get());

// Verify all threads got results
assertEquals("All threads should complete", threadCount, pageSizes.size());
assertEquals("All threads should complete", threadCount, totalSizes.size());

// Verify all threads got the same page size (no race condition)
long firstPageSize = pageSizes.get(0);
assertThat("Page size should be valid", firstPageSize, greaterThan(0L));
assertThat("Page size should be a power of 2", firstPageSize & (firstPageSize - 1), is(0L));

for (long pageSize : pageSizes) {
assertEquals("All threads should get the same page size", firstPageSize, pageSize);
}

// Verify all threads got a valid total size
for (long totalSize : totalSizes) {
assertThat("Total size should be valid", totalSize, greaterThan(0L));
}

room.deleteAllRecords();
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ public Long call()
private OfflineRoomDatabase m_db;
private StorageRecordDao m_srDao;
private StorageSettingDao m_settingDao;
private long m_pageSize = -1;
private volatile long m_pageSize = -1;
private static final long PAGE_SIZE_DEFAULT = 4096;
private static final Object PAGE_SIZE_LOCK = new Object();

public OfflineRoom(Context context, String name) {
RoomDatabase.Builder<OfflineRoomDatabase> builder;
Expand Down Expand Up @@ -290,22 +291,26 @@ public void deleteSetting(String name) {

private void initPageSize() {
if (m_pageSize == -1) {
try {
try (Cursor c = m_db.query("PRAGMA page_size", null)) {
if (c.getCount() == 1 && c.getColumnCount() == 1) {
c.moveToFirst();
m_pageSize = c.getLong(0);
} else {
synchronized (PAGE_SIZE_LOCK) {
if (m_pageSize == -1) {
try {
try (Cursor c = m_db.query("PRAGMA page_size", null)) {
if (c.getCount() == 1 && c.getColumnCount() == 1) {
c.moveToFirst();
m_pageSize = c.getLong(0);
} else {
m_pageSize = PAGE_SIZE_DEFAULT;
Log.e("MAE",
String.format("Unexpected result from PRAGMA page_size: %d rows, %d columns",
c.getCount(),
c.getColumnCount()));
}
}
} catch (Exception e) {
m_pageSize = PAGE_SIZE_DEFAULT;
Log.e("MAE",
String.format("Unexpected result from PRAGMA page_size: %d rows, %d columns",
c.getCount(),
c.getColumnCount()));
Log.e("MAE", "Failed to query PRAGMA page_size, using default page size.", e);
}
}
} catch (Exception e) {
m_pageSize = PAGE_SIZE_DEFAULT;
Log.e("MAE", "Failed to query PRAGMA page_size, using default page size.", e);
}
}
Comment on lines 291 to 315
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thread-safety fix for concurrent initialization lacks test coverage. Consider adding a test that verifies thread-safe initialization by calling totalSize() or loadPageSize() concurrently from multiple threads to ensure the PRAGMA query is only executed once and that no race conditions occur.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added ConcurrentPageSizeInitialization test that verifies thread-safe initialization by:

  • Creating 10 threads that simultaneously call loadPageSize() and totalSize()
  • Using CountDownLatch to ensure concurrent execution
  • Verifying all threads receive the same page size value (no race condition)
  • Validating no errors occur during concurrent access
  • Confirming page size is valid (positive and power of 2)

Changes in commit b140ab0.

}
Expand Down