Skip to content

Commit 1c7bc78

Browse files
committed
loadAllFromCursor with deadlock prevention
1 parent 1fd4b70 commit 1c7bc78

File tree

2 files changed

+97
-58
lines changed

2 files changed

+97
-58
lines changed

DaoCore/src/main/java/de/greenrobot/dao/AbstractDao.java

Lines changed: 85 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@
1616

1717
package de.greenrobot.dao;
1818

19-
import java.util.ArrayList;
20-
import java.util.Arrays;
21-
import java.util.Collection;
22-
import java.util.List;
23-
2419
import android.database.CrossProcessCursor;
2520
import android.database.Cursor;
2621
import android.database.CursorWindow;
2722
import android.database.DatabaseUtils;
2823
import android.database.sqlite.SQLiteDatabase;
2924
import android.database.sqlite.SQLiteStatement;
25+
26+
import java.util.ArrayList;
27+
import java.util.Arrays;
28+
import java.util.Collection;
29+
import java.util.Collections;
30+
import java.util.List;
31+
3032
import de.greenrobot.dao.identityscope.IdentityScope;
3133
import de.greenrobot.dao.identityscope.IdentityScopeLong;
3234
import de.greenrobot.dao.internal.DaoConfig;
@@ -37,15 +39,12 @@
3739

3840
/**
3941
* Base class for all DAOs: Implements entity operations like insert, load, delete, and query.
40-
*
42+
* <p>
4143
* This class is thread-safe.
4244
*
45+
* @param <T> Entity type
46+
* @param <K> Primary key (PK) type; use Void if entity does not have exactly one PK
4347
* @author Markus
44-
*
45-
* @param <T>
46-
* Entity type
47-
* @param <K>
48-
* Primary key (PK) type; use Void if entity does not have exactly one PK
4948
*/
5049
/*
5150
* When operating on TX, statements, or identity scope the following locking order must be met to avoid deadlocks:
@@ -118,8 +117,7 @@ public String[] getNonPkColumns() {
118117
/**
119118
* Loads and entity for the given PK.
120119
*
121-
* @param key
122-
* a PK value or null
120+
* @param key a PK value or null
123121
* @return The entity or null, if no entity matched the PK value
124122
*/
125123
public T load(K key) {
@@ -134,13 +132,13 @@ public T load(K key) {
134132
}
135133
}
136134
String sql = statements.getSelectByKey();
137-
String[] keyArray = new String[] { key.toString() };
135+
String[] keyArray = new String[]{key.toString()};
138136
Cursor cursor = db.rawQuery(sql, keyArray);
139137
return loadUniqueAndCloseCursor(cursor);
140138
}
141139

142140
public T loadByRowId(long rowId) {
143-
String[] idArray = new String[] { Long.toString(rowId) };
141+
String[] idArray = new String[]{Long.toString(rowId)};
144142
Cursor cursor = db.rawQuery(statements.getSelectByRowId(), idArray);
145143
return loadUniqueAndCloseCursor(cursor);
146144
}
@@ -190,8 +188,7 @@ protected List<T> loadAllAndCloseCursor(Cursor cursor) {
190188
/**
191189
* Inserts the given entities in the database using a transaction.
192190
*
193-
* @param entities
194-
* The entities to insert.
191+
* @param entities The entities to insert.
195192
*/
196193
public void insertInTx(Iterable<T> entities) {
197194
insertInTx(entities, isEntityUpdateable());
@@ -200,8 +197,7 @@ public void insertInTx(Iterable<T> entities) {
200197
/**
201198
* Inserts the given entities in the database using a transaction.
202199
*
203-
* @param entities
204-
* The entities to insert.
200+
* @param entities The entities to insert.
205201
*/
206202
public void insertInTx(T... entities) {
207203
insertInTx(Arrays.asList(entities), isEntityUpdateable());
@@ -211,10 +207,8 @@ public void insertInTx(T... entities) {
211207
* Inserts the given entities in the database using a transaction. The given entities will become tracked if the PK
212208
* is set.
213209
*
214-
* @param entities
215-
* The entities to insert.
216-
* @param setPrimaryKey
217-
* if true, the PKs of the given will be set after the insert; pass false to improve performance.
210+
* @param entities The entities to insert.
211+
* @param setPrimaryKey if true, the PKs of the given will be set after the insert; pass false to improve performance.
218212
*/
219213
public void insertInTx(Iterable<T> entities, boolean setPrimaryKey) {
220214
SQLiteStatement stmt = statements.getInsertStatement();
@@ -225,10 +219,8 @@ public void insertInTx(Iterable<T> entities, boolean setPrimaryKey) {
225219
* Inserts or replaces the given entities in the database using a transaction. The given entities will become
226220
* tracked if the PK is set.
227221
*
228-
* @param entities
229-
* The entities to insert.
230-
* @param setPrimaryKey
231-
* if true, the PKs of the given will be set after the insert; pass false to improve performance.
222+
* @param entities The entities to insert.
223+
* @param setPrimaryKey if true, the PKs of the given will be set after the insert; pass false to improve performance.
232224
*/
233225
public void insertOrReplaceInTx(Iterable<T> entities, boolean setPrimaryKey) {
234226
SQLiteStatement stmt = statements.getInsertOrReplaceStatement();
@@ -238,8 +230,7 @@ public void insertOrReplaceInTx(Iterable<T> entities, boolean setPrimaryKey) {
238230
/**
239231
* Inserts or replaces the given entities in the database using a transaction.
240232
*
241-
* @param entities
242-
* The entities to insert.
233+
* @param entities The entities to insert.
243234
*/
244235
public void insertOrReplaceInTx(Iterable<T> entities) {
245236
insertOrReplaceInTx(entities, isEntityUpdateable());
@@ -248,8 +239,7 @@ public void insertOrReplaceInTx(Iterable<T> entities) {
248239
/**
249240
* Inserts or replaces the given entities in the database using a transaction.
250241
*
251-
* @param entities
252-
* The entities to insert.
242+
* @param entities The entities to insert.
253243
*/
254244
public void insertOrReplaceInTx(T... entities) {
255245
insertOrReplaceInTx(Arrays.asList(entities), isEntityUpdateable());
@@ -369,12 +359,18 @@ protected void updateKeyAfterInsertAndAttach(T entity, long rowId, boolean lock)
369359
/** Reads all available rows from the given cursor and returns a list of entities. */
370360
protected List<T> loadAllFromCursor(Cursor cursor) {
371361
int count = cursor.getCount();
362+
if (count == 0) {
363+
return Collections.EMPTY_LIST;
364+
}
372365
List<T> list = new ArrayList<T>(count);
366+
CursorWindow window = null;
367+
boolean useFastCursor = false;
373368
if (cursor instanceof CrossProcessCursor) {
374-
CursorWindow window = ((CrossProcessCursor) cursor).getWindow();
375-
if (window != null) { // E.g. Roboelectric has no Window at this point
369+
window = ((CrossProcessCursor) cursor).getWindow();
370+
if (window != null) { // E.g. Robolectric has no Window at this point
376371
if (window.getNumRows() == count) {
377372
cursor = new FastCursor(window);
373+
useFastCursor = true;
378374
} else {
379375
DaoLog.d("Window vs. result size: " + window.getNumRows() + "/" + count);
380376
}
@@ -386,10 +382,15 @@ protected List<T> loadAllFromCursor(Cursor cursor) {
386382
identityScope.lock();
387383
identityScope.reserveRoom(count);
388384
}
385+
389386
try {
390-
do {
391-
list.add(loadCurrent(cursor, 0, false));
392-
} while (cursor.moveToNext());
387+
if (!useFastCursor && window != null && identityScope != null) {
388+
loadAllUnlockOnWindowBounds(cursor, window, list);
389+
} else {
390+
do {
391+
list.add(loadCurrent(cursor, 0, false));
392+
} while (cursor.moveToNext());
393+
}
393394
} finally {
394395
if (identityScope != null) {
395396
identityScope.unlock();
@@ -399,6 +400,42 @@ protected List<T> loadAllFromCursor(Cursor cursor) {
399400
return list;
400401
}
401402

403+
private void loadAllUnlockOnWindowBounds(Cursor cursor, CursorWindow window, List<T> list) {
404+
int windowEnd = window.getStartPosition() + window.getNumRows();
405+
for (int row = 0; ; row++) {
406+
list.add(loadCurrent(cursor, 0, false));
407+
row++;
408+
if (row >= windowEnd) {
409+
window = moveToNextUnlocked(cursor);
410+
if(window == null) {
411+
break;
412+
}
413+
windowEnd = window.getStartPosition() + window.getNumRows();
414+
} else {
415+
if(!cursor.moveToNext()) {
416+
break;
417+
}
418+
}
419+
}
420+
}
421+
422+
/**
423+
* Unlock identityScope during cursor.moveToNext() when it is about to fill the window (needs a db connection):
424+
* We should not hold the lock while trying to acquire a db connection to avoid deadlocks.
425+
*/
426+
private CursorWindow moveToNextUnlocked(Cursor cursor) {
427+
identityScope.unlock();
428+
try {
429+
if(cursor.moveToNext()) {
430+
return ((CrossProcessCursor) cursor).getWindow();
431+
} else {
432+
return null;
433+
}
434+
} finally {
435+
identityScope.lock();
436+
}
437+
}
438+
402439
/** Internal use only. Considers identity scope. */
403440
final protected T loadCurrent(Cursor cursor, int offset, boolean lock) {
404441
if (identityScopeLong != null) {
@@ -580,8 +617,7 @@ private void deleteInTxInternal(Iterable<T> entities, Iterable<K> keys) {
580617
/**
581618
* Deletes the given entities in the database using a transaction.
582619
*
583-
* @param entities
584-
* The entities to delete.
620+
* @param entities The entities to delete.
585621
*/
586622
public void deleteInTx(Iterable<T> entities) {
587623
deleteInTxInternal(entities, null);
@@ -590,8 +626,7 @@ public void deleteInTx(Iterable<T> entities) {
590626
/**
591627
* Deletes the given entities in the database using a transaction.
592628
*
593-
* @param entities
594-
* The entities to delete.
629+
* @param entities The entities to delete.
595630
*/
596631
public void deleteInTx(T... entities) {
597632
deleteInTxInternal(Arrays.asList(entities), null);
@@ -600,8 +635,7 @@ public void deleteInTx(T... entities) {
600635
/**
601636
* Deletes all entities with the given keys in the database using a transaction.
602637
*
603-
* @param keys
604-
* Keys of the entities to delete.
638+
* @param keys Keys of the entities to delete.
605639
*/
606640
public void deleteByKeyInTx(Iterable<K> keys) {
607641
deleteInTxInternal(null, keys);
@@ -610,8 +644,7 @@ public void deleteByKeyInTx(Iterable<K> keys) {
610644
/**
611645
* Deletes all entities with the given keys in the database using a transaction.
612646
*
613-
* @param keys
614-
* Keys of the entities to delete.
647+
* @param keys Keys of the entities to delete.
615648
*/
616649
public void deleteByKeyInTx(K... keys) {
617650
deleteInTxInternal(null, Arrays.asList(keys));
@@ -622,7 +655,7 @@ public void refresh(T entity) {
622655
assertSinglePk();
623656
K key = getKeyVerified(entity);
624657
String sql = statements.getSelectByKey();
625-
String[] keyArray = new String[] { key.toString() };
658+
String[] keyArray = new String[]{key.toString()};
626659
Cursor cursor = db.rawQuery(sql, keyArray);
627660
try {
628661
boolean available = cursor.moveToFirst();
@@ -683,11 +716,9 @@ protected void updateInsideSynchronized(T entity, SQLiteStatement stmt, boolean
683716
/**
684717
* Attaches the entity to the identity scope. Calls attachEntity(T entity).
685718
*
686-
* @param key
687-
* Needed only for identity scope, pass null if there's none.
688-
* @param entity
689-
* The entitiy to attach
690-
* */
719+
* @param key Needed only for identity scope, pass null if there's none.
720+
* @param entity The entitiy to attach
721+
*/
691722
protected final void attachEntity(K key, T entity, boolean lock) {
692723
attachEntity(entity);
693724
if (identityScope != null && key != null) {
@@ -703,17 +734,15 @@ protected final void attachEntity(K key, T entity, boolean lock) {
703734
* Sub classes with relations additionally set the DaoMaster here. Must be called before the entity is attached to
704735
* the identity scope.
705736
*
706-
* @param entity
707-
* The entitiy to attach
708-
* */
737+
* @param entity The entitiy to attach
738+
*/
709739
protected void attachEntity(T entity) {
710740
}
711741

712742
/**
713743
* Updates the given entities in the database using a transaction.
714744
*
715-
* @param entities
716-
* The entities to insert.
745+
* @param entities The entities to insert.
717746
*/
718747
public void updateInTx(Iterable<T> entities) {
719748
SQLiteStatement stmt = statements.getUpdateStatement();
@@ -754,8 +783,7 @@ public void updateInTx(Iterable<T> entities) {
754783
/**
755784
* Updates the given entities in the database using a transaction.
756785
*
757-
* @param entities
758-
* The entities to update.
786+
* @param entities The entities to update.
759787
*/
760788
public void updateInTx(T... entities) {
761789
updateInTx(Arrays.asList(entities));

DaoTest/src/de/greenrobot/daotest/DeadlockPreventionTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,17 @@ private class InsertThread extends Thread {
108108

109109
@Override
110110
public void run() {
111+
List<TestEntity> toDelete = new ArrayList<>();
111112
while (done.getCount() > 0) {
112113
TestEntity entity = new TestEntity();
113114
entity.setSimpleStringNotNull("TextThread" + counter);
114115
dao.insert(entity);
116+
toDelete.add(entity);
115117
counter++;
116118
if (counter % 10 == 0) {
117-
System.out.println("Thread inserted " + counter);
119+
System.out.println("Thread inserted " + counter+ ", now deleting");
120+
dao.deleteInTx(toDelete);
121+
toDelete.clear();
118122
}
119123
}
120124
}
@@ -126,6 +130,7 @@ private class InsertBatchThread extends Thread {
126130
@Override
127131
public void run() {
128132
List<TestEntity> batch = new ArrayList<>();
133+
List<TestEntity> toDelete = new ArrayList<>();
129134
while (done.getCount() > 0) {
130135
TestEntity entity = new TestEntity();
131136
entity.setSimpleStringNotNull("TextThreadBatch" + counter);
@@ -134,8 +139,14 @@ public void run() {
134139
if (counter % 10 == 0) {
135140
dao.insertInTx(batch);
136141
System.out.println("Batch Thread inserted " + counter);
142+
toDelete.addAll(batch);
137143
batch.clear();
138144
}
145+
if (counter % 1000 == 0) {
146+
dao.deleteInTx(toDelete);
147+
toDelete.clear();
148+
System.out.println("Batch Thread deleted " + counter);
149+
}
139150
}
140151
}
141152
}

0 commit comments

Comments
 (0)