diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java index 042a2a2ed95f16f422a889b4212ca6863cc7aa73..f6e679a0ac6980e6659c9d202ab5e99328d9b211 100644 --- a/components/net/sf/briar/db/Database.java +++ b/components/net/sf/briar/db/Database.java @@ -264,8 +264,7 @@ interface Database<T> { Rating getRating(T txn, AuthorId a) throws DbException; /** - * Returns the sendability score of the given message. Group messages with - * sendability scores greater than zero are eligible to be sent to contacts. + * Returns the sendability score of the given group message. * <p> * Locking: messages read. */ diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java index ccb162b80cd9ed90e0ca4be8cc6e7ac026e3938f..66fe5dc1c841a957a50291a83275780f00e21f3b 100644 --- a/components/net/sf/briar/db/DatabaseComponentImpl.java +++ b/components/net/sf/briar/db/DatabaseComponentImpl.java @@ -1,34 +1,80 @@ package net.sf.briar.db; +import java.io.IOException; import java.util.ArrayList; +import java.util.BitSet; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; import java.util.logging.Logger; +import com.google.inject.Inject; + import net.sf.briar.api.ContactId; import net.sf.briar.api.Rating; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DatabaseListener; import net.sf.briar.api.db.DbException; +import net.sf.briar.api.db.NoSuchContactException; import net.sf.briar.api.db.Status; +import net.sf.briar.api.db.DatabaseListener.Event; +import net.sf.briar.api.protocol.Ack; import net.sf.briar.api.protocol.AuthorId; +import net.sf.briar.api.protocol.Batch; +import net.sf.briar.api.protocol.BatchId; +import net.sf.briar.api.protocol.Group; +import net.sf.briar.api.protocol.GroupId; import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageId; +import net.sf.briar.api.protocol.Offer; +import net.sf.briar.api.protocol.SubscriptionUpdate; +import net.sf.briar.api.protocol.TransportUpdate; +import net.sf.briar.api.protocol.writers.AckWriter; +import net.sf.briar.api.protocol.writers.BatchWriter; +import net.sf.briar.api.protocol.writers.OfferWriter; +import net.sf.briar.api.protocol.writers.RequestWriter; +import net.sf.briar.api.protocol.writers.SubscriptionWriter; +import net.sf.briar.api.protocol.writers.TransportWriter; +import net.sf.briar.api.transport.ConnectionWindow; /** - * Abstract superclass containing code shared by ReadWriteLockDatabaseComponent - * and SynchronizedDatabaseComponent. + * An implementation of DatabaseComponent using reentrant read-write locks. + * Depending on the JVM's lock implementation, this implementation may allow + * writers to starve. LockFairnessTest can be used to test whether this + * implementation is safe on a given JVM. */ -abstract class DatabaseComponentImpl<T> implements DatabaseComponent, +class DatabaseComponentImpl<T> implements DatabaseComponent, DatabaseCleaner.Callback { private static final Logger LOG = Logger.getLogger(DatabaseComponentImpl.class.getName()); - protected final Database<T> db; - protected final DatabaseCleaner cleaner; + /* + * Locks must always be acquired in alphabetical order. See the Database + * interface to find out which calls require which locks. + */ + + private final ReentrantReadWriteLock contactLock = + new ReentrantReadWriteLock(true); + private final ReentrantReadWriteLock messageLock = + new ReentrantReadWriteLock(true); + private final ReentrantReadWriteLock messageStatusLock = + new ReentrantReadWriteLock(true); + private final ReentrantReadWriteLock ratingLock = + new ReentrantReadWriteLock(true); + private final ReentrantReadWriteLock subscriptionLock = + new ReentrantReadWriteLock(true); + private final ReentrantReadWriteLock transportLock = + new ReentrantReadWriteLock(true); + private final ReentrantReadWriteLock windowLock = + new ReentrantReadWriteLock(true); + + private final Database<T> db; + private final DatabaseCleaner cleaner; private final List<DatabaseListener> listeners = new ArrayList<DatabaseListener>(); // Locking: self @@ -38,6 +84,7 @@ DatabaseCleaner.Callback { private long timeOfLastCheck = 0L; // Locking: spaceLock private volatile boolean writesAllowed = true; + @Inject DatabaseComponentImpl(Database<T> db, DatabaseCleaner cleaner) { this.db = db; this.cleaner = cleaner; @@ -48,6 +95,11 @@ DatabaseCleaner.Callback { cleaner.startCleaning(); } + public void close() throws DbException { + cleaner.stopCleaning(); + db.close(); + } + public void addListener(DatabaseListener d) { synchronized(listeners) { listeners.add(d); @@ -60,31 +112,40 @@ DatabaseCleaner.Callback { } } - /** - * Removes the oldest messages from the database, with a total size less - * than or equal to the given size. - */ - protected abstract void expireMessages(int size) throws DbException; - - /** - * Calculates and returns the sendability score of a message. - * <p> - * Locking: messages write. - */ - private int calculateSendability(T txn, Message m) throws DbException { - int sendability = 0; - // One point for a good rating - if(db.getRating(txn, m.getAuthor()) == Rating.GOOD) sendability++; - // One point per sendable child (backward inclusion) - sendability += db.getNumberOfSendableChildren(txn, m.getId()); - return sendability; + public ContactId addContact(Map<String, Map<String, String>> transports, + byte[] secret) throws DbException { + if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact"); + ContactId c; + contactLock.writeLock().lock(); + try { + transportLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + c = db.addContact(txn, transports, secret); + db.commitTransaction(txn); + if(LOG.isLoggable(Level.FINE)) + LOG.fine("Added contact " + c); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.writeLock().unlock(); + } + } finally { + contactLock.writeLock().unlock(); + } + // Call the listeners outside the lock + callListeners(Event.CONTACTS_UPDATED); + return c; } - /** Notifies all MessageListeners that new messages may be available. */ - protected void callListeners(DatabaseListener.Event e) { + /** Notifies all listeners of a database event. */ + private void callListeners(DatabaseListener.Event e) { synchronized(listeners) { if(!listeners.isEmpty()) { - // Shuffle the listeners so we don't always send new packets + // Shuffle the listeners so we don't always send new messages // to contacts in the same order Collections.shuffle(listeners); for(DatabaseListener d : listeners) d.eventOccurred(e); @@ -92,80 +153,62 @@ DatabaseCleaner.Callback { } } - public void checkFreeSpaceAndClean() throws DbException { - long freeSpace = db.getFreeSpace(); - while(freeSpace < MIN_FREE_SPACE) { - // If disk space is critical, disable the storage of new messages - if(freeSpace < CRITICAL_FREE_SPACE) { - if(LOG.isLoggable(Level.FINE)) LOG.fine("Critical cleanup"); - writesAllowed = false; - } else { - if(LOG.isLoggable(Level.FINE)) LOG.fine("Normal cleanup"); - } - expireMessages(BYTES_PER_SWEEP); - Thread.yield(); - freeSpace = db.getFreeSpace(); - // If disk space is no longer critical, re-enable writes - if(freeSpace >= CRITICAL_FREE_SPACE && !writesAllowed) { - writesAllowed = true; - synchronized(writeLock) { - writeLock.notifyAll(); + public void addLocalGroupMessage(Message m) throws DbException { + boolean added = false; + waitForPermissionToWrite(); + contactLock.readLock().lock(); + try { + messageLock.writeLock().lock(); + try { + messageStatusLock.writeLock().lock(); + try { + subscriptionLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + // Don't store the message if the user has + // unsubscribed from the group or the message + // predates the subscription + if(db.containsSubscription(txn, m.getGroup(), + m.getTimestamp())) { + added = storeGroupMessage(txn, m, null); + } + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.readLock().unlock(); + } + } finally { + messageStatusLock.writeLock().unlock(); } + } finally { + messageLock.writeLock().unlock(); } + } finally { + contactLock.readLock().unlock(); } + // Call the listeners outside the lock + if(added) callListeners(Event.MESSAGES_ADDED); } /** - * Returns true if the database contains the given contact. - * <p> - * Locking: contacts read. - */ - protected boolean containsContact(ContactId c) throws DbException { - T txn = db.startTransaction(); - try { - boolean contains = db.containsContact(txn, c); - db.commitTransaction(txn); - return contains; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } - - /** - * Removes the given message (and all associated state) from the database. - * <p> - * Locking: contacts read, messages write, messageStatuses write. + * Blocks until messages are allowed to be stored in the database. The + * storage of messages is not allowed while the amount of free storage + * space available to the database is less than CRITICAL_FREE_SPACE. */ - protected void removeMessage(T txn, MessageId id) throws DbException { - Integer sendability = db.getSendability(txn, id); - assert sendability != null; - // If the message is sendable, deleting it may affect its ancestors' - // sendability (backward inclusion) - if(sendability > 0) updateAncestorSendability(txn, id, false); - db.removeMessage(txn, id); - } - - public boolean shouldCheckFreeSpace() { - synchronized(spaceLock) { - long now = System.currentTimeMillis(); - if(bytesStoredSinceLastCheck > MAX_BYTES_BETWEEN_SPACE_CHECKS) { - if(LOG.isLoggable(Level.FINE)) - LOG.fine(bytesStoredSinceLastCheck - + " bytes stored since last check"); - bytesStoredSinceLastCheck = 0L; - timeOfLastCheck = now; - return true; - } - if(now - timeOfLastCheck > MAX_MS_BETWEEN_SPACE_CHECKS) { + private void waitForPermissionToWrite() { + synchronized(writeLock) { + while(!writesAllowed) { if(LOG.isLoggable(Level.FINE)) - LOG.fine((now - timeOfLastCheck) + " ms since last check"); - bytesStoredSinceLastCheck = 0L; - timeOfLastCheck = now; - return true; + LOG.fine("Waiting for permission to write"); + try { + writeLock.wait(); + } catch(InterruptedException ignored) {} } } - return false; } /** @@ -176,7 +219,7 @@ DatabaseCleaner.Callback { * <p> * Locking: contacts read, messages write, messageStatuses write. */ - protected boolean storeGroupMessage(T txn, Message m, ContactId sender) + private boolean storeGroupMessage(T txn, Message m, ContactId sender) throws DbException { if(m.getGroup() == null) throw new IllegalArgumentException(); boolean stored = db.addGroupMessage(txn, m); @@ -201,46 +244,20 @@ DatabaseCleaner.Callback { } /** - * Attempts to store the given messages, received from the given contact, - * and returns true if any were stored. - */ - protected boolean storeMessages(T txn, ContactId c, - Collection<Message> messages) throws DbException { - boolean anyStored = false; - for(Message m : messages) { - if(m.getGroup() == null) { - if(storePrivateMessage(txn, m, c, true)) anyStored = true; - } else if(db.containsVisibleSubscription(txn, m.getGroup(), c, - m.getTimestamp())) { - if(storeGroupMessage(txn, m, c)) anyStored = true; - } - } - return anyStored; - } - - /** - * If the given message is already in the database, returns false. - * Otherwise stores the message and marks it as new or seen with respect to - * the given contact, depending on whether the message is outgoing or - * incoming, respectively. + * Calculates and returns the sendability score of a message. * <p> - * Locking: contacts read, messages write, messageStatuses write. + * Locking: messages write. */ - protected boolean storePrivateMessage(T txn, Message m, ContactId c, - boolean incoming) throws DbException { - if(m.getGroup() != null) throw new IllegalArgumentException(); - if(m.getAuthor() != null) throw new IllegalArgumentException(); - if(!db.addPrivateMessage(txn, m, c)) return false; - MessageId id = m.getId(); - if(incoming) db.setStatus(txn, c, id, Status.SEEN); - else db.setStatus(txn, c, id, Status.NEW); - // Count the bytes stored - synchronized(spaceLock) { - bytesStoredSinceLastCheck += m.getSize(); - } - return true; + private int calculateSendability(T txn, Message m) throws DbException { + int sendability = 0; + // One point for a good rating + if(db.getRating(txn, m.getAuthor()) == Rating.GOOD) sendability++; + // One point per sendable child (backward inclusion) + sendability += db.getNumberOfSendableChildren(txn, m.getId()); + return sendability; } + /** * Iteratively updates the sendability of a message's ancestors to reflect * a change in the message's sendability. Returns the number of ancestors @@ -250,8 +267,8 @@ DatabaseCleaner.Callback { * @param increment True if the message's sendability has changed from 0 to * greater than 0, or false if it has changed from greater than 0 to 0. */ - private int updateAncestorSendability(T txn, MessageId m, - boolean increment) throws DbException { + private int updateAncestorSendability(T txn, MessageId m, boolean increment) + throws DbException { int affected = 0; boolean changed = true; while(changed) { @@ -277,6 +294,889 @@ DatabaseCleaner.Callback { return affected; } + public void addLocalPrivateMessage(Message m, ContactId c) + throws DbException { + boolean added = false; + waitForPermissionToWrite(); + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + messageLock.writeLock().lock(); + try { + messageStatusLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + added = storePrivateMessage(txn, m, c, false); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + messageStatusLock.writeLock().unlock(); + } + } finally { + messageLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + // Call the listeners outside the lock + if(added) callListeners(Event.MESSAGES_ADDED); + } + + /** + * If the given message is already in the database, returns false. + * Otherwise stores the message and marks it as new or seen with respect to + * the given contact, depending on whether the message is outgoing or + * incoming, respectively. + * <p> + * Locking: contacts read, messages write, messageStatuses write. + */ + private boolean storePrivateMessage(T txn, Message m, ContactId c, + boolean incoming) throws DbException { + if(m.getGroup() != null) throw new IllegalArgumentException(); + if(m.getAuthor() != null) throw new IllegalArgumentException(); + if(!db.addPrivateMessage(txn, m, c)) return false; + MessageId id = m.getId(); + if(incoming) db.setStatus(txn, c, id, Status.SEEN); + else db.setStatus(txn, c, id, Status.NEW); + // Count the bytes stored + synchronized(spaceLock) { + bytesStoredSinceLastCheck += m.getSize(); + } + return true; + } + + /** + * Returns true if the database contains the given contact. + * <p> + * Locking: contacts read. + */ + private boolean containsContact(ContactId c) throws DbException { + T txn = db.startTransaction(); + try { + boolean contains = db.containsContact(txn, c); + db.commitTransaction(txn); + return contains; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } + + public void findLostBatches(ContactId c) throws DbException { + // Find any lost batches that need to be retransmitted + Collection<BatchId> lost; + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + messageLock.readLock().lock(); + try { + messageStatusLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + lost = db.getLostBatches(txn, c); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + messageStatusLock.writeLock().unlock(); + } + } finally { + messageLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + for(BatchId batch : lost) { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + messageLock.readLock().lock(); + try { + messageStatusLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(LOG.isLoggable(Level.FINE)) + LOG.fine("Removing lost batch"); + db.removeLostBatch(txn, c, batch); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + messageStatusLock.writeLock().unlock(); + } + } finally { + messageLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + } + + public void generateAck(ContactId c, AckWriter a) throws DbException, + IOException { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + messageStatusLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + Collection<BatchId> acks = db.getBatchesToAck(txn, c); + Collection<BatchId> sent = new ArrayList<BatchId>(); + for(BatchId b : acks) if(a.writeBatchId(b)) sent.add(b); + a.finish(); + db.removeBatchesToAck(txn, c, sent); + if(LOG.isLoggable(Level.FINE)) + LOG.fine("Added " + acks.size() + " acks"); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } catch(IOException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + messageStatusLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public void generateBatch(ContactId c, BatchWriter b) throws DbException, + IOException { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + messageLock.readLock().lock(); + try { + Collection<MessageId> sent; + int bytesSent = 0; + messageStatusLock.readLock().lock(); + try { + subscriptionLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + sent = new ArrayList<MessageId>(); + int capacity = b.getCapacity(); + Collection<MessageId> sendable = + db.getSendableMessages(txn, c, capacity); + for(MessageId m : sendable) { + byte[] raw = db.getMessage(txn, m); + if(!b.writeMessage(raw)) break; + bytesSent += raw.length; + sent.add(m); + } + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } catch(IOException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.readLock().unlock(); + } + } finally { + messageStatusLock.readLock().unlock(); + } + // Record the contents of the batch, unless it's empty + if(sent.isEmpty()) return; + BatchId id = b.finish(); + messageStatusLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + db.addOutstandingBatch(txn, c, id, sent); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + messageStatusLock.writeLock().unlock(); + } + } finally { + messageLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public Collection<MessageId> generateBatch(ContactId c, BatchWriter b, + Collection<MessageId> requested) throws DbException, IOException { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + messageLock.readLock().lock(); + try { + Collection<MessageId> sent, considered; + messageStatusLock.readLock().lock(); + try{ + subscriptionLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + sent = new ArrayList<MessageId>(); + considered = new ArrayList<MessageId>(); + int bytesSent = 0; + for(MessageId m : requested) { + byte[] raw = db.getMessageIfSendable(txn, c, m); + // If the message is still sendable, try to add + // it to the batch. If the batch is full, don't + // treat the message as considered, and don't + // try to add any further messages. + if(raw != null) { + if(!b.writeMessage(raw)) break; + bytesSent += raw.length; + sent.add(m); + } + considered.add(m); + } + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } catch(IOException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.readLock().unlock(); + } + } finally { + messageStatusLock.readLock().unlock(); + } + // Record the contents of the batch, unless it's empty + if(sent.isEmpty()) return considered; + BatchId id = b.finish(); + messageStatusLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + db.addOutstandingBatch(txn, c, id, sent); + db.commitTransaction(txn); + return considered; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + messageStatusLock.writeLock().unlock(); + } + } finally { + messageLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public Collection<MessageId> generateOffer(ContactId c, OfferWriter o) + throws DbException, IOException { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + messageLock.readLock().lock(); + try { + messageStatusLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + Collection<MessageId> sendable = + db.getSendableMessages(txn, c, Integer.MAX_VALUE); + Iterator<MessageId> it = sendable.iterator(); + Collection<MessageId> sent = new ArrayList<MessageId>(); + while(it.hasNext()) { + MessageId m = it.next(); + if(!o.writeMessageId(m)) break; + sent.add(m); + } + o.finish(); + db.commitTransaction(txn); + return sent; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } catch(IOException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + messageStatusLock.readLock().unlock(); + } + } finally { + messageLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public void generateSubscriptionUpdate(ContactId c, SubscriptionWriter s) + throws DbException, IOException { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + subscriptionLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + Map<Group, Long> subs = db.getVisibleSubscriptions(txn, c); + s.writeSubscriptions(subs, System.currentTimeMillis()); + if(LOG.isLoggable(Level.FINE)) + LOG.fine("Added " + subs.size() + " subscriptions"); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } catch(IOException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public void generateTransportUpdate(ContactId c, TransportWriter t) + throws DbException, IOException { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + transportLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + Map<String, Map<String, String>> transports = + db.getTransports(txn); + t.writeTransports(transports, System.currentTimeMillis()); + if(LOG.isLoggable(Level.FINE)) + LOG.fine("Added " + transports.size() + " transports"); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } catch(IOException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public ConnectionWindow getConnectionWindow(ContactId c, int transportId) + throws DbException { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + windowLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + ConnectionWindow w = + db.getConnectionWindow(txn, c, transportId); + db.commitTransaction(txn); + return w; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + windowLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public Collection<ContactId> getContacts() throws DbException { + contactLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + Collection<ContactId> contacts = db.getContacts(txn); + db.commitTransaction(txn); + return contacts; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + contactLock.readLock().unlock(); + } + } + + public Rating getRating(AuthorId a) throws DbException { + ratingLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + Rating r = db.getRating(txn, a); + db.commitTransaction(txn); + return r; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + ratingLock.readLock().unlock(); + } + } + + public byte[] getSharedSecret(ContactId c) throws DbException { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + T txn = db.startTransaction(); + try { + byte[] secret = db.getSharedSecret(txn, c); + db.commitTransaction(txn); + return secret; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + contactLock.readLock().unlock(); + } + } + + public Collection<Group> getSubscriptions() throws DbException { + subscriptionLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + Collection<Group> subs = db.getSubscriptions(txn); + db.commitTransaction(txn); + return subs; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.readLock().unlock(); + } + } + + public Map<String, String> getTransportConfig(String name) + throws DbException { + transportLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + Map<String, String> config = db.getTransportConfig(txn, name); + db.commitTransaction(txn); + return config; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.readLock().unlock(); + } + } + + public Map<String, Map<String, String>> getTransports() throws DbException { + transportLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + Map<String, Map<String, String>> transports = + db.getTransports(txn); + db.commitTransaction(txn); + return transports; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.readLock().unlock(); + } + } + + public Map<String, Map<String, String>> getTransports(ContactId c) + throws DbException { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + transportLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + Map<String, Map<String, String>> transports = + db.getTransports(txn, c); + db.commitTransaction(txn); + return transports; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public Collection<ContactId> getVisibility(GroupId g) throws DbException { + contactLock.readLock().lock(); + try { + subscriptionLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + Collection<ContactId> visible = db.getVisibility(txn, g); + db.commitTransaction(txn); + return visible; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public boolean hasSendableMessages(ContactId c) throws DbException { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + messageLock.readLock().lock(); + try { + messageStatusLock.readLock().lock(); + try { + subscriptionLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + boolean has = db.hasSendableMessages(txn, c); + db.commitTransaction(txn); + return has; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.readLock().unlock(); + } + } finally { + messageStatusLock.readLock().unlock(); + } + } finally { + messageLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public void receiveAck(ContactId c, Ack a) throws DbException { + // Mark all messages in acked batches as seen + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + messageLock.readLock().lock(); + try { + messageStatusLock.writeLock().lock(); + try { + Collection<BatchId> acks = a.getBatchIds(); + for(BatchId ack : acks) { + T txn = db.startTransaction(); + try { + db.removeAckedBatch(txn, c, ack); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } + if(LOG.isLoggable(Level.FINE)) + LOG.fine("Received " + acks.size() + " acks"); + } finally { + messageStatusLock.writeLock().unlock(); + } + } finally { + messageLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public void receiveBatch(ContactId c, Batch b) throws DbException { + boolean anyAdded = false; + waitForPermissionToWrite(); + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + messageLock.writeLock().lock(); + try { + messageStatusLock.writeLock().lock(); + try { + subscriptionLock.readLock().lock(); + try { + T txn = db.startTransaction(); + try { + anyAdded = storeMessages(txn, c, b.getMessages()); + db.addBatchToAck(txn, c, b.getId()); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.readLock().unlock(); + } + } finally { + messageStatusLock.writeLock().unlock(); + } + } finally { + messageLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + // Call the listeners outside the lock + if(anyAdded) callListeners(Event.MESSAGES_ADDED); + } + + /** + * Attempts to store a collection of messages received from the given + * contact, and returns true if any were stored. + * <p> + * Locking: contacts read, messages write, messageStatuses write, + * subscriptions read. + */ + private boolean storeMessages(T txn, ContactId c, + Collection<Message> messages) throws DbException { + boolean anyStored = false; + for(Message m : messages) { + GroupId g = m.getGroup(); + if(g == null) { + if(storePrivateMessage(txn, m, c, true)) anyStored = true; + } else { + long timestamp = m.getTimestamp(); + if(db.containsVisibleSubscription(txn, g, c, timestamp)) { + if(storeGroupMessage(txn, m, c)) anyStored = true; + } + } + } + return anyStored; + } + + public void receiveOffer(ContactId c, Offer o, RequestWriter r) + throws DbException, IOException { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + messageLock.readLock().lock(); + try { + messageStatusLock.writeLock().lock(); + try { + subscriptionLock.readLock().lock(); + try { + Collection<MessageId> offered = o.getMessageIds(); + BitSet request = new BitSet(offered.size()); + T txn = db.startTransaction(); + try { + Iterator<MessageId> it = offered.iterator(); + for(int i = 0; it.hasNext(); i++) { + // If the message is not in the database, or if + // it is not visible to the contact, request it + MessageId m = it.next(); + if(!db.setStatusSeenIfVisible(txn, c, m)) + request.set(i); + } + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + r.writeRequest(request, offered.size()); + } finally { + subscriptionLock.readLock().unlock(); + } + } finally { + messageStatusLock.writeLock().unlock(); + } + } finally { + messageLock.readLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public void receiveSubscriptionUpdate(ContactId c, SubscriptionUpdate s) + throws DbException { + // Update the contact's subscriptions + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + subscriptionLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + Map<Group, Long> subs = s.getSubscriptions(); + db.setSubscriptions(txn, c, subs, s.getTimestamp()); + if(LOG.isLoggable(Level.FINE)) + LOG.fine("Received " + subs.size() + " subscriptions"); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public void receiveTransportUpdate(ContactId c, TransportUpdate t) + throws DbException { + // Update the contact's transport properties + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + transportLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + Map<String, Map<String, String>> transports = + t.getTransports(); + db.setTransports(txn, c, transports, t.getTimestamp()); + if(LOG.isLoggable(Level.FINE)) + LOG.fine("Received " + transports.size() + + " transports"); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public void removeContact(ContactId c) throws DbException { + if(LOG.isLoggable(Level.FINE)) LOG.fine("Removing contact " + c); + contactLock.writeLock().lock(); + try { + messageLock.writeLock().lock(); + try { + messageStatusLock.writeLock().lock(); + try { + subscriptionLock.writeLock().lock(); + try { + transportLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + db.removeContact(txn, c); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.writeLock().unlock(); + } + } finally { + subscriptionLock.writeLock().unlock(); + } + } finally { + messageStatusLock.writeLock().unlock(); + } + } finally { + messageLock.writeLock().unlock(); + } + } finally { + contactLock.writeLock().unlock(); + } + // Call the listeners outside the lock + callListeners(Event.CONTACTS_UPDATED); + } + + public void setConnectionWindow(ContactId c, int transportId, + ConnectionWindow w) throws DbException { + contactLock.readLock().lock(); + try { + if(!containsContact(c)) throw new NoSuchContactException(); + windowLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + db.setConnectionWindow(txn, c, transportId, w); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + } + } finally { + windowLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public void setRating(AuthorId a, Rating r) throws DbException { + messageLock.writeLock().lock(); + try { + ratingLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + Rating old = db.setRating(txn, a, r); + // Update the sendability of the author's messages + if(r == Rating.GOOD && old != Rating.GOOD) + updateAuthorSendability(txn, a, true); + else if(r != Rating.GOOD && old == Rating.GOOD) + updateAuthorSendability(txn, a, false); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + ratingLock.writeLock().unlock(); + } + } finally { + messageLock.writeLock().unlock(); + } + } + /** * Updates the sendability of all messages written by the given author, and * the ancestors of those messages if necessary. @@ -285,8 +1185,8 @@ DatabaseCleaner.Callback { * @param increment True if the user's rating for the author has changed * from not good to good, or false if it has changed from good to not good. */ - protected void updateAuthorSendability(T txn, AuthorId a, - boolean increment) throws DbException { + private void updateAuthorSendability(T txn, AuthorId a, boolean increment) + throws DbException { int direct = 0, indirect = 0; for(MessageId id : db.getMessagesByAuthor(txn, a)) { int sendability = db.getSendability(txn, id); @@ -310,20 +1210,226 @@ DatabaseCleaner.Callback { + indirect + " indirectly"); } + public void setTransportConfig(String name, + Map<String, String> config) throws DbException { + boolean changed = false; + transportLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + Map<String, String> old = db.getTransportConfig(txn, name); + if(!config.equals(old)) { + db.setTransportConfig(txn, name, config); + changed = true; + } + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.writeLock().unlock(); + } + // Call the listeners outside the lock + if(changed) callListeners(Event.TRANSPORTS_UPDATED); + } + + public void setTransportProperties(String name, + Map<String, String> properties) throws DbException { + boolean changed = false; + transportLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + Map<String, String> old = db.getTransports(txn).get(name); + if(!properties.equals(old)) { + db.setTransportProperties(txn, name, properties); + changed = true; + } + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + transportLock.writeLock().unlock(); + } + // Call the listeners outside the lock + if(changed) callListeners(Event.TRANSPORTS_UPDATED); + } + + public void setVisibility(GroupId g, Collection<ContactId> visible) + throws DbException { + contactLock.readLock().lock(); + try { + subscriptionLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + // Remove any ex-contacts from the set + Collection<ContactId> present = + new ArrayList<ContactId>(visible.size()); + for(ContactId c : visible) { + if(db.containsContact(txn, c)) present.add(c); + } + db.setVisibility(txn, g, present); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + + public void subscribe(Group g) throws DbException { + if(LOG.isLoggable(Level.FINE)) LOG.fine("Subscribing to " + g); + boolean added = false; + subscriptionLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(db.containsSubscription(txn, g.getId())) { + db.addSubscription(txn, g); + added = true; + } + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.writeLock().unlock(); + } + // Call the listeners outside the lock + if(added) callListeners(Event.SUBSCRIPTIONS_UPDATED); + } + + public void unsubscribe(GroupId g) throws DbException { + if(LOG.isLoggable(Level.FINE)) LOG.fine("Unsubscribing from " + g); + boolean removed = false; + contactLock.readLock().lock(); + try { + messageLock.writeLock().lock(); + try { + messageStatusLock.writeLock().lock(); + try { + subscriptionLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(db.containsSubscription(txn, g)) { + db.removeSubscription(txn, g); + removed = true; + } + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + subscriptionLock.writeLock().unlock(); + } + } finally { + messageStatusLock.writeLock().unlock(); + } + } finally { + messageLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + // Call the listeners outside the lock + if(removed) callListeners(Event.SUBSCRIPTIONS_UPDATED); + } + + public void checkFreeSpaceAndClean() throws DbException { + long freeSpace = db.getFreeSpace(); + while(freeSpace < MIN_FREE_SPACE) { + // If disk space is critical, disable the storage of new messages + if(freeSpace < CRITICAL_FREE_SPACE) { + if(LOG.isLoggable(Level.FINE)) LOG.fine("Critical cleanup"); + writesAllowed = false; + } else { + if(LOG.isLoggable(Level.FINE)) LOG.fine("Normal cleanup"); + } + expireMessages(BYTES_PER_SWEEP); + Thread.yield(); + freeSpace = db.getFreeSpace(); + // If disk space is no longer critical, re-enable writes + if(freeSpace >= CRITICAL_FREE_SPACE && !writesAllowed) { + writesAllowed = true; + synchronized(writeLock) { + writeLock.notifyAll(); + } + } + } + } + + private void expireMessages(int size) throws DbException { + contactLock.readLock().lock(); + try { + messageLock.writeLock().lock(); + try { + messageStatusLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + for(MessageId m : db.getOldMessages(txn, size)) { + removeMessage(txn, m); + } + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + messageStatusLock.writeLock().unlock(); + } + } finally { + messageLock.writeLock().unlock(); + } + } finally { + contactLock.readLock().unlock(); + } + } + /** - * Blocks until messages are allowed to be stored in the database. The - * storage of messages is not allowed while the amount of free storage - * space available to the database is less than CRITICAL_FREE_SPACE. + * Removes the given message (and all associated state) from the database. + * <p> + * Locking: contacts read, messages write, messageStatuses write. */ - protected void waitForPermissionToWrite() { - synchronized(writeLock) { - while(!writesAllowed) { + private void removeMessage(T txn, MessageId m) throws DbException { + int sendability = db.getSendability(txn, m); + // If the message is sendable, deleting it may affect its ancestors' + // sendability (backward inclusion) + if(sendability > 0) updateAncestorSendability(txn, m, false); + db.removeMessage(txn, m); + } + + public boolean shouldCheckFreeSpace() { + synchronized(spaceLock) { + long now = System.currentTimeMillis(); + if(bytesStoredSinceLastCheck > MAX_BYTES_BETWEEN_SPACE_CHECKS) { if(LOG.isLoggable(Level.FINE)) - LOG.fine("Waiting for permission to write"); - try { - writeLock.wait(); - } catch(InterruptedException ignored) {} + LOG.fine(bytesStoredSinceLastCheck + + " bytes stored since last check"); + bytesStoredSinceLastCheck = 0L; + timeOfLastCheck = now; + return true; + } + if(now - timeOfLastCheck > MAX_MS_BETWEEN_SPACE_CHECKS) { + if(LOG.isLoggable(Level.FINE)) + LOG.fine((now - timeOfLastCheck) + " ms since last check"); + bytesStoredSinceLastCheck = 0L; + timeOfLastCheck = now; + return true; } } + return false; } } diff --git a/components/net/sf/briar/db/DatabaseModule.java b/components/net/sf/briar/db/DatabaseModule.java index 75afcd0c6c5812f5a504930893096c55e59b6a7c..693921d418aa934604d77095efa74e111eb9ca74 100644 --- a/components/net/sf/briar/db/DatabaseModule.java +++ b/components/net/sf/briar/db/DatabaseModule.java @@ -12,8 +12,8 @@ public class DatabaseModule extends AbstractModule { @Override protected void configure() { bind(Database.class).to(H2Database.class); - bind(DatabaseComponent.class).to( - ReadWriteLockDatabaseComponent.class).in(Singleton.class); + bind(DatabaseComponent.class).to(DatabaseComponentImpl.class).in( + Singleton.class); bind(Password.class).annotatedWith(DatabasePassword.class).toInstance( new Password() { public char[] getPassword() { diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java deleted file mode 100644 index 2800184cda340de0487c3f5ab6d9e402275f446e..0000000000000000000000000000000000000000 --- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java +++ /dev/null @@ -1,1133 +0,0 @@ -package net.sf.briar.db; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.logging.Level; -import java.util.logging.Logger; - -import net.sf.briar.api.ContactId; -import net.sf.briar.api.Rating; -import net.sf.briar.api.db.DatabaseListener.Event; -import net.sf.briar.api.db.DbException; -import net.sf.briar.api.db.NoSuchContactException; -import net.sf.briar.api.protocol.Ack; -import net.sf.briar.api.protocol.AuthorId; -import net.sf.briar.api.protocol.Batch; -import net.sf.briar.api.protocol.BatchId; -import net.sf.briar.api.protocol.Group; -import net.sf.briar.api.protocol.GroupId; -import net.sf.briar.api.protocol.Message; -import net.sf.briar.api.protocol.MessageId; -import net.sf.briar.api.protocol.Offer; -import net.sf.briar.api.protocol.SubscriptionUpdate; -import net.sf.briar.api.protocol.TransportUpdate; -import net.sf.briar.api.protocol.writers.AckWriter; -import net.sf.briar.api.protocol.writers.BatchWriter; -import net.sf.briar.api.protocol.writers.OfferWriter; -import net.sf.briar.api.protocol.writers.RequestWriter; -import net.sf.briar.api.protocol.writers.SubscriptionWriter; -import net.sf.briar.api.protocol.writers.TransportWriter; -import net.sf.briar.api.transport.ConnectionWindow; - -import com.google.inject.Inject; - -/** - * An implementation of DatabaseComponent using reentrant read-write locks. - * Depending on the JVM's read-write lock implementation, this implementation - * may allow writers to starve. LockFairnessTest can be used to test whether - * this implementation is safe on a given JVM. - */ -class ReadWriteLockDatabaseComponent<T> extends DatabaseComponentImpl<T> { - - private static final Logger LOG = - Logger.getLogger(ReadWriteLockDatabaseComponent.class.getName()); - - /* - * Locks must always be acquired in alphabetical order. See the Database - * interface to find out which calls require which locks. - */ - - private final ReentrantReadWriteLock contactLock = - new ReentrantReadWriteLock(true); - private final ReentrantReadWriteLock messageLock = - new ReentrantReadWriteLock(true); - private final ReentrantReadWriteLock messageStatusLock = - new ReentrantReadWriteLock(true); - private final ReentrantReadWriteLock ratingLock = - new ReentrantReadWriteLock(true); - private final ReentrantReadWriteLock subscriptionLock = - new ReentrantReadWriteLock(true); - private final ReentrantReadWriteLock transportLock = - new ReentrantReadWriteLock(true); - private final ReentrantReadWriteLock windowLock = - new ReentrantReadWriteLock(true); - - @Inject - ReadWriteLockDatabaseComponent(Database<T> db, DatabaseCleaner cleaner) { - super(db, cleaner); - } - - protected void expireMessages(int size) throws DbException { - contactLock.readLock().lock(); - try { - messageLock.writeLock().lock(); - try { - messageStatusLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - for(MessageId m : db.getOldMessages(txn, size)) { - removeMessage(txn, m); - } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - messageStatusLock.writeLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void close() throws DbException { - cleaner.stopCleaning(); - db.close(); - } - - public ContactId addContact(Map<String, Map<String, String>> transports, - byte[] secret) throws DbException { - if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact"); - ContactId c; - contactLock.writeLock().lock(); - try { - transportLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - c = db.addContact(txn, transports, secret); - db.commitTransaction(txn); - if(LOG.isLoggable(Level.FINE)) - LOG.fine("Added contact " + c); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.writeLock().unlock(); - } - } finally { - contactLock.writeLock().unlock(); - } - // Call the listeners outside the lock - callListeners(Event.CONTACTS_UPDATED); - return c; - } - - public void addLocalGroupMessage(Message m) throws DbException { - boolean added = false; - waitForPermissionToWrite(); - contactLock.readLock().lock(); - try { - messageLock.writeLock().lock(); - try { - messageStatusLock.writeLock().lock(); - try { - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - // Don't store the message if the user has - // unsubscribed from the group or the message - // predates the subscription - if(db.containsSubscription(txn, m.getGroup(), - m.getTimestamp())) { - added = storeGroupMessage(txn, m, null); - } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); - } - } finally { - messageStatusLock.writeLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - // Call the listeners outside the lock - if(added) callListeners(Event.MESSAGES_ADDED); - } - - public void addLocalPrivateMessage(Message m, ContactId c) - throws DbException { - boolean added = false; - waitForPermissionToWrite(); - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - messageLock.writeLock().lock(); - try { - messageStatusLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - added = storePrivateMessage(txn, m, c, false); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - messageStatusLock.writeLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - // Call the listeners outside the lock - if(added) callListeners(Event.MESSAGES_ADDED); - } - - public void findLostBatches(ContactId c) throws DbException { - // Find any lost batches that need to be retransmitted - Collection<BatchId> lost; - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - messageLock.readLock().lock(); - try { - messageStatusLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - lost = db.getLostBatches(txn, c); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - messageStatusLock.writeLock().unlock(); - } - } finally { - messageLock.readLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - for(BatchId batch : lost) { - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - messageLock.readLock().lock(); - try { - messageStatusLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(LOG.isLoggable(Level.FINE)) - LOG.fine("Removing lost batch"); - db.removeLostBatch(txn, c, batch); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - messageStatusLock.writeLock().unlock(); - } - } finally { - messageLock.readLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - } - - public void generateAck(ContactId c, AckWriter a) throws DbException, - IOException { - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - messageStatusLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - Collection<BatchId> acks = db.getBatchesToAck(txn, c); - Collection<BatchId> sent = new ArrayList<BatchId>(); - for(BatchId b : acks) if(a.writeBatchId(b)) sent.add(b); - a.finish(); - db.removeBatchesToAck(txn, c, sent); - if(LOG.isLoggable(Level.FINE)) - LOG.fine("Added " + acks.size() + " acks"); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } catch(IOException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - messageStatusLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void generateBatch(ContactId c, BatchWriter b) throws DbException, - IOException { - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - messageLock.readLock().lock(); - try { - Collection<MessageId> sent; - int bytesSent = 0; - messageStatusLock.readLock().lock(); - try { - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - sent = new ArrayList<MessageId>(); - int capacity = b.getCapacity(); - Collection<MessageId> sendable = - db.getSendableMessages(txn, c, capacity); - for(MessageId m : sendable) { - byte[] raw = db.getMessage(txn, m); - if(!b.writeMessage(raw)) break; - bytesSent += raw.length; - sent.add(m); - } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } catch(IOException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); - } - } finally { - messageStatusLock.readLock().unlock(); - } - // Record the contents of the batch, unless it's empty - if(sent.isEmpty()) return; - BatchId id = b.finish(); - messageStatusLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - db.addOutstandingBatch(txn, c, id, sent); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - messageStatusLock.writeLock().unlock(); - } - } finally { - messageLock.readLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public Collection<MessageId> generateBatch(ContactId c, BatchWriter b, - Collection<MessageId> requested) throws DbException, IOException { - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - messageLock.readLock().lock(); - try { - Collection<MessageId> sent, considered; - messageStatusLock.readLock().lock(); - try{ - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - sent = new ArrayList<MessageId>(); - considered = new ArrayList<MessageId>(); - int bytesSent = 0; - for(MessageId m : requested) { - byte[] raw = db.getMessageIfSendable(txn, c, m); - // If the message is still sendable, try to add - // it to the batch. If the batch is full, don't - // treat the message as considered, and don't - // try to add any further messages. - if(raw != null) { - if(!b.writeMessage(raw)) break; - bytesSent += raw.length; - sent.add(m); - } - considered.add(m); - } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } catch(IOException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); - } - } finally { - messageStatusLock.readLock().unlock(); - } - // Record the contents of the batch, unless it's empty - if(sent.isEmpty()) return considered; - BatchId id = b.finish(); - messageStatusLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - db.addOutstandingBatch(txn, c, id, sent); - db.commitTransaction(txn); - return considered; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - messageStatusLock.writeLock().unlock(); - } - } finally { - messageLock.readLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public Collection<MessageId> generateOffer(ContactId c, OfferWriter o) - throws DbException, IOException { - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - messageLock.readLock().lock(); - try { - messageStatusLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - Collection<MessageId> sendable = - db.getSendableMessages(txn, c, Integer.MAX_VALUE); - Iterator<MessageId> it = sendable.iterator(); - Collection<MessageId> sent = new ArrayList<MessageId>(); - while(it.hasNext()) { - MessageId m = it.next(); - if(!o.writeMessageId(m)) break; - sent.add(m); - } - o.finish(); - db.commitTransaction(txn); - return sent; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } catch(IOException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - messageStatusLock.readLock().unlock(); - } - } finally { - messageLock.readLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void generateSubscriptionUpdate(ContactId c, SubscriptionWriter s) - throws DbException, IOException { - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - Map<Group, Long> subs = db.getVisibleSubscriptions(txn, c); - s.writeSubscriptions(subs, System.currentTimeMillis()); - if(LOG.isLoggable(Level.FINE)) - LOG.fine("Added " + subs.size() + " subscriptions"); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } catch(IOException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void generateTransportUpdate(ContactId c, TransportWriter t) - throws DbException, IOException { - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - transportLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - Map<String, Map<String, String>> transports = - db.getTransports(txn); - t.writeTransports(transports, System.currentTimeMillis()); - if(LOG.isLoggable(Level.FINE)) - LOG.fine("Added " + transports.size() + " transports"); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } catch(IOException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.readLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public ConnectionWindow getConnectionWindow(ContactId c, int transportId) - throws DbException { - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - windowLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - ConnectionWindow w = - db.getConnectionWindow(txn, c, transportId); - db.commitTransaction(txn); - return w; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - windowLock.readLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public Collection<ContactId> getContacts() throws DbException { - contactLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - Collection<ContactId> contacts = db.getContacts(txn); - db.commitTransaction(txn); - return contacts; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - contactLock.readLock().unlock(); - } - } - - public Rating getRating(AuthorId a) throws DbException { - ratingLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - Rating r = db.getRating(txn, a); - db.commitTransaction(txn); - return r; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - ratingLock.readLock().unlock(); - } - } - - public byte[] getSharedSecret(ContactId c) throws DbException { - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - T txn = db.startTransaction(); - try { - byte[] secret = db.getSharedSecret(txn, c); - db.commitTransaction(txn); - return secret; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - contactLock.readLock().unlock(); - } - } - - public Collection<Group> getSubscriptions() throws DbException { - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - Collection<Group> subs = db.getSubscriptions(txn); - db.commitTransaction(txn); - return subs; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); - } - } - - public Map<String, String> getTransportConfig(String name) - throws DbException { - transportLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - Map<String, String> config = db.getTransportConfig(txn, name); - db.commitTransaction(txn); - return config; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.readLock().unlock(); - } - } - - public Map<String, Map<String, String>> getTransports() throws DbException { - transportLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - Map<String, Map<String, String>> transports = - db.getTransports(txn); - db.commitTransaction(txn); - return transports; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.readLock().unlock(); - } - } - - public Map<String, Map<String, String>> getTransports(ContactId c) - throws DbException { - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - transportLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - Map<String, Map<String, String>> transports = - db.getTransports(txn, c); - db.commitTransaction(txn); - return transports; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.readLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public Collection<ContactId> getVisibility(GroupId g) throws DbException { - contactLock.readLock().lock(); - try { - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - Collection<ContactId> visible = db.getVisibility(txn, g); - db.commitTransaction(txn); - return visible; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public boolean hasSendableMessages(ContactId c) throws DbException { - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - messageLock.readLock().lock(); - try { - messageStatusLock.readLock().lock(); - try { - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - boolean has = db.hasSendableMessages(txn, c); - db.commitTransaction(txn); - return has; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); - } - } finally { - messageStatusLock.readLock().unlock(); - } - } finally { - messageLock.readLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void receiveAck(ContactId c, Ack a) throws DbException { - // Mark all messages in acked batches as seen - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - messageLock.readLock().lock(); - try { - messageStatusLock.writeLock().lock(); - try { - Collection<BatchId> acks = a.getBatchIds(); - for(BatchId ack : acks) { - T txn = db.startTransaction(); - try { - db.removeAckedBatch(txn, c, ack); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } - if(LOG.isLoggable(Level.FINE)) - LOG.fine("Received " + acks.size() + " acks"); - } finally { - messageStatusLock.writeLock().unlock(); - } - } finally { - messageLock.readLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void receiveBatch(ContactId c, Batch b) throws DbException { - boolean anyAdded = false; - waitForPermissionToWrite(); - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - messageLock.writeLock().lock(); - try { - messageStatusLock.writeLock().lock(); - try { - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - anyAdded = storeMessages(txn, c, b.getMessages()); - db.addBatchToAck(txn, c, b.getId()); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); - } - } finally { - messageStatusLock.writeLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - // Call the listeners outside the lock - if(anyAdded) callListeners(Event.MESSAGES_ADDED); - } - - public void receiveOffer(ContactId c, Offer o, RequestWriter r) - throws DbException, IOException { - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - messageLock.readLock().lock(); - try { - messageStatusLock.writeLock().lock(); - try { - subscriptionLock.readLock().lock(); - try { - Collection<MessageId> offered = o.getMessageIds(); - BitSet request = new BitSet(offered.size()); - T txn = db.startTransaction(); - try { - Iterator<MessageId> it = offered.iterator(); - for(int i = 0; it.hasNext(); i++) { - // If the message is not in the database, or if - // it is not visible to the contact, request it - MessageId m = it.next(); - if(!db.setStatusSeenIfVisible(txn, c, m)) - request.set(i); - } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - r.writeRequest(request, offered.size()); - } finally { - subscriptionLock.readLock().unlock(); - } - } finally { - messageStatusLock.writeLock().unlock(); - } - } finally { - messageLock.readLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void receiveSubscriptionUpdate(ContactId c, SubscriptionUpdate s) - throws DbException { - // Update the contact's subscriptions - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - subscriptionLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - Map<Group, Long> subs = s.getSubscriptions(); - db.setSubscriptions(txn, c, subs, s.getTimestamp()); - if(LOG.isLoggable(Level.FINE)) - LOG.fine("Received " + subs.size() + " subscriptions"); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void receiveTransportUpdate(ContactId c, TransportUpdate t) - throws DbException { - // Update the contact's transport properties - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - transportLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - Map<String, Map<String, String>> transports = - t.getTransports(); - db.setTransports(txn, c, transports, t.getTimestamp()); - if(LOG.isLoggable(Level.FINE)) - LOG.fine("Received " + transports.size() - + " transports"); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void removeContact(ContactId c) throws DbException { - if(LOG.isLoggable(Level.FINE)) LOG.fine("Removing contact " + c); - contactLock.writeLock().lock(); - try { - messageLock.writeLock().lock(); - try { - messageStatusLock.writeLock().lock(); - try { - subscriptionLock.writeLock().lock(); - try { - transportLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - db.removeContact(txn, c); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.writeLock().unlock(); - } - } finally { - subscriptionLock.writeLock().unlock(); - } - } finally { - messageStatusLock.writeLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); - } - } finally { - contactLock.writeLock().unlock(); - } - // Call the listeners outside the lock - callListeners(Event.CONTACTS_UPDATED); - } - - public void setConnectionWindow(ContactId c, int transportId, - ConnectionWindow w) throws DbException { - contactLock.readLock().lock(); - try { - if(!containsContact(c)) throw new NoSuchContactException(); - windowLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - db.setConnectionWindow(txn, c, transportId, w); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - } - } finally { - windowLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void setRating(AuthorId a, Rating r) throws DbException { - messageLock.writeLock().lock(); - try { - ratingLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - Rating old = db.setRating(txn, a, r); - // Update the sendability of the author's messages - if(r == Rating.GOOD && old != Rating.GOOD) - updateAuthorSendability(txn, a, true); - else if(r != Rating.GOOD && old == Rating.GOOD) - updateAuthorSendability(txn, a, false); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - ratingLock.writeLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); - } - } - - public void setTransportConfig(String name, - Map<String, String> config) throws DbException { - boolean changed = false; - transportLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - Map<String, String> old = db.getTransportConfig(txn, name); - if(!config.equals(old)) { - db.setTransportConfig(txn, name, config); - changed = true; - } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.writeLock().unlock(); - } - // Call the listeners outside the lock - if(changed) callListeners(Event.TRANSPORTS_UPDATED); - } - - public void setTransportProperties(String name, - Map<String, String> properties) throws DbException { - boolean changed = false; - transportLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - Map<String, String> old = db.getTransports(txn).get(name); - if(!properties.equals(old)) { - db.setTransportProperties(txn, name, properties); - changed = true; - } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.writeLock().unlock(); - } - // Call the listeners outside the lock - if(changed) callListeners(Event.TRANSPORTS_UPDATED); - } - - public void setVisibility(GroupId g, Collection<ContactId> visible) - throws DbException { - contactLock.readLock().lock(); - try { - subscriptionLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - // Remove any ex-contacts from the set - Collection<ContactId> present = - new ArrayList<ContactId>(visible.size()); - for(ContactId c : visible) { - if(db.containsContact(txn, c)) present.add(c); - } - db.setVisibility(txn, g, present); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - } - - public void subscribe(Group g) throws DbException { - if(LOG.isLoggable(Level.FINE)) LOG.fine("Subscribing to " + g); - boolean added = false; - subscriptionLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(db.containsSubscription(txn, g.getId())) { - db.addSubscription(txn, g); - added = true; - } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); - } - // Call the listeners outside the lock - if(added) callListeners(Event.SUBSCRIPTIONS_UPDATED); - } - - public void unsubscribe(GroupId g) throws DbException { - if(LOG.isLoggable(Level.FINE)) LOG.fine("Unsubscribing from " + g); - boolean removed = false; - contactLock.readLock().lock(); - try { - messageLock.writeLock().lock(); - try { - messageStatusLock.writeLock().lock(); - try { - subscriptionLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(db.containsSubscription(txn, g)) { - db.removeSubscription(txn, g); - removed = true; - } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); - } - } finally { - messageStatusLock.writeLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); - } - } finally { - contactLock.readLock().unlock(); - } - // Call the listeners outside the lock - if(removed) callListeners(Event.SUBSCRIPTIONS_UPDATED); - } -} \ No newline at end of file diff --git a/test/net/sf/briar/db/DatabaseComponentImplTest.java b/test/net/sf/briar/db/DatabaseComponentImplTest.java index 33ce3feeba8d4f252c9aa8c547842e37b0043d40..ea17f97171549bd83f5eaf544201f1992a9127fb 100644 --- a/test/net/sf/briar/db/DatabaseComponentImplTest.java +++ b/test/net/sf/briar/db/DatabaseComponentImplTest.java @@ -119,6 +119,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { context.assertIsSatisfied(); } + @Override protected <T> DatabaseComponent createDatabaseComponent( Database<T> database, DatabaseCleaner cleaner) { return createDatabaseComponentImpl(database, cleaner); @@ -126,6 +127,6 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { private <T> DatabaseComponentImpl<T> createDatabaseComponentImpl( Database<T> database, DatabaseCleaner cleaner) { - return new ReadWriteLockDatabaseComponent<T>(database, cleaner); + return new DatabaseComponentImpl<T>(database, cleaner); } }