diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java index ad645ddfb10531f4d2c627ae825239b9f7a86a2f..ad0fb223e0fe77e67e9db09977a4e0747bc73e0c 100644 --- a/briar-core/src/org/briarproject/db/Database.java +++ b/briar-core/src/org/briarproject/db/Database.java @@ -38,29 +38,22 @@ import org.briarproject.api.transport.TemporarySecret; * terminated by calling either {@link #abortTransaction(T)} or * {@link #commitTransaction(T)}, even if an exception is thrown. * <p> - * Locking is provided by the DatabaseComponent implementation. To prevent - * deadlock, locks must be acquired in the following (alphabetical) order: - * <ul> - * <li> contact - * <li> identity - * <li> message - * <li> retention - * <li> setting - * <li> subscription - * <li> transport - * <li> window - * </ul> - * If table A has a foreign key pointing to table B, we get a read lock on A to - * read A, a write lock on A to write A, and write locks on A and B to write B. + * Read-write locking is provided by the DatabaseComponent implementation. */ interface Database<T> { - /** Opens the database and returns true if the database already existed. */ + /** + * Opens the database and returns true if the database already existed. + * <p> + * Locking: write. + */ boolean open() throws DbException, IOException; /** * Prevents new transactions from starting, waits for all current * transactions to finish, and closes the database. + * <p> + * Locking: write. */ void close() throws DbException, IOException; @@ -92,8 +85,7 @@ interface Database<T> { * Stores a contact associated with the given local and remote pseudonyms, * and returns an ID for the contact. * <p> - * Locking: contact write, message write, retention write, - * subscription write, transport write, window write. + * Locking: write. */ ContactId addContact(T txn, Author remote, AuthorId local) throws DbException; @@ -101,7 +93,7 @@ interface Database<T> { /** * Stores an endpoint. * <p> - * Locking: window write. + * Locking: write. */ void addEndpoint(T txn, Endpoint ep) throws DbException; @@ -109,29 +101,28 @@ interface Database<T> { * Subscribes to a group, or returns false if the user already has the * maximum number of subscriptions. * <p> - * Locking: message write, subscription write. + * Locking: write. */ boolean addGroup(T txn, Group g) throws DbException; /** * Stores a local pseudonym. * <p> - * Locking: contact write, identity write, message write, retention write, - * subscription write, transport write, window write. + * Locking: write. */ void addLocalAuthor(T txn, LocalAuthor a) throws DbException; /** * Stores a message. * <p> - * Locking: message write. + * Locking: write. */ void addMessage(T txn, Message m, boolean local) throws DbException; /** * Records that a message has been offered by the given contact. * <p> - * Locking: message write. + * Locking: write. */ void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException; @@ -139,7 +130,7 @@ interface Database<T> { * Stores the given temporary secrets and deletes any secrets that have * been made obsolete. * <p> - * Locking: window write. + * Locking: write. */ void addSecrets(T txn, Collection<TemporarySecret> secrets) throws DbException; @@ -147,10 +138,10 @@ interface Database<T> { /** * Initialises the status of the given message with respect to the given * contact. + * <p> + * Locking: write. * @param ack whether the message needs to be acknowledged. * @param seen whether the contact has seen the message. - * <p> - * Locking: message write. */ void addStatus(T txn, ContactId c, MessageId m, boolean ack, boolean seen) throws DbException; @@ -159,7 +150,7 @@ interface Database<T> { * Stores a transport and returns true if the transport was not previously * in the database. * <p> - * Locking: transport write, window write. + * Locking: write. */ boolean addTransport(T txn, TransportId t, long maxLatency) throws DbException; @@ -167,49 +158,49 @@ interface Database<T> { /** * Makes a group visible to the given contact. * <p> - * Locking: subscription write. + * Locking: write. */ void addVisibility(T txn, ContactId c, GroupId g) throws DbException; /** * Returns true if the database contains the given contact. * <p> - * Locking: contact read. + * Locking: read. */ boolean containsContact(T txn, AuthorId a) throws DbException; /** * Returns true if the database contains the given contact. * <p> - * Locking: contact read. + * Locking: read. */ boolean containsContact(T txn, ContactId c) throws DbException; /** * Returns true if the user subscribes to the given group. * <p> - * Locking: subscription read. + * Locking: read. */ boolean containsGroup(T txn, GroupId g) throws DbException; /** * Returns true if the database contains the given local pseudonym. * <p> - * Locking: identity read. + * Locking: read. */ boolean containsLocalAuthor(T txn, AuthorId a) throws DbException; /** * Returns true if the database contains the given message. * <p> - * Locking: message read. + * Locking: read. */ boolean containsMessage(T txn, MessageId m) throws DbException; /** * Returns true if the database contains the given transport. * <p> - * Locking: transport read. + * Locking: read. */ boolean containsTransport(T txn, TransportId t) throws DbException; @@ -217,7 +208,7 @@ interface Database<T> { * Returns true if the user subscribes to the given group and the group is * visible to the given contact. * <p> - * Locking: subscription read. + * Locking: read. */ boolean containsVisibleGroup(T txn, ContactId c, GroupId g) throws DbException; @@ -226,7 +217,7 @@ interface Database<T> { * Returns true if the database contains the given message and the message * is visible to the given contact. * <p> - * Locking: message read, subscription read. + * Locking: read. */ boolean containsVisibleMessage(T txn, ContactId c, MessageId m) throws DbException; @@ -234,7 +225,7 @@ interface Database<T> { /** * Returns the number of messages offered by the given contact. * <p> - * Locking: message read. + * Locking: read. */ int countOfferedMessages(T txn, ContactId c) throws DbException; @@ -242,49 +233,49 @@ interface Database<T> { * Returns the status of all groups to which the user subscribes or can * subscribe, excluding inbox groups. * <p> - * Locking: subscription read. + * Locking: read. */ Collection<GroupStatus> getAvailableGroups(T txn) throws DbException; /** * Returns the configuration for the given transport. * <p> - * Locking: transport read. + * Locking: read. */ TransportConfig getConfig(T txn, TransportId t) throws DbException; /** * Returns the contact with the given ID. * <p> - * Locking: contact read. + * Locking: read. */ Contact getContact(T txn, ContactId c) throws DbException; /** * Returns the IDs of all contacts. * <p> - * Locking: contact read. + * Locking: read. */ Collection<ContactId> getContactIds(T txn) throws DbException; /** * Returns all contacts. * <p> - * Locking: contact read, window read. + * Locking: read. */ Collection<Contact> getContacts(T txn) throws DbException; /** * Returns all contacts associated with the given local pseudonym. * <p> - * Locking: contact read. + * Locking: read. */ Collection<ContactId> getContacts(T txn, AuthorId a) throws DbException; /** * Returns all endpoints. * <p> - * Locking: window read. + * Locking: read. */ Collection<Endpoint> getEndpoints(T txn) throws DbException; @@ -298,14 +289,14 @@ interface Database<T> { /** * Returns the group with the given ID, if the user subscribes to it. * <p> - * Locking: subscription read. + * Locking: read. */ Group getGroup(T txn, GroupId g) throws DbException; /** * Returns all groups to which the user subscribes. * <p> - * Locking: subscription read. + * Locking: read. */ Collection<Group> getGroups(T txn) throws DbException; @@ -313,7 +304,7 @@ interface Database<T> { * Returns the ID of the inbox group for the given contact, or null if no * inbox group has been set. * <p> - * Locking: contact read, subscription read. + * Locking: read. */ GroupId getInboxGroupId(T txn, ContactId c) throws DbException; @@ -321,7 +312,7 @@ interface Database<T> { * Returns the headers of all messages in the inbox group for the given * contact, or null if no inbox group has been set. * <p> - * Locking: contact read, identity read, message read, subscription read. + * Locking: read. */ Collection<MessageHeader> getInboxMessageHeaders(T txn, ContactId c) throws DbException; @@ -329,21 +320,21 @@ interface Database<T> { /** * Returns the local pseudonym with the given ID. * <p> - * Locking: identity read. + * Locking: read. */ LocalAuthor getLocalAuthor(T txn, AuthorId a) throws DbException; /** * Returns all local pseudonyms. * <p> - * Locking: identity read. + * Locking: read. */ Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException; /** * Returns the local transport properties for all transports. * <p> - * Locking: transport read. + * Locking: read. */ Map<TransportId, TransportProperties> getLocalProperties(T txn) throws DbException; @@ -351,7 +342,7 @@ interface Database<T> { /** * Returns the local transport properties for the given transport. * <p> - * Locking: transport read. + * Locking: read. */ TransportProperties getLocalProperties(T txn, TransportId t) throws DbException; @@ -359,14 +350,14 @@ interface Database<T> { /** * Returns the body of the message identified by the given ID. * <p> - * Locking: message read. + * Locking: read. */ byte[] getMessageBody(T txn, MessageId m) throws DbException; /** * Returns the headers of all messages in the given group. * <p> - * Locking: message read. + * Locking: read. */ Collection<MessageHeader> getMessageHeaders(T txn, GroupId g) throws DbException; @@ -375,7 +366,7 @@ interface Database<T> { * Returns the IDs of some messages received from the given contact that * need to be acknowledged, up to the given number of messages. * <p> - * Locking: message read. + * Locking: read. */ Collection<MessageId> getMessagesToAck(T txn, ContactId c, int maxMessages) throws DbException; @@ -384,7 +375,7 @@ interface Database<T> { * Returns the IDs of some messages that are eligible to be offered to the * given contact, up to the given number of messages. * <p> - * Locking: message read, subscription read. + * Locking: read. */ Collection<MessageId> getMessagesToOffer(T txn, ContactId c, int maxMessages) throws DbException; @@ -393,7 +384,7 @@ interface Database<T> { * Returns the IDs of some messages that are eligible to be sent to the * given contact, up to the given total length. * <p> - * Locking: message read, subscription read. + * Locking: read. */ Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength) throws DbException; @@ -402,7 +393,7 @@ interface Database<T> { * Returns the IDs of some messages that are eligible to be requested from * the given contact, up to the given number of messages. * <p> - * Locking: message read. + * Locking: read. */ Collection<MessageId> getMessagesToRequest(T txn, ContactId c, int maxMessages) throws DbException; @@ -411,7 +402,7 @@ interface Database<T> { * Returns the IDs of the oldest messages in the database, with a total * size less than or equal to the given size. * <p> - * Locking: message read. + * Locking: read. */ Collection<MessageId> getOldMessages(T txn, int size) throws DbException; @@ -420,28 +411,28 @@ interface Database<T> { * has no parent, or the parent is absent from the database, or the parent * belongs to a different group. * <p> - * Locking: message read. + * Locking: read. */ MessageId getParent(T txn, MessageId m) throws DbException; /** * Returns the message identified by the given ID, in serialised form. * <p> - * Locking: message read. + * Locking: read. */ byte[] getRawMessage(T txn, MessageId m) throws DbException; /** * Returns true if the given message is marked as read. * <p> - * Locking: message read. + * Locking: read. */ boolean getReadFlag(T txn, MessageId m) throws DbException; /** * Returns all remote properties for the given transport. * <p> - * Locking: transport read. + * Locking: read. */ Map<ContactId, TransportProperties> getRemoteProperties(T txn, TransportId t) throws DbException; @@ -451,7 +442,7 @@ interface Database<T> { * given contact and have been requested by the contact, up to the given * total length. * <p> - * Locking: message read, subscription read. + * Locking: read. */ Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c, int maxLength) throws DbException; @@ -459,7 +450,7 @@ interface Database<T> { /** * Returns a retention ack for the given contact, or null if no ack is due. * <p> - * Locking: retention write. + * Locking: write. */ RetentionAck getRetentionAck(T txn, ContactId c) throws DbException; @@ -467,7 +458,7 @@ interface Database<T> { * Returns a retention update for the given contact and updates its expiry * time using the given latency, or returns null if no update is due. * <p> - * Locking: message read, retention write. + * Locking: write. */ RetentionUpdate getRetentionUpdate(T txn, ContactId c, long maxLatency) throws DbException; @@ -475,21 +466,21 @@ interface Database<T> { /** * Returns all temporary secrets. * <p> - * Locking: window read. + * Locking: read. */ Collection<TemporarySecret> getSecrets(T txn) throws DbException; /** * Returns all settings. * <p> - * Locking: setting read. + * Locking: read. */ Settings getSettings(T txn) throws DbException; /** * Returns all contacts who subscribe to the given group. * <p> - * Locking: subscription read. + * Locking: read. */ Collection<Contact> getSubscribers(T txn, GroupId g) throws DbException; @@ -497,7 +488,7 @@ interface Database<T> { * Returns a subscription ack for the given contact, or null if no ack is * due. * <p> - * Locking: subscription write. + * Locking: write. */ SubscriptionAck getSubscriptionAck(T txn, ContactId c) throws DbException; @@ -505,7 +496,7 @@ interface Database<T> { * Returns a subscription update for the given contact and updates its * expiry time using the given latency, or returns null if no update is due. * <p> - * Locking: subscription write. + * Locking: write. */ SubscriptionUpdate getSubscriptionUpdate(T txn, ContactId c, long maxLatency) throws DbException; @@ -514,7 +505,7 @@ interface Database<T> { * Returns a collection of transport acks for the given contact, or null if * no acks are due. * <p> - * Locking: transport write. + * Locking: write. */ Collection<TransportAck> getTransportAcks(T txn, ContactId c) throws DbException; @@ -522,7 +513,7 @@ interface Database<T> { /** * Returns the maximum latencies of all local transports. * <p> - * Locking: transport read. + * Locking: read. */ Map<TransportId, Long> getTransportLatencies(T txn) throws DbException; @@ -531,7 +522,7 @@ interface Database<T> { * updates their expiry times using the given latency, or returns null if * no updates are due. * <p> - * Locking: transport write. + * Locking: write. */ Collection<TransportUpdate> getTransportUpdates(T txn, ContactId c, long maxLatency) throws DbException; @@ -539,14 +530,14 @@ interface Database<T> { /** * Returns the number of unread messages in each subscribed group. * <p> - * Locking: message read. + * Locking: read. */ Map<GroupId, Integer> getUnreadMessageCounts(T txn) throws DbException; /** * Returns the IDs of all contacts to which the given group is visible. * <p> - * Locking: subscription read. + * Locking: read. */ Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException; @@ -555,7 +546,7 @@ interface Database<T> { * in the given rotation period and returns the old value, or -1 if the * counter does not exist. * <p> - * Locking: window write. + * Locking: write. */ long incrementConnectionCounter(T txn, ContactId c, TransportId t, long period) throws DbException; @@ -564,7 +555,7 @@ interface Database<T> { * Increments the retention time versions for all contacts to indicate that * the database's retention time has changed and updates should be sent. * <p> - * Locking: retention write. + * Locking: write. */ void incrementRetentionVersions(T txn) throws DbException; @@ -572,7 +563,7 @@ interface Database<T> { * Marks the given messages as not needing to be acknowledged to the * given contact. * <p> - * Locking: message write. + * Locking: write. */ void lowerAckFlag(T txn, ContactId c, Collection<MessageId> acked) throws DbException; @@ -581,7 +572,7 @@ interface Database<T> { * Marks the given messages as not having been requested by the given * contact. * <p> - * Locking: message write. + * Locking: write. */ void lowerRequestedFlag(T txn, ContactId c, Collection<MessageId> requested) throws DbException; @@ -590,7 +581,7 @@ interface Database<T> { * Merges the given configuration with the existing configuration for the * given transport. * <p> - * Locking: transport write. + * Locking: write. */ void mergeConfig(T txn, TransportId t, TransportConfig config) throws DbException; @@ -599,7 +590,7 @@ interface Database<T> { * Merges the given properties with the existing local properties for the * given transport. * <p> - * Locking: transport write. + * Locking: write. */ void mergeLocalProperties(T txn, TransportId t, TransportProperties p) throws DbException; @@ -607,36 +598,35 @@ interface Database<T> { /** * Merges the given settings with the existing settings. * <p> - * Locking: setting write. + * Locking: write. */ void mergeSettings(T txn, Settings s) throws DbException; /** * Marks a message as needing to be acknowledged to the given contact. * <p> - * Locking: message write. + * Locking: write. */ void raiseAckFlag(T txn, ContactId c, MessageId m) throws DbException; /** * Marks a message as having been requested by the given contact. * <p> - * Locking: message write. + * Locking: write. */ void raiseRequestedFlag(T txn, ContactId c, MessageId m) throws DbException; /** * Marks a message as having been seen by the given contact. * <p> - * Locking: message write. + * Locking: write. */ void raiseSeenFlag(T txn, ContactId c, MessageId m) throws DbException; /** * Removes a contact from the database. * <p> - * Locking: contact write, message write, retention write, - * subscription write, transport write, window write. + * Locking: write. */ void removeContact(T txn, ContactId c) throws DbException; @@ -644,7 +634,7 @@ interface Database<T> { * Unsubscribes from a group. Any messages belonging to the group are * deleted from the database. * <p> - * Locking: message write, subscription write. + * Locking: write. */ void removeGroup(T txn, GroupId g) throws DbException; @@ -652,15 +642,14 @@ interface Database<T> { * Removes a local pseudonym (and all associated contacts) from the * database. * <p> - * Locking: contact write, identity write, message write, retention write, - * subscription write, transport write, window write. + * Locking: write. */ void removeLocalAuthor(T txn, AuthorId a) throws DbException; /** * Removes a message (and all associated state) from the database. * <p> - * Locking: message write. + * Locking: write. */ void removeMessage(T txn, MessageId m) throws DbException; @@ -668,7 +657,7 @@ interface Database<T> { * Removes an offered message that was offered by the given contact, or * returns false if there is no such message. * <p> - * Locking: message write. + * Locking: write. */ boolean removeOfferedMessage(T txn, ContactId c, MessageId m) throws DbException; @@ -677,7 +666,7 @@ interface Database<T> { * Removes the given offered messages that were offered by the given * contact. * <p> - * Locking: message write. + * Locking: write. */ void removeOfferedMessages(T txn, ContactId c, Collection<MessageId> requested) throws DbException; @@ -685,14 +674,14 @@ interface Database<T> { /** * Removes a transport (and all associated state) from the database. * <p> - * Locking: transport write, window write. + * Locking: write. */ void removeTransport(T txn, TransportId t) throws DbException; /** * Makes a group invisible to the given contact. * <p> - * Locking: subscription write. + * Locking: write. */ void removeVisibility(T txn, ContactId c, GroupId g) throws DbException; @@ -700,7 +689,7 @@ interface Database<T> { * Resets the transmission count and expiry time of the given message with * respect to the given contact. * <p> - * Locking: message write. + * Locking: write. */ void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException; @@ -708,7 +697,7 @@ interface Database<T> { * Sets the connection reordering window for the given endpoint in the * given rotation period. * <p> - * Locking: window write. + * Locking: write. */ void setConnectionWindow(T txn, ContactId c, TransportId t, long period, long centre, byte[] bitmap) throws DbException; @@ -718,7 +707,7 @@ interface Database<T> { * true, unless an update with an equal or higher version number has * already been received from the contact. * <p> - * Locking: message write, subscription write. + * Locking: write. */ boolean setGroups(T txn, ContactId c, Collection<Group> groups, long version) throws DbException; @@ -727,14 +716,14 @@ interface Database<T> { * Makes a group visible to the given contact, adds it to the contact's * subscriptions, and sets it as the inbox group for the contact. * <p> - * Locking: subscription write. + * Locking: write. */ public void setInboxGroup(T txn, ContactId c, Group g) throws DbException; /** * Marks a message as read or unread. * <p> - * Locking: message write. + * Locking: write. */ void setReadFlag(T txn, MessageId m, boolean read) throws DbException; @@ -742,7 +731,7 @@ interface Database<T> { * Sets the remote transport properties for the given contact, replacing * any existing properties. * <p> - * Locking: transport write. + * Locking: write. */ void setRemoteProperties(T txn, ContactId c, Map<TransportId, TransportProperties> p) throws DbException; @@ -753,7 +742,7 @@ interface Database<T> { * unless an update with an equal or higher version number has already been * received from the contact. * <p> - * Locking: transport write. + * Locking: write. */ boolean setRemoteProperties(T txn, ContactId c, TransportId t, TransportProperties p, long version) throws DbException; @@ -763,7 +752,7 @@ interface Database<T> { * true, unless an update with an equal or higher version number has * already been received from the contact. * <p> - * Locking: retention write. + * Locking: write. */ boolean setRetentionTime(T txn, ContactId c, long retention, long version) throws DbException; @@ -772,7 +761,7 @@ interface Database<T> { * Records a retention ack from the given contact for the given version, * unless the contact has already acked an equal or higher version. * <p> - * Locking: retention write. + * Locking: write. */ void setRetentionUpdateAcked(T txn, ContactId c, long version) throws DbException; @@ -781,7 +770,7 @@ interface Database<T> { * Records a subscription ack from the given contact for the given version, * unless the contact has already acked an equal or higher version. * <p> - * Locking: subscription write. + * Locking: write. */ void setSubscriptionUpdateAcked(T txn, ContactId c, long version) throws DbException; @@ -790,7 +779,7 @@ interface Database<T> { * Records a transport ack from the give contact for the given version, * unless the contact has already acked an equal or higher version. * <p> - * Locking: transport write. + * Locking: write. */ void setTransportUpdateAcked(T txn, ContactId c, TransportId t, long version) throws DbException; @@ -798,7 +787,7 @@ interface Database<T> { /** * Makes a group visible or invisible to future contacts by default. * <p> - * Locking: subscription write. + * Locking: write. */ void setVisibleToAll(T txn, GroupId g, boolean all) throws DbException; @@ -807,7 +796,7 @@ interface Database<T> { * with respect to the given contact, using the latency of the transport * over which it was sent. * <p> - * Locking: message write. + * Locking: write. */ void updateExpiryTime(T txn, ContactId c, MessageId m, long maxLatency) throws DbException; diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java index ed245931e0a26690485c8dc1197bad27e30f0f99..ffc1061c56d1ec4dc26b9011f0ec8517a97275e3 100644 --- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java +++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java @@ -93,38 +93,17 @@ DatabaseCleaner.Callback { Logger.getLogger(DatabaseComponentImpl.class.getName()); private static final int MS_BETWEEN_SWEEPS = 10 * 1000; // 10 seconds - /* - * 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 identityLock = - new ReentrantReadWriteLock(true); - private final ReentrantReadWriteLock messageLock = - new ReentrantReadWriteLock(true); - private final ReentrantReadWriteLock retentionLock = - new ReentrantReadWriteLock(true); - private final ReentrantReadWriteLock settingLock = - 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 ShutdownManager shutdown; + private final ReentrantReadWriteLock lock = + new ReentrantReadWriteLock(true); private final Collection<EventListener> listeners = new CopyOnWriteArrayList<EventListener>(); - private final Object openCloseLock = new Object(); - private boolean open = false; // Locking: openCloseLock; - private int shutdownHandle = -1; // Locking: openCloseLock; + private boolean open = false; // Locking: lock.writeLock + private int shutdownHandle = -1; // Locking: lock.writeLock @Inject DatabaseComponentImpl(Database<T> db, DatabaseCleaner cleaner, @@ -135,39 +114,47 @@ DatabaseCleaner.Callback { } public boolean open() throws DbException, IOException { - synchronized(openCloseLock) { + Runnable shutdownHook = new Runnable() { + public void run() { + lock.writeLock().lock(); + try { + shutdownHandle = -1; + close(); + } catch(DbException e) { + if(LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } catch(IOException e) { + if(LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } finally { + lock.writeLock().unlock(); + } + } + }; + lock.writeLock().lock(); + try { if(open) throw new IllegalStateException(); open = true; boolean reopened = db.open(); cleaner.startCleaning(this, MS_BETWEEN_SWEEPS); - shutdownHandle = shutdown.addShutdownHook(new Runnable() { - public void run() { - try { - synchronized(openCloseLock) { - shutdownHandle = -1; - close(); - } - } catch(DbException e) { - if(LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } catch(IOException e) { - if(LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } - } - }); + shutdownHandle = shutdown.addShutdownHook(shutdownHook); return reopened; + } finally { + lock.writeLock().unlock(); } } public void close() throws DbException, IOException { - synchronized(openCloseLock) { + lock.writeLock().lock(); + try { if(!open) return; open = false; if(shutdownHandle != -1) shutdown.removeShutdownHook(shutdownHandle); cleaner.stopCleaning(); db.close(); + } finally { + lock.writeLock().unlock(); } } @@ -179,208 +166,119 @@ DatabaseCleaner.Callback { listeners.remove(l); } + /** Notifies all listeners of a database event. */ + private void callListeners(Event e) { + for(EventListener l : listeners) l.eventOccurred(e); + } + public ContactId addContact(Author remote, AuthorId local) throws DbException { ContactId c; - contactLock.writeLock().lock(); + lock.writeLock().lock(); try { - identityLock.readLock().lock(); + T txn = db.startTransaction(); try { - messageLock.writeLock().lock(); - try { - retentionLock.writeLock().lock(); - try { - subscriptionLock.writeLock().lock(); - try { - transportLock.writeLock().lock(); - try { - windowLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(db.containsContact(txn, remote.getId())) - throw new ContactExistsException(); - if(!db.containsLocalAuthor(txn, local)) - throw new NoSuchLocalAuthorException(); - c = db.addContact(txn, remote, local); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - windowLock.writeLock().unlock(); - } - } finally { - transportLock.writeLock().unlock(); - } - } finally { - subscriptionLock.writeLock().unlock(); - } - } finally { - retentionLock.writeLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); - } - } finally { - identityLock.readLock().unlock(); + if(db.containsContact(txn, remote.getId())) + throw new ContactExistsException(); + if(!db.containsLocalAuthor(txn, local)) + throw new NoSuchLocalAuthorException(); + c = db.addContact(txn, remote, local); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.writeLock().unlock(); + lock.writeLock().unlock(); } callListeners(new ContactAddedEvent(c)); return c; } - /** Notifies all listeners of a database event. */ - private void callListeners(Event e) { - for(EventListener l : listeners) l.eventOccurred(e); - } - public void addEndpoint(Endpoint ep) throws DbException { - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - transportLock.readLock().lock(); + T txn = db.startTransaction(); try { - windowLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, ep.getContactId())) - throw new NoSuchContactException(); - if(!db.containsTransport(txn, ep.getTransportId())) - throw new NoSuchTransportException(); - db.addEndpoint(txn, ep); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - windowLock.writeLock().unlock(); - } - } finally { - transportLock.readLock().unlock(); + if(!db.containsContact(txn, ep.getContactId())) + throw new NoSuchContactException(); + if(!db.containsTransport(txn, ep.getTransportId())) + throw new NoSuchTransportException(); + db.addEndpoint(txn, ep); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } } public boolean addGroup(Group g) throws DbException { boolean added = false; - messageLock.writeLock().lock(); + lock.writeLock().lock(); try { - subscriptionLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsGroup(txn, g.getId())) - added = db.addGroup(txn, g); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); + if(!db.containsGroup(txn, g.getId())) + added = db.addGroup(txn, g); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - messageLock.writeLock().unlock(); + lock.writeLock().unlock(); } if(added) callListeners(new SubscriptionAddedEvent(g)); return added; } public void addLocalAuthor(LocalAuthor a) throws DbException { - contactLock.writeLock().lock(); + lock.writeLock().lock(); try { - identityLock.writeLock().lock(); + T txn = db.startTransaction(); try { - messageLock.writeLock().lock(); - try { - retentionLock.writeLock().lock(); - try { - subscriptionLock.writeLock().lock(); - try { - transportLock.writeLock().lock(); - try { - windowLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(db.containsLocalAuthor(txn, a.getId())) - throw new LocalAuthorExistsException(); - db.addLocalAuthor(txn, a); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - windowLock.writeLock().unlock(); - } - } finally { - transportLock.writeLock().unlock(); - } - } finally { - subscriptionLock.writeLock().unlock(); - } - } finally { - retentionLock.writeLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); - } - } finally { - identityLock.writeLock().unlock(); + if(db.containsLocalAuthor(txn, a.getId())) + throw new LocalAuthorExistsException(); + db.addLocalAuthor(txn, a); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.writeLock().unlock(); + lock.writeLock().unlock(); } callListeners(new LocalAuthorAddedEvent(a.getId())); } public void addLocalMessage(Message m) throws DbException { boolean duplicate; - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - messageLock.writeLock().lock(); + T txn = db.startTransaction(); try { - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - duplicate = db.containsMessage(txn, m.getId()); - if(!duplicate) { - GroupId g = m.getGroup().getId(); - if(db.containsGroup(txn, g)) - addMessage(txn, m, null); - } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); + duplicate = db.containsMessage(txn, m.getId()); + if(!duplicate && db.containsGroup(txn, m.getGroup().getId())) + addMessage(txn, m, null); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } - if(!duplicate) - callListeners(new MessageAddedEvent(m.getGroup(), null)); + if(!duplicate) callListeners(new MessageAddedEvent(m.getGroup(), null)); } /** * Stores a message, initialises its status with respect to each contact, * and marks it as read if it was locally generated. * <p> - * Locking: contact read, message write, subscription read. + * Locking: write. * @param sender null for a locally generated message. */ private void addMessage(T txn, Message m, ContactId sender) @@ -408,60 +306,43 @@ DatabaseCleaner.Callback { public void addSecrets(Collection<TemporarySecret> secrets) throws DbException { - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - transportLock.readLock().lock(); + T txn = db.startTransaction(); try { - windowLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - Collection<TemporarySecret> relevant = - new ArrayList<TemporarySecret>(); - for(TemporarySecret s : secrets) { - ContactId c = s.getContactId(); - if(!db.containsContact(txn, c)) continue; - TransportId t = s.getTransportId(); - if(!db.containsTransport(txn, t)) continue; + Collection<TemporarySecret> relevant = + new ArrayList<TemporarySecret>(); + for(TemporarySecret s : secrets) { + if(db.containsContact(txn, s.getContactId())) + if(db.containsTransport(txn, s.getTransportId())) relevant.add(s); - } - if(!secrets.isEmpty()) db.addSecrets(txn, relevant); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - windowLock.writeLock().unlock(); } - } finally { - transportLock.readLock().unlock(); + if(!secrets.isEmpty()) db.addSecrets(txn, relevant); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } } public boolean addTransport(TransportId t, long maxLatency) throws DbException { boolean added; - transportLock.writeLock().lock(); + lock.writeLock().lock(); try { - windowLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - added = db.addTransport(txn, t, maxLatency); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - windowLock.writeLock().unlock(); + added = db.addTransport(txn, t, maxLatency); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - transportLock.writeLock().unlock(); + lock.writeLock().unlock(); } if(added) callListeners(new TransportAddedEvent(t, maxLatency)); return added; @@ -469,26 +350,21 @@ DatabaseCleaner.Callback { public Ack generateAck(ContactId c, int maxMessages) throws DbException { Collection<MessageId> ids; - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - messageLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - ids = db.getMessagesToAck(txn, c, maxMessages); - if(!ids.isEmpty()) db.lowerAckFlag(txn, c, ids); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - messageLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + ids = db.getMessagesToAck(txn, c, maxMessages); + if(!ids.isEmpty()) db.lowerAckFlag(txn, c, ids); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } if(ids.isEmpty()) return null; return new Ack(ids); @@ -498,35 +374,25 @@ DatabaseCleaner.Callback { long maxLatency) throws DbException { Collection<MessageId> ids; List<byte[]> messages = new ArrayList<byte[]>(); - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - messageLock.writeLock().lock(); + T txn = db.startTransaction(); try { - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - ids = db.getMessagesToSend(txn, c, maxLength); - for(MessageId m : ids) { - messages.add(db.getRawMessage(txn, m)); - db.updateExpiryTime(txn, c, m, maxLatency); - } - if(!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + ids = db.getMessagesToSend(txn, c, maxLength); + for(MessageId m : ids) { + messages.add(db.getRawMessage(txn, m)); + db.updateExpiryTime(txn, c, m, maxLatency); } - } finally { - messageLock.writeLock().unlock(); + if(!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } if(messages.isEmpty()) return null; return Collections.unmodifiableList(messages); @@ -535,32 +401,22 @@ DatabaseCleaner.Callback { public Offer generateOffer(ContactId c, int maxMessages, long maxLatency) throws DbException { Collection<MessageId> ids; - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - messageLock.writeLock().lock(); + T txn = db.startTransaction(); try { - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - ids = db.getMessagesToOffer(txn, c, maxMessages); - for(MessageId m : ids) - db.updateExpiryTime(txn, c, m, maxLatency); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + ids = db.getMessagesToOffer(txn, c, maxMessages); + for(MessageId m : ids) + db.updateExpiryTime(txn, c, m, maxLatency); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } if(ids.isEmpty()) return null; return new Offer(ids); @@ -569,26 +425,21 @@ DatabaseCleaner.Callback { public Request generateRequest(ContactId c, int maxMessages) throws DbException { Collection<MessageId> ids; - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - messageLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - ids = db.getMessagesToRequest(txn, c, maxMessages); - if(!ids.isEmpty()) db.removeOfferedMessages(txn, c, ids); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - messageLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + ids = db.getMessagesToRequest(txn, c, maxMessages); + if(!ids.isEmpty()) db.removeOfferedMessages(txn, c, ids); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } if(ids.isEmpty()) return null; return new Request(ids); @@ -598,199 +449,153 @@ DatabaseCleaner.Callback { long maxLatency) throws DbException { Collection<MessageId> ids; List<byte[]> messages = new ArrayList<byte[]>(); - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - messageLock.writeLock().lock(); + T txn = db.startTransaction(); try { - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - ids = db.getRequestedMessagesToSend(txn, c, maxLength); - for(MessageId m : ids) { - messages.add(db.getRawMessage(txn, m)); - db.updateExpiryTime(txn, c, m, maxLatency); - } - if(!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + ids = db.getRequestedMessagesToSend(txn, c, maxLength); + for(MessageId m : ids) { + messages.add(db.getRawMessage(txn, m)); + db.updateExpiryTime(txn, c, m, maxLatency); } - } finally { - messageLock.writeLock().unlock(); + if(!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } if(messages.isEmpty()) return null; return Collections.unmodifiableList(messages); } public RetentionAck generateRetentionAck(ContactId c) throws DbException { - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - retentionLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - RetentionAck a = db.getRetentionAck(txn, c); - db.commitTransaction(txn); - return a; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - retentionLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + RetentionAck a = db.getRetentionAck(txn, c); + db.commitTransaction(txn); + return a; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } } public RetentionUpdate generateRetentionUpdate(ContactId c, long maxLatency) throws DbException { - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - messageLock.readLock().lock(); + T txn = db.startTransaction(); try { - retentionLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - RetentionUpdate u = - db.getRetentionUpdate(txn, c, maxLatency); - db.commitTransaction(txn); - return u; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - retentionLock.writeLock().unlock(); - } - } finally { - messageLock.readLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + RetentionUpdate u = db.getRetentionUpdate(txn, c, maxLatency); + db.commitTransaction(txn); + return u; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } } public SubscriptionAck generateSubscriptionAck(ContactId c) throws DbException { - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - subscriptionLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - SubscriptionAck a = db.getSubscriptionAck(txn, c); - db.commitTransaction(txn); - return a; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + SubscriptionAck a = db.getSubscriptionAck(txn, c); + db.commitTransaction(txn); + return a; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } } public SubscriptionUpdate generateSubscriptionUpdate(ContactId c, long maxLatency) throws DbException { - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - subscriptionLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - SubscriptionUpdate u = - db.getSubscriptionUpdate(txn, c, maxLatency); - db.commitTransaction(txn); - return u; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + SubscriptionUpdate u = + db.getSubscriptionUpdate(txn, c, maxLatency); + db.commitTransaction(txn); + return u; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } } public Collection<TransportAck> generateTransportAcks(ContactId c) throws DbException { - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - transportLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - Collection<TransportAck> acks = db.getTransportAcks(txn, c); - db.commitTransaction(txn); - return acks; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + Collection<TransportAck> acks = db.getTransportAcks(txn, c); + db.commitTransaction(txn); + return acks; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } } public Collection<TransportUpdate> generateTransportUpdates(ContactId c, long maxLatency) throws DbException { - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - transportLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - Collection<TransportUpdate> updates = - db.getTransportUpdates(txn, c, maxLatency); - db.commitTransaction(txn); - return updates; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + Collection<TransportUpdate> updates = + db.getTransportUpdates(txn, c, maxLatency); + db.commitTransaction(txn); + return updates; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } } public Collection<GroupStatus> getAvailableGroups() throws DbException { - subscriptionLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -802,12 +607,12 @@ DatabaseCleaner.Callback { throw e; } } finally { - subscriptionLock.readLock().unlock(); + lock.readLock().unlock(); } } public TransportConfig getConfig(TransportId t) throws DbException { - transportLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -821,12 +626,12 @@ DatabaseCleaner.Callback { throw e; } } finally { - transportLock.readLock().unlock(); + lock.readLock().unlock(); } } public Contact getContact(ContactId c) throws DbException { - contactLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -840,34 +645,29 @@ DatabaseCleaner.Callback { throw e; } } finally { - contactLock.readLock().unlock(); + lock.readLock().unlock(); } } public Collection<Contact> getContacts() throws DbException { - contactLock.readLock().lock(); + lock.readLock().lock(); try { - windowLock.readLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - Collection<Contact> contacts = db.getContacts(txn); - db.commitTransaction(txn); - return contacts; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - windowLock.readLock().unlock(); + Collection<Contact> contacts = db.getContacts(txn); + db.commitTransaction(txn); + return contacts; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.readLock().unlock(); } } public Group getGroup(GroupId g) throws DbException { - subscriptionLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -881,12 +681,12 @@ DatabaseCleaner.Callback { throw e; } } finally { - subscriptionLock.readLock().unlock(); + lock.readLock().unlock(); } } public Collection<Group> getGroups() throws DbException { - subscriptionLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -898,72 +698,52 @@ DatabaseCleaner.Callback { throw e; } } finally { - subscriptionLock.readLock().unlock(); + lock.readLock().unlock(); } } public GroupId getInboxGroupId(ContactId c) throws DbException { - contactLock.readLock().lock(); + lock.readLock().lock(); try { - subscriptionLock.readLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - GroupId inbox = db.getInboxGroupId(txn, c); - db.commitTransaction(txn); - return inbox; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + GroupId inbox = db.getInboxGroupId(txn, c); + db.commitTransaction(txn); + return inbox; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.readLock().unlock(); } } public Collection<MessageHeader> getInboxMessageHeaders(ContactId c) throws DbException { - contactLock.readLock().lock(); + lock.readLock().lock(); try { - identityLock.readLock().lock(); + T txn = db.startTransaction(); try { - messageLock.readLock().lock(); - try { - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - Collection<MessageHeader> headers = - db.getInboxMessageHeaders(txn, c); - db.commitTransaction(txn); - return headers; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); - } - } finally { - messageLock.readLock().unlock(); - } - } finally { - identityLock.readLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + Collection<MessageHeader> headers = + db.getInboxMessageHeaders(txn, c); + db.commitTransaction(txn); + return headers; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.readLock().unlock(); } } public LocalAuthor getLocalAuthor(AuthorId a) throws DbException { - identityLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -977,12 +757,12 @@ DatabaseCleaner.Callback { throw e; } } finally { - identityLock.readLock().unlock(); + lock.readLock().unlock(); } } public Collection<LocalAuthor> getLocalAuthors() throws DbException { - identityLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -994,13 +774,13 @@ DatabaseCleaner.Callback { throw e; } } finally { - identityLock.readLock().unlock(); + lock.readLock().unlock(); } } public Map<TransportId, TransportProperties> getLocalProperties() throws DbException { - transportLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -1013,13 +793,13 @@ DatabaseCleaner.Callback { throw e; } } finally { - transportLock.readLock().unlock(); + lock.readLock().unlock(); } } public TransportProperties getLocalProperties(TransportId t) throws DbException { - transportLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -1033,12 +813,12 @@ DatabaseCleaner.Callback { throw e; } } finally { - transportLock.readLock().unlock(); + lock.readLock().unlock(); } } public byte[] getMessageBody(MessageId m) throws DbException { - messageLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -1052,38 +832,33 @@ DatabaseCleaner.Callback { throw e; } } finally { - messageLock.readLock().unlock(); + lock.readLock().unlock(); } } public Collection<MessageHeader> getMessageHeaders(GroupId g) throws DbException { - messageLock.readLock().lock(); + lock.readLock().lock(); try { - subscriptionLock.readLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsGroup(txn, g)) - throw new NoSuchSubscriptionException(); - Collection<MessageHeader> headers = - db.getMessageHeaders(txn, g); - db.commitTransaction(txn); - return headers; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); + if(!db.containsGroup(txn, g)) + throw new NoSuchSubscriptionException(); + Collection<MessageHeader> headers = + db.getMessageHeaders(txn, g); + db.commitTransaction(txn); + return headers; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - messageLock.readLock().unlock(); + lock.readLock().unlock(); } } public boolean getReadFlag(MessageId m) throws DbException { - messageLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -1097,13 +872,13 @@ DatabaseCleaner.Callback { throw e; } } finally { - messageLock.readLock().unlock(); + lock.readLock().unlock(); } } public Map<ContactId, TransportProperties> getRemoteProperties( TransportId t) throws DbException { - transportLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -1116,12 +891,12 @@ DatabaseCleaner.Callback { throw e; } } finally { - transportLock.readLock().unlock(); + lock.readLock().unlock(); } } public Collection<TemporarySecret> getSecrets() throws DbException { - windowLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -1133,12 +908,12 @@ DatabaseCleaner.Callback { throw e; } } finally { - windowLock.readLock().unlock(); + lock.readLock().unlock(); } } public Settings getSettings() throws DbException { - settingLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -1150,12 +925,12 @@ DatabaseCleaner.Callback { throw e; } } finally { - settingLock.readLock().unlock(); + lock.readLock().unlock(); } } public Collection<Contact> getSubscribers(GroupId g) throws DbException { - subscriptionLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -1167,12 +942,12 @@ DatabaseCleaner.Callback { throw e; } } finally { - subscriptionLock.readLock().unlock(); + lock.readLock().unlock(); } } public Map<TransportId, Long> getTransportLatencies() throws DbException { - transportLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -1185,12 +960,12 @@ DatabaseCleaner.Callback { throw e; } } finally { - transportLock.readLock().unlock(); + lock.readLock().unlock(); } } public Map<GroupId, Integer> getUnreadMessageCounts() throws DbException { - messageLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -1202,12 +977,12 @@ DatabaseCleaner.Callback { throw e; } } finally { - messageLock.readLock().unlock(); + lock.readLock().unlock(); } } public Collection<ContactId> getVisibility(GroupId g) throws DbException { - subscriptionLock.readLock().lock(); + lock.readLock().lock(); try { T txn = db.startTransaction(); try { @@ -1221,46 +996,35 @@ DatabaseCleaner.Callback { throw e; } } finally { - subscriptionLock.readLock().unlock(); + lock.readLock().unlock(); } } public long incrementConnectionCounter(ContactId c, TransportId t, long period) throws DbException { - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - transportLock.readLock().lock(); + T txn = db.startTransaction(); try { - windowLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - if(!db.containsTransport(txn, t)) - throw new NoSuchTransportException(); - long counter = db.incrementConnectionCounter(txn, c, t, - period); - db.commitTransaction(txn); - return counter; - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - windowLock.writeLock().unlock(); - } - } finally { - transportLock.readLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + if(!db.containsTransport(txn, t)) + throw new NoSuchTransportException(); + long counter = db.incrementConnectionCounter(txn, c, t, period); + db.commitTransaction(txn); + return counter; + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } } public void mergeConfig(TransportId t, TransportConfig c) throws DbException { - transportLock.writeLock().lock(); + lock.writeLock().lock(); try { T txn = db.startTransaction(); try { @@ -1273,14 +1037,14 @@ DatabaseCleaner.Callback { throw e; } } finally { - transportLock.writeLock().unlock(); + lock.writeLock().unlock(); } } public void mergeLocalProperties(TransportId t, TransportProperties p) throws DbException { boolean changed = false; - transportLock.writeLock().lock(); + lock.writeLock().lock(); try { T txn = db.startTransaction(); try { @@ -1296,14 +1060,14 @@ DatabaseCleaner.Callback { throw e; } } finally { - transportLock.writeLock().unlock(); + lock.writeLock().unlock(); } if(changed) callListeners(new LocalTransportsUpdatedEvent()); } public void mergeSettings(Settings s) throws DbException { boolean changed = false; - settingLock.writeLock().lock(); + lock.writeLock().lock(); try { T txn = db.startTransaction(); try { @@ -1317,113 +1081,90 @@ DatabaseCleaner.Callback { throw e; } } finally { - settingLock.writeLock().unlock(); + lock.writeLock().unlock(); } if(changed) callListeners(new SettingsUpdatedEvent()); } public void receiveAck(ContactId c, Ack a) throws DbException { Collection<MessageId> acked = new ArrayList<MessageId>(); - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - messageLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - for(MessageId m : a.getMessageIds()) { - if(db.containsVisibleMessage(txn, c, m)) { - db.raiseSeenFlag(txn, c, m); - acked.add(m); - } + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + for(MessageId m : a.getMessageIds()) { + if(db.containsVisibleMessage(txn, c, m)) { + db.raiseSeenFlag(txn, c, m); + acked.add(m); } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; } - } finally { - messageLock.writeLock().unlock(); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } callListeners(new MessagesAckedEvent(c, acked)); } public void receiveMessage(ContactId c, Message m) throws DbException { boolean duplicate, visible; - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - messageLock.writeLock().lock(); + T txn = db.startTransaction(); try { - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - duplicate = db.containsMessage(txn, m.getId()); - GroupId g = m.getGroup().getId(); - visible = db.containsVisibleGroup(txn, c, g); - if(!duplicate && visible) addMessage(txn, m, c); - if(visible) db.raiseAckFlag(txn, c, m.getId()); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.readLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + duplicate = db.containsMessage(txn, m.getId()); + visible = db.containsVisibleGroup(txn, c, m.getGroup().getId()); + if(visible) { + if(!duplicate) addMessage(txn, m, c); + db.raiseAckFlag(txn, c, m.getId()); } - } finally { - messageLock.writeLock().unlock(); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } + // FIXME: MessageAddedEvent should only be broadcast if msg is visible if(visible) callListeners(new MessageToAckEvent(c)); if(!duplicate) callListeners(new MessageAddedEvent(m.getGroup(), c)); } public void receiveOffer(ContactId c, Offer o) throws DbException { boolean ack = false, request = false; - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - messageLock.writeLock().lock(); + T txn = db.startTransaction(); try { - subscriptionLock.readLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - int count = db.countOfferedMessages(txn, c); - for(MessageId m : o.getMessageIds()) { - if(db.containsVisibleMessage(txn, c, m)) { - db.raiseSeenFlag(txn, c, m); - db.raiseAckFlag(txn, c, m); - ack = true; - } else if(count < MAX_OFFERED_MESSAGES) { - db.addOfferedMessage(txn, c, m); - request = true; - count++; - } - } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + int count = db.countOfferedMessages(txn, c); + for(MessageId m : o.getMessageIds()) { + if(db.containsVisibleMessage(txn, c, m)) { + db.raiseSeenFlag(txn, c, m); + db.raiseAckFlag(txn, c, m); + ack = true; + } else if(count < MAX_OFFERED_MESSAGES) { + db.addOfferedMessage(txn, c, m); + request = true; + count++; } - } finally { - subscriptionLock.readLock().unlock(); } - } finally { - messageLock.writeLock().unlock(); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } if(ack) callListeners(new MessageToAckEvent(c)); if(request) callListeners(new MessageToRequestEvent(c)); @@ -1431,269 +1172,196 @@ DatabaseCleaner.Callback { public void receiveRequest(ContactId c, Request r) throws DbException { boolean requested = false; - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - messageLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - for(MessageId m : r.getMessageIds()) { - if(db.containsVisibleMessage(txn, c, m)) { - db.raiseRequestedFlag(txn, c, m); - db.resetExpiryTime(txn, c, m); - requested = true; - } + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + for(MessageId m : r.getMessageIds()) { + if(db.containsVisibleMessage(txn, c, m)) { + db.raiseRequestedFlag(txn, c, m); + db.resetExpiryTime(txn, c, m); + requested = true; } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; } - } finally { - messageLock.writeLock().unlock(); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } if(requested) callListeners(new MessageRequestedEvent(c)); } public void receiveRetentionAck(ContactId c, RetentionAck a) throws DbException { - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - retentionLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - db.setRetentionUpdateAcked(txn, c, a.getVersion()); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - retentionLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + db.setRetentionUpdateAcked(txn, c, a.getVersion()); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } } public void receiveRetentionUpdate(ContactId c, RetentionUpdate u) throws DbException { boolean updated; - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - retentionLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - updated = db.setRetentionTime(txn, c, u.getRetentionTime(), - u.getVersion()); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - retentionLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + long retention = u.getRetentionTime(), version = u.getVersion(); + updated = db.setRetentionTime(txn, c, retention, version); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } if(updated) callListeners(new RemoteRetentionTimeUpdatedEvent(c)); } public void receiveSubscriptionAck(ContactId c, SubscriptionAck a) throws DbException { - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - subscriptionLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - db.setSubscriptionUpdateAcked(txn, c, a.getVersion()); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + db.setSubscriptionUpdateAcked(txn, c, a.getVersion()); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } } public void receiveSubscriptionUpdate(ContactId c, SubscriptionUpdate u) throws DbException { boolean updated; - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - messageLock.writeLock().lock(); + T txn = db.startTransaction(); try { - subscriptionLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - Collection<Group> groups = u.getGroups(); - long version = u.getVersion(); - updated = db.setGroups(txn, c, groups, version); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + updated = db.setGroups(txn, c, u.getGroups(), u.getVersion()); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } if(updated) callListeners(new RemoteSubscriptionsUpdatedEvent(c)); } public void receiveTransportAck(ContactId c, TransportAck a) throws DbException { - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - transportLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - TransportId t = a.getId(); - if(!db.containsTransport(txn, t)) - throw new NoSuchTransportException(); - db.setTransportUpdateAcked(txn, c, t, a.getVersion()); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + if(!db.containsTransport(txn, a.getId())) + throw new NoSuchTransportException(); + db.setTransportUpdateAcked(txn, c, a.getId(), a.getVersion()); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } } public void receiveTransportUpdate(ContactId c, TransportUpdate u) throws DbException { boolean updated; - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - transportLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - TransportId t = u.getId(); - TransportProperties p = u.getProperties(); - long version = u.getVersion(); - updated = db.setRemoteProperties(txn, c, t, p, version); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + TransportId t = u.getId(); + TransportProperties p = u.getProperties(); + long version = u.getVersion(); + updated = db.setRemoteProperties(txn, c, t, p, version); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } if(updated) callListeners(new RemoteTransportsUpdatedEvent(c, u.getId())); } public void removeContact(ContactId c) throws DbException { - contactLock.writeLock().lock(); + lock.writeLock().lock(); try { - messageLock.writeLock().lock(); + T txn = db.startTransaction(); try { - retentionLock.writeLock().lock(); - try { - subscriptionLock.writeLock().lock(); - try { - transportLock.writeLock().lock(); - try { - windowLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - GroupId g = db.getInboxGroupId(txn, c); - if(g != null) db.removeGroup(txn, g); - db.removeContact(txn, c); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - windowLock.writeLock().unlock(); - } - } finally { - transportLock.writeLock().unlock(); - } - } finally { - subscriptionLock.writeLock().unlock(); - } - } finally { - retentionLock.writeLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + GroupId g = db.getInboxGroupId(txn, c); + if(g != null) db.removeGroup(txn, g); + db.removeContact(txn, c); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.writeLock().unlock(); + lock.writeLock().unlock(); } callListeners(new ContactRemovedEvent(c)); } public void removeGroup(Group g) throws DbException { Collection<ContactId> affected; - messageLock.writeLock().lock(); + lock.writeLock().lock(); try { - subscriptionLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - GroupId id = g.getId(); - if(!db.containsGroup(txn, id)) - throw new NoSuchSubscriptionException(); - affected = db.getVisibility(txn, id); - db.removeGroup(txn, id); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); + GroupId id = g.getId(); + if(!db.containsGroup(txn, id)) + throw new NoSuchSubscriptionException(); + affected = db.getVisibility(txn, id); + db.removeGroup(txn, id); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - messageLock.writeLock().unlock(); + lock.writeLock().unlock(); } callListeners(new SubscriptionRemovedEvent(g)); callListeners(new LocalSubscriptionsUpdatedEvent(affected)); @@ -1701,141 +1369,90 @@ DatabaseCleaner.Callback { public void removeLocalAuthor(AuthorId a) throws DbException { Collection<ContactId> affected; - contactLock.writeLock().lock(); + lock.writeLock().lock(); try { - identityLock.writeLock().lock(); + T txn = db.startTransaction(); try { - messageLock.writeLock().lock(); - try { - retentionLock.writeLock().lock(); - try { - subscriptionLock.writeLock().lock(); - try { - transportLock.writeLock().lock(); - try { - windowLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsLocalAuthor(txn, a)) - throw new NoSuchLocalAuthorException(); - affected = db.getContacts(txn, a); - for(ContactId c : affected) { - GroupId g = db.getInboxGroupId(txn, c); - if(g != null) db.removeGroup(txn, g); - } - db.removeLocalAuthor(txn, a); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - windowLock.writeLock().unlock(); - } - } finally { - transportLock.writeLock().unlock(); - } - } finally { - subscriptionLock.writeLock().unlock(); - } - } finally { - retentionLock.writeLock().unlock(); - } - } finally { - messageLock.writeLock().unlock(); + if(!db.containsLocalAuthor(txn, a)) + throw new NoSuchLocalAuthorException(); + affected = db.getContacts(txn, a); + for(ContactId c : affected) { + GroupId g = db.getInboxGroupId(txn, c); + if(g != null) db.removeGroup(txn, g); } - } finally { - identityLock.writeLock().unlock(); + db.removeLocalAuthor(txn, a); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.writeLock().unlock(); + lock.writeLock().unlock(); } for(ContactId c : affected) callListeners(new ContactRemovedEvent(c)); callListeners(new LocalAuthorRemovedEvent(a)); } public void removeTransport(TransportId t) throws DbException { - transportLock.writeLock().lock(); + lock.writeLock().lock(); try { - windowLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsTransport(txn, t)) - throw new NoSuchTransportException(); - db.removeTransport(txn, t); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - windowLock.writeLock().unlock(); + if(!db.containsTransport(txn, t)) + throw new NoSuchTransportException(); + db.removeTransport(txn, t); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - transportLock.writeLock().unlock(); + lock.writeLock().unlock(); } callListeners(new TransportRemovedEvent(t)); } public void setConnectionWindow(ContactId c, TransportId t, long period, long centre, byte[] bitmap) throws DbException { - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - transportLock.readLock().lock(); + T txn = db.startTransaction(); try { - windowLock.writeLock().lock(); - try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - if(!db.containsTransport(txn, t)) - throw new NoSuchTransportException(); - db.setConnectionWindow(txn, c, t, period, centre, - bitmap); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - windowLock.writeLock().unlock(); - } - } finally { - transportLock.readLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + if(!db.containsTransport(txn, t)) + throw new NoSuchTransportException(); + db.setConnectionWindow(txn, c, t, period, centre, bitmap); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } } public void setInboxGroup(ContactId c, Group g) throws DbException { - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - subscriptionLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - db.setInboxGroup(txn, c, g); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - subscriptionLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + db.setInboxGroup(txn, c, g); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } } public void setReadFlag(MessageId m, boolean read) throws DbException { - messageLock.writeLock().lock(); + lock.writeLock().lock(); try { T txn = db.startTransaction(); try { @@ -1848,73 +1465,63 @@ DatabaseCleaner.Callback { throw e; } } finally { - messageLock.writeLock().unlock(); + lock.writeLock().unlock(); } } public void setRemoteProperties(ContactId c, Map<TransportId, TransportProperties> p) throws DbException { - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - transportLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsContact(txn, c)) - throw new NoSuchContactException(); - db.setRemoteProperties(txn, c, p); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; - } - } finally { - transportLock.writeLock().unlock(); + if(!db.containsContact(txn, c)) + throw new NoSuchContactException(); + db.setRemoteProperties(txn, c, p); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } } public void setVisibility(GroupId g, Collection<ContactId> visible) throws DbException { Collection<ContactId> affected = new ArrayList<ContactId>(); - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - subscriptionLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsGroup(txn, g)) - throw new NoSuchSubscriptionException(); - // Use HashSets for O(1) lookups, O(n) overall running time - HashSet<ContactId> now = new HashSet<ContactId>(visible); - Collection<ContactId> before = db.getVisibility(txn, g); - before = new HashSet<ContactId>(before); - // Set the group's visibility for each current contact - for(ContactId c : db.getContactIds(txn)) { - boolean wasBefore = before.contains(c); - boolean isNow = now.contains(c); - if(!wasBefore && isNow) { - db.addVisibility(txn, c, g); - affected.add(c); - } else if(wasBefore && !isNow) { - db.removeVisibility(txn, c, g); - affected.add(c); - } + if(!db.containsGroup(txn, g)) + throw new NoSuchSubscriptionException(); + // Use HashSets for O(1) lookups, O(n) overall running time + HashSet<ContactId> now = new HashSet<ContactId>(visible); + Collection<ContactId> before = db.getVisibility(txn, g); + before = new HashSet<ContactId>(before); + // Set the group's visibility for each current contact + for(ContactId c : db.getContactIds(txn)) { + boolean wasBefore = before.contains(c); + boolean isNow = now.contains(c); + if(!wasBefore && isNow) { + db.addVisibility(txn, c, g); + affected.add(c); + } else if(wasBefore && !isNow) { + db.removeVisibility(txn, c, g); + affected.add(c); } - // Make the group invisible to future contacts - db.setVisibleToAll(txn, g, false); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; } - } finally { - subscriptionLock.writeLock().unlock(); + // Make the group invisible to future contacts + db.setVisibleToAll(txn, g, false); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } if(!affected.isEmpty()) callListeners(new LocalSubscriptionsUpdatedEvent(affected)); @@ -1922,37 +1529,32 @@ DatabaseCleaner.Callback { public void setVisibleToAll(GroupId g, boolean all) throws DbException { Collection<ContactId> affected = new ArrayList<ContactId>(); - contactLock.readLock().lock(); + lock.writeLock().lock(); try { - subscriptionLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - if(!db.containsGroup(txn, g)) - throw new NoSuchSubscriptionException(); - // Make the group visible or invisible to future contacts - db.setVisibleToAll(txn, g, all); - if(all) { - // Make the group visible to all current contacts - Collection<ContactId> before = db.getVisibility(txn, g); - before = new HashSet<ContactId>(before); - for(ContactId c : db.getContactIds(txn)) { - if(!before.contains(c)) { - db.addVisibility(txn, c, g); - affected.add(c); - } + if(!db.containsGroup(txn, g)) + throw new NoSuchSubscriptionException(); + // Make the group visible or invisible to future contacts + db.setVisibleToAll(txn, g, all); + if(all) { + // Make the group visible to all current contacts + Collection<ContactId> before = db.getVisibility(txn, g); + before = new HashSet<ContactId>(before); + for(ContactId c : db.getContactIds(txn)) { + if(!before.contains(c)) { + db.addVisibility(txn, c, g); + affected.add(c); } } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; } - } finally { - subscriptionLock.writeLock().unlock(); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - contactLock.readLock().unlock(); + lock.writeLock().unlock(); } if(!affected.isEmpty()) callListeners(new LocalSubscriptionsUpdatedEvent(affected)); @@ -1979,29 +1581,24 @@ DatabaseCleaner.Callback { */ private boolean expireMessages(int size) throws DbException { Collection<MessageId> expired; - messageLock.writeLock().lock(); + lock.writeLock().lock(); try { - retentionLock.writeLock().lock(); + T txn = db.startTransaction(); try { - T txn = db.startTransaction(); - try { - expired = db.getOldMessages(txn, size); - if(!expired.isEmpty()) { - for(MessageId m : expired) db.removeMessage(txn, m); - db.incrementRetentionVersions(txn); - if(LOG.isLoggable(INFO)) - LOG.info("Expired " + expired.size() + " messages"); - } - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; + expired = db.getOldMessages(txn, size); + if(!expired.isEmpty()) { + for(MessageId m : expired) db.removeMessage(txn, m); + db.incrementRetentionVersions(txn); + if(LOG.isLoggable(INFO)) + LOG.info("Expired " + expired.size() + " messages"); } - } finally { - retentionLock.writeLock().unlock(); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; } } finally { - messageLock.writeLock().unlock(); + lock.writeLock().unlock(); } if(expired.isEmpty()) return false; callListeners(new MessageExpiredEvent()); diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java index 42214788e855005b03aae50fb4eddb0f58212ca9..b3988605631e036362019bfa05ebe466f17a67c2 100644 --- a/briar-core/src/org/briarproject/db/JdbcDatabase.java +++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java @@ -71,8 +71,6 @@ abstract class JdbcDatabase implements Database<Connection> { + " value VARCHAR NOT NULL," + " PRIMARY KEY (key))"; - // Locking: identity - // Dependents: contact, message, retention, subscription, transport, window private static final String CREATE_LOCAL_AUTHORS = "CREATE TABLE localAuthors" + " (authorId HASH NOT NULL," @@ -82,8 +80,6 @@ abstract class JdbcDatabase implements Database<Connection> { + " created BIGINT NOT NULL," + " PRIMARY KEY (authorId))"; - // Locking: contact - // Dependents: message, retention, subscription, transport, window private static final String CREATE_CONTACTS = "CREATE TABLE contacts" + " (contactId COUNTER," @@ -97,8 +93,6 @@ abstract class JdbcDatabase implements Database<Connection> { + " REFERENCES localAuthors (authorId)" + " ON DELETE CASCADE)"; - // Locking: subscription - // Dependents: message private static final String CREATE_GROUPS = "CREATE TABLE groups" + " (groupId HASH NOT NULL," @@ -107,7 +101,6 @@ abstract class JdbcDatabase implements Database<Connection> { + " visibleToAll BOOLEAN NOT NULL," + " PRIMARY KEY (groupId))"; - // Locking: subscription private static final String CREATE_GROUP_VISIBILITIES = "CREATE TABLE groupVisibilities" + " (contactId INT NOT NULL," @@ -121,7 +114,6 @@ abstract class JdbcDatabase implements Database<Connection> { + " REFERENCES groups (groupId)" + " ON DELETE CASCADE)"; - // Locking: subscription private static final String CREATE_CONTACT_GROUPS = "CREATE TABLE contactGroups" + " (contactId INT NOT NULL," @@ -133,7 +125,6 @@ abstract class JdbcDatabase implements Database<Connection> { + " REFERENCES contacts (contactId)" + " ON DELETE CASCADE)"; - // Locking: subscription private static final String CREATE_GROUP_VERSIONS = "CREATE TABLE groupVersions" + " (contactId INT NOT NULL," @@ -148,7 +139,6 @@ abstract class JdbcDatabase implements Database<Connection> { + " REFERENCES contacts (contactId)" + " ON DELETE CASCADE)"; - // Locking: message private static final String CREATE_MESSAGES = "CREATE TABLE messages" + " (messageId HASH NOT NULL," @@ -182,7 +172,6 @@ abstract class JdbcDatabase implements Database<Connection> { + " REFERENCES contacts (contactId)" + " ON DELETE CASCADE)"; - // Locking: message private static final String CREATE_STATUSES = "CREATE TABLE statuses" + " (messageId HASH NOT NULL," @@ -206,7 +195,6 @@ abstract class JdbcDatabase implements Database<Connection> { private static final String INDEX_STATUSES_BY_CONTACT = "CREATE INDEX statusesByContact ON statuses (contactId)"; - // Locking: retention private static final String CREATE_RETENTION_VERSIONS = "CREATE TABLE retentionVersions" + " (contactId INT NOT NULL," @@ -222,15 +210,12 @@ abstract class JdbcDatabase implements Database<Connection> { + " REFERENCES contacts (contactId)" + " ON DELETE CASCADE)"; - // Locking: transport - // Dependents: window private static final String CREATE_TRANSPORTS = "CREATE TABLE transports" + " (transportId VARCHAR NOT NULL," + " maxLatency BIGINT NOT NULL," + " PRIMARY KEY (transportId))"; - // Locking: transport private static final String CREATE_TRANSPORT_CONFIGS = "CREATE TABLE transportConfigs" + " (transportId VARCHAR NOT NULL," @@ -241,7 +226,6 @@ abstract class JdbcDatabase implements Database<Connection> { + " REFERENCES transports (transportId)" + " ON DELETE CASCADE)"; - // Locking: transport private static final String CREATE_TRANSPORT_PROPS = "CREATE TABLE transportProperties" + " (transportId VARCHAR NOT NULL," @@ -252,7 +236,6 @@ abstract class JdbcDatabase implements Database<Connection> { + " REFERENCES transports (transportId)" + " ON DELETE CASCADE)"; - // Locking: transport private static final String CREATE_TRANSPORT_VERSIONS = "CREATE TABLE transportVersions" + " (contactId INT NOT NULL," @@ -269,7 +252,6 @@ abstract class JdbcDatabase implements Database<Connection> { + " REFERENCES transports (transportId)" + " ON DELETE CASCADE)"; - // Locking: transport private static final String CREATE_CONTACT_TRANSPORT_PROPS = "CREATE TABLE contactTransportProperties" + " (contactId INT NOT NULL," @@ -281,7 +263,6 @@ abstract class JdbcDatabase implements Database<Connection> { + " REFERENCES contacts (contactId)" + " ON DELETE CASCADE)"; - // Locking: transport private static final String CREATE_CONTACT_TRANSPORT_VERSIONS = "CREATE TABLE contactTransportVersions" + " (contactId INT NOT NULL," @@ -293,7 +274,6 @@ abstract class JdbcDatabase implements Database<Connection> { + " REFERENCES contacts (contactId)" + " ON DELETE CASCADE)"; - // Locking: window private static final String CREATE_ENDPOINTS = "CREATE TABLE endpoints" + " (contactId INT NOT NULL," @@ -308,7 +288,6 @@ abstract class JdbcDatabase implements Database<Connection> { + " REFERENCES transports (transportId)" + " ON DELETE CASCADE)"; - // Locking: window private static final String CREATE_SECRETS = "CREATE TABLE secrets" + " (contactId INT NOT NULL,"