/* * Copyright (C) 2011-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * This file is part of greenDAO Generator. * * greenDAO Generator is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * greenDAO Generator is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with greenDAO Generator. If not, see <http://www.gnu.org/licenses/>. */ package org.greenrobot.greendao.daotest; import java.util.concurrent.CountDownLatch; import android.os.SystemClock; import org.greenrobot.greendao.DaoLog; import org.greenrobot.greendao.database.Database; import org.greenrobot.greendao.database.DatabaseStatement; import org.greenrobot.greendao.query.DeleteQuery; import org.greenrobot.greendao.query.Query; import org.greenrobot.greendao.test.AbstractDaoSessionTest; public class DaoSessionConcurrentTest extends AbstractDaoSessionTest<DaoMaster, DaoSession> { class TestThread extends Thread { final Runnable runnable; public TestThread(Runnable runnable) { this.runnable = runnable; } @Override public void run() { latchThreadsReady.countDown(); try { latchInsideTx.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } runnable.run(); latchThreadsDone.countDown(); } } private final static int TIME_TO_WAIT_FOR_THREAD = 100; // Use 1000 to be on the safe side, 100 once stable protected TestEntityDao dao; protected CountDownLatch latchThreadsReady; protected CountDownLatch latchInsideTx; protected CountDownLatch latchThreadsDone; public DaoSessionConcurrentTest() { super(DaoMaster.class); } @Override protected void setUp() throws Exception { super.setUp(); dao = daoSession.getTestEntityDao(); } protected void initThreads(Runnable... runnables) throws InterruptedException { latchThreadsReady = new CountDownLatch(runnables.length); latchInsideTx = new CountDownLatch(1); latchThreadsDone = new CountDownLatch(runnables.length); for (Runnable runnable : runnables) { new TestThread(runnable).start(); } latchThreadsReady.await(); } public void testConcurrentInsertDuringTx() throws InterruptedException { Runnable runnable1 = new Runnable() { @Override public void run() { dao.insert(createEntity(null)); } }; Runnable runnable2 = new Runnable() { @Override public void run() { dao.insertInTx(createEntity(null)); } }; Runnable runnable3 = new Runnable() { @Override public void run() { daoSession.runInTx(new Runnable() { @Override public void run() { dao.insert(createEntity(null)); } }); } }; Runnable runnable4 = new Runnable() { @Override public void run() { dao.insertWithoutSettingPk(createEntity(null)); } }; Runnable runnable5 = new Runnable() { @Override public void run() { dao.insertOrReplace(createEntity(null)); } }; initThreads(runnable1, runnable2, runnable3, runnable4, runnable5); // Builds the statement so it is ready immediately in the thread dao.insert(createEntity(null)); doTx(new Runnable() { @Override public void run() { dao.insert(createEntity(null)); } }); latchThreadsDone.await(); assertEquals(7, dao.count()); } public void testConcurrentUpdateDuringTx() throws InterruptedException { final TestEntity entity = createEntity(null); dao.insert(entity); Runnable runnable1 = new Runnable() { @Override public void run() { dao.update(entity); } }; Runnable runnable2 = new Runnable() { @Override public void run() { dao.updateInTx(entity); } }; Runnable runnable3 = new Runnable() { @Override public void run() { daoSession.runInTx(new Runnable() { @Override public void run() { dao.update(entity); } }); } }; initThreads(runnable1, runnable2, runnable3); // Builds the statement so it is ready immediately in the thread dao.update(entity); doTx(new Runnable() { @Override public void run() { dao.update(entity); } }); latchThreadsDone.await(); } public void testConcurrentDeleteDuringTx() throws InterruptedException { final TestEntity entity = createEntity(null); dao.insert(entity); Runnable runnable1 = new Runnable() { @Override public void run() { dao.delete(entity); } }; Runnable runnable2 = new Runnable() { @Override public void run() { dao.deleteInTx(entity); } }; Runnable runnable3 = new Runnable() { @Override public void run() { daoSession.runInTx(new Runnable() { @Override public void run() { dao.delete(entity); } }); } }; initThreads(runnable1, runnable2, runnable3); // Builds the statement so it is ready immediately in the thread dao.delete(entity); doTx(new Runnable() { @Override public void run() { dao.delete(entity); } }); latchThreadsDone.await(); } // Query doesn't involve any statement locking currently, but just to stay on the safe side... public void testConcurrentQueryDuringTx() throws InterruptedException { final TestEntity entity = createEntity(null); dao.insert(entity); final Query<TestEntity> query = dao.queryBuilder().build(); Runnable runnable1 = new Runnable() { @Override public void run() { query.forCurrentThread().list(); } }; initThreads(runnable1); // Builds the statement so it is ready immediately in the thread query.list(); doTx(new Runnable() { @Override public void run() { query.list(); } }); latchThreadsDone.await(); } public void testConcurrentLockAndQueryDuringTx() throws InterruptedException { final TestEntity entity = createEntity(null); dao.insert(entity); final Query<TestEntity> query = dao.queryBuilder().build(); Runnable runnable1 = new Runnable() { @Override public void run() { query.forCurrentThread().list(); } }; initThreads(runnable1); // Builds the statement so it is ready immediately in the thread query.list(); doTx(new Runnable() { @Override public void run() { query.list(); } }); latchThreadsDone.await(); } public void testConcurrentDeleteQueryDuringTx() throws InterruptedException { final TestEntity entity = createEntity(null); dao.insert(entity); final DeleteQuery<TestEntity> query = dao.queryBuilder().buildDelete(); Runnable runnable1 = new Runnable() { @Override public void run() { query.forCurrentThread().executeDeleteWithoutDetachingEntities(); } }; initThreads(runnable1); // Builds the statement so it is ready immediately in the thread query.executeDeleteWithoutDetachingEntities(); doTx(new Runnable() { @Override public void run() { query.executeDeleteWithoutDetachingEntities(); } }); latchThreadsDone.await(); } public void testConcurrentResolveToMany() throws InterruptedException { final ToManyEntity entity = new ToManyEntity(); ToManyEntityDao toManyDao = daoSession.getToManyEntityDao(); toManyDao.insert(entity); Runnable runnable1 = new Runnable() { @Override public void run() { entity.getToManyTargetEntityList(); } }; initThreads(runnable1); doTx(new Runnable() { @Override public void run() { entity.getToManyTargetEntityList(); } }); latchThreadsDone.await(); } public void testConcurrentResolveToOne() throws InterruptedException { final TreeEntity entity = new TreeEntity(); TreeEntityDao toOneDao = daoSession.getTreeEntityDao(); toOneDao.insert(entity); Runnable runnable1 = new Runnable() { @Override public void run() { entity.getParent(); } }; initThreads(runnable1); doTx(new Runnable() { @Override public void run() { entity.getParent(); } }); latchThreadsDone.await(); } /** * We could put the statements inside ThreadLocals (fast enough), but it comes with initialization penalty for new * threads and costs more memory. */ public void _testThreadLocalSpeed() { final Database db = dao.getDatabase(); ThreadLocal<DatabaseStatement> threadLocal = new ThreadLocal<DatabaseStatement>() { @Override protected DatabaseStatement initialValue() { return db.compileStatement("SELECT 42"); } }; threadLocal.get(); long start = SystemClock.currentThreadTimeMillis(); for (int i = 0; i < 1000; i++) { DatabaseStatement sqLiteStatement = threadLocal.get(); assertNotNull(sqLiteStatement); } Long time = SystemClock.currentThreadTimeMillis() - start; DaoLog.d("TIME: " + time + "ms"); // Around 1ms on a S3 assertTrue(time < 10); } protected void doTx(final Runnable runnableInsideTx) { daoSession.runInTx(new Runnable() { @Override public void run() { latchInsideTx.countDown(); // Give the concurrent thread time so it will try to acquire locks try { Thread.sleep(TIME_TO_WAIT_FOR_THREAD); } catch (InterruptedException e) { throw new RuntimeException(e); } runnableInsideTx.run(); } }); } protected TestEntity createEntity(Long key) { TestEntity entity = new TestEntity(key); entity.setSimpleStringNotNull("green"); return entity; } }