From 2576d578ce2226f7a599839e3974905da9d490de Mon Sep 17 00:00:00 2001 From: fpanizza Date: Mon, 26 May 2025 18:17:03 -0300 Subject: [PATCH 1/3] use ReentrantLock to avoid concurrent execute cursor in SqLite database in Android when autocommit is on (NoIntegrity). --- .../com/genexus/db/DataStoreProvider.java | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/genexus/db/DataStoreProvider.java b/android/src/main/java/com/genexus/db/DataStoreProvider.java index ad59e50a2..23ea3236c 100644 --- a/android/src/main/java/com/genexus/db/DataStoreProvider.java +++ b/android/src/main/java/com/genexus/db/DataStoreProvider.java @@ -2,6 +2,7 @@ import java.sql.SQLException; import java.util.Date; +import java.util.concurrent.locks.ReentrantLock; import com.genexus.*; import com.genexus.db.driver.DataSource; @@ -164,10 +165,41 @@ public void executeBatch(int cursorIdx) { execute(cursorIdx, null, false); } + + static ReentrantLock lockExecute = new ReentrantLock(); + public synchronized void execute(int cursorIdx, Object[] parms) { - execute(cursorIdx, parms, true); - } + DataSource ds = getDataSourceNoException(); + if(ds!=null && ds.jdbcIntegrity==false) + { + // If the JDBC integrity is disabled, SQLIte is in autocommit mode. + // we need to lock the execute method to avoid concurrency issues. + // for example, the postExecute method (select changes()) cause exception in Android SQLite. + AndroidLog.debug("execute cursorIdx : " + cursorIdx); + long timeStamp = System.currentTimeMillis(); + lockExecute.lock(); + long timeStampLock2 = System.currentTimeMillis(); + AndroidLog.debug("START execute afterlock cursorIdx: " + cursorIdx + " Waiting time: " + (timeStampLock2 - timeStamp)); + try + { + // execute the cursor with the parameters. + execute(cursorIdx, parms, true); + } + finally + { + lockExecute.unlock(); + } + long timeStamp2 = System.currentTimeMillis(); + AndroidLog.debug("END execute cursorIdx: " + cursorIdx+ " Execute time: " + (timeStamp2 - timeStampLock2)); + } + else + { + // default case, we execute the cursor with the parameters. + execute(cursorIdx, parms, true); + } + + } private void execute(int cursorIdx, Object[] parms, boolean preExecute) { From b0aa02c959c16fcd788498ba28fdebccc28cb757 Mon Sep 17 00:00:00 2001 From: fpanizza Date: Fri, 30 May 2025 10:12:24 -0300 Subject: [PATCH 2/3] change var names and add better comment --- .../main/java/com/genexus/db/DataStoreProvider.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/genexus/db/DataStoreProvider.java b/android/src/main/java/com/genexus/db/DataStoreProvider.java index 23ea3236c..5a5ba00c5 100644 --- a/android/src/main/java/com/genexus/db/DataStoreProvider.java +++ b/android/src/main/java/com/genexus/db/DataStoreProvider.java @@ -173,14 +173,15 @@ public synchronized void execute(int cursorIdx, Object[] parms) DataSource ds = getDataSourceNoException(); if(ds!=null && ds.jdbcIntegrity==false) { - // If the JDBC integrity is disabled, SQLIte is in autocommit mode. + // If the JDBC integrity is disabled (XBASE_TINT), SQLIte is in autocommit mode. // we need to lock the execute method to avoid concurrency issues. // for example, the postExecute method (select changes()) cause exception in Android SQLite. + // it happends with execute sql statements like from diferent threads, like using procedures submit. AndroidLog.debug("execute cursorIdx : " + cursorIdx); - long timeStamp = System.currentTimeMillis(); + long timeStampStart = System.currentTimeMillis(); lockExecute.lock(); - long timeStampLock2 = System.currentTimeMillis(); - AndroidLog.debug("START execute afterlock cursorIdx: " + cursorIdx + " Waiting time: " + (timeStampLock2 - timeStamp)); + long timeStampLock = System.currentTimeMillis(); + AndroidLog.debug("START execute afterlock cursorIdx: " + cursorIdx + " Waiting time ms: " + (timeStampLock - timeStampStart)); try { // execute the cursor with the parameters. @@ -190,8 +191,8 @@ public synchronized void execute(int cursorIdx, Object[] parms) { lockExecute.unlock(); } - long timeStamp2 = System.currentTimeMillis(); - AndroidLog.debug("END execute cursorIdx: " + cursorIdx+ " Execute time: " + (timeStamp2 - timeStampLock2)); + long timeStampEnd = System.currentTimeMillis(); + AndroidLog.debug("END execute cursorIdx: " + cursorIdx+ " Execute time ms: " + (timeStampEnd - timeStampLock)); } else { From 972dd85898795cdc7b3179b39bcea6647c857950 Mon Sep 17 00:00:00 2001 From: fpanizza Date: Sat, 31 May 2025 12:35:07 -0300 Subject: [PATCH 3/3] only avoid concurrence for UpdateCursor --- .../com/genexus/db/DataStoreProvider.java | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/android/src/main/java/com/genexus/db/DataStoreProvider.java b/android/src/main/java/com/genexus/db/DataStoreProvider.java index 5a5ba00c5..1fe8d66de 100644 --- a/android/src/main/java/com/genexus/db/DataStoreProvider.java +++ b/android/src/main/java/com/genexus/db/DataStoreProvider.java @@ -177,22 +177,34 @@ public synchronized void execute(int cursorIdx, Object[] parms) // we need to lock the execute method to avoid concurrency issues. // for example, the postExecute method (select changes()) cause exception in Android SQLite. // it happends with execute sql statements like from diferent threads, like using procedures submit. - AndroidLog.debug("execute cursorIdx : " + cursorIdx); - long timeStampStart = System.currentTimeMillis(); - lockExecute.lock(); - long timeStampLock = System.currentTimeMillis(); - AndroidLog.debug("START execute afterlock cursorIdx: " + cursorIdx + " Waiting time ms: " + (timeStampLock - timeStampStart)); - try - { - // execute the cursor with the parameters. - execute(cursorIdx, parms, true); + Cursor cursor = cursors[cursorIdx]; + if (cursor instanceof UpdateCursor) // if the cursor is an UpdateCursor, we need to lock the execute method. + { + AndroidLog.debug("execute UpdateCursor cursorIdx : " + cursorIdx); + long timeStampStart = System.currentTimeMillis(); + lockExecute.lock(); + long timeStampLock = System.currentTimeMillis(); + AndroidLog.debug("START execute UpdateCursor afterlock cursorIdx: " + cursorIdx + " Waiting time ms: " + (timeStampLock - timeStampStart)); + try + { + // execute the cursor with the parameters. + execute(cursorIdx, parms, true); + } + finally + { + lockExecute.unlock(); + } + long timeStampEnd = System.currentTimeMillis(); + AndroidLog.debug("END execute UpdateCursor cursorIdx: " + cursorIdx+ " Execute time ms: " + (timeStampEnd - timeStampLock)); } - finally + else { - lockExecute.unlock(); + // if the cursor is not an UpdateCursor, we can execute it without locking. + long timeStampStart = System.currentTimeMillis(); + execute(cursorIdx, parms, true); + long timeStampEnd = System.currentTimeMillis(); + AndroidLog.debug("END execute cursorIdx: " + cursorIdx+ " Execute time ms: " + (timeStampEnd - timeStampStart)); } - long timeStampEnd = System.currentTimeMillis(); - AndroidLog.debug("END execute cursorIdx: " + cursorIdx+ " Execute time ms: " + (timeStampEnd - timeStampLock)); } else {