/**
* This file is part of ObjectFabric (http://objectfabric.org).
*
* ObjectFabric is licensed under the Apache License, Version 2.0, the terms
* of which may be found at http://www.apache.org/licenses/LICENSE-2.0.html.
*
* Copyright ObjectFabric Inc.
*
* This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
* WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
package org.objectfabric;
import java.util.HashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import com.almworks.sqlite4java.SQLiteBusyException;
import com.almworks.sqlite4java.SQLiteConnection;
import com.almworks.sqlite4java.SQLiteException;
final class SQLiteLoop {
private static final int MAX_BATCH = 100; // TODO tune
static abstract class Query {
int statements() {
return 1;
}
abstract void run(SQLiteConnection db) throws SQLiteException;
void ack() {
}
}
private final SQLite _location;
private final Run[] _runs;
private final LinkedBlockingQueue<Query> _queue = new LinkedBlockingQueue<Query>();
private final AtomicInteger _ongoing;
private final HashMap<Peer, Peer> _walks;
private volatile boolean _running = true;
SQLiteLoop(SQLite location, int threads, boolean count) {
_location = location;
_runs = new Run[threads];
for (int i = 0; i < threads; i++) {
_runs[i] = new Run();
_runs[i].start();
}
if (count) {
_ongoing = new AtomicInteger();
_walks = new HashMap<Peer, Peer>();
} else {
_ongoing = null;
_walks = null;
}
}
final void close() {
_running = false;
for (int i = 0; i < _runs.length; i++) {
_runs[i].interrupt();
try {
_runs[i].join();
} catch (InterruptedException e) {
}
}
}
final int room() {
return MAX_BATCH - _ongoing.get();
}
final void add(Query query) {
_queue.offer(query);
if (_ongoing != null)
_ongoing.addAndGet(query.statements());
}
final HashMap<Peer, Peer> walks() {
return _walks;
}
private final class Run extends Thread {
Run() {
setName("SQLiteQueue");
setDaemon(true);
}
@Override
public void run() {
SQLiteConnection db = new SQLiteConnection(_location.file());
List<Query> toAck = new List<Query>();
try {
db.open(true);
for (;;) {
try {
db.exec(Shared.INIT);
break;
} catch (SQLiteBusyException e) {
Thread.sleep(1);
}
}
while (_running) {
Query query = _queue.take();
if (_ongoing == null)
query.run(db);
else {
if (_walks.size() == 0) {
/*
* Immediate because clock adds reads at beginning of
* transaction, which can lead to deadlock if done from
* multiple processes.
*/
db.exec("BEGIN IMMEDIATE");
if (Debug.ENABLED)
Debug.assertion(toAck.size() == 0);
}
while (query != null) {
query.run(db);
toAck.add(query);
query = _queue.poll();
}
if (_walks.size() == 0) {
for (;;) {
try {
db.exec("COMMIT");
break;
} catch (SQLiteBusyException e) {
Thread.sleep(1);
}
}
int count = 0;
for (int i = 0; i < toAck.size(); i++)
count += toAck.get(i).statements();
_ongoing.addAndGet(-count);
for (int i = 0; i < toAck.size(); i++)
toAck.get(i).ack();
toAck.clear();
}
}
}
} catch (Exception e) {
if (!(e instanceof InterruptedException))
Log.write(e);
} finally {
db.dispose();
}
}
}
}