Add DB method for checking whether there's anything to send

This commit is contained in:
akwizgran
2021-06-16 16:25:11 +01:00
committed by Torsten Grote
parent 4cf5242aa5
commit 796cbcaf4b
5 changed files with 191 additions and 97 deletions

View File

@@ -118,6 +118,18 @@ public interface DatabaseComponent extends TransactionManager {
KeySetId addTransportKeys(Transaction txn, PendingContactId p, KeySetId addTransportKeys(Transaction txn, PendingContactId p,
TransportKeys k) throws DbException; TransportKeys k) throws DbException;
/**
* Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsAnythingToSend(Transaction txn, ContactId c,
int maxLatency, boolean eager) throws DbException;
/** /**
* Returns true if the database contains the given contact for the given * Returns true if the database contains the given contact for the given
* local pseudonym. * local pseudonym.

View File

@@ -162,6 +162,18 @@ interface Database<T> {
KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k) KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k)
throws DbException; throws DbException;
/**
* Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsAnythingToSend(T txn, ContactId c, int maxLatency,
boolean eager) throws DbException;
/** /**
* Returns true if the database contains the given contact for the given * Returns true if the database contains the given contact for the given
* local pseudonym. * local pseudonym.

View File

@@ -341,6 +341,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.addTransportKeys(txn, p, k); return db.addTransportKeys(txn, p, k);
} }
@Override
public boolean containsAnythingToSend(Transaction transaction, ContactId c,
int maxLatency, boolean eager) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.containsAnythingToSend(txn, c, maxLatency, eager);
}
@Override @Override
public boolean containsContact(Transaction transaction, AuthorId remote, public boolean containsContact(Transaction transaction, AuthorId remote,
AuthorId local) throws DbException { AuthorId local) throws DbException {

View File

@@ -1127,6 +1127,55 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public boolean containsAnythingToSend(Connection txn, ContactId c,
int maxLatency, boolean eager) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND ack = TRUE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
boolean acksToSend = rs.next();
rs.close();
ps.close();
if (acksToSend) return true;
if (eager) {
sql = "SELECT NULL from statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
} else {
long now = clock.currentTimeMillis();
long eta = now + maxLatency;
sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " AND (expiry <= ? OR eta > ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now);
ps.setLong(4, eta);
}
rs = ps.executeQuery();
boolean messagesToSend = rs.next();
rs.close();
ps.close();
return messagesToSend;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public boolean containsContact(Connection txn, AuthorId remote, public boolean containsContact(Connection txn, AuthorId remote,
AuthorId local) throws DbException { AuthorId local) throws DbException {

View File

@@ -57,6 +57,7 @@ import java.util.concurrent.atomic.AtomicLong;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
@@ -222,21 +223,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message, DELIVERED, true, false, null); db.addMessage(txn, message, DELIVERED, true, false, null);
// The contact has not seen the message, so it should be sendable // The contact has not seen the message, so it should be sendable
Collection<MessageId> ids = assertOneMessageToSendEagerly(db, txn);
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertOneMessageToSendLazily(db, txn);
assertEquals(singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
assertEquals(message.getRawLength(),
db.getUnackedMessageBytesToSend(txn, contactId));
// Changing the status to seen = true should make the message unsendable // Changing the status to seen = true should make the message unsendable
db.raiseSeenFlag(txn, contactId, messageId); db.raiseSeenFlag(txn, contactId, messageId);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendEagerly(db, txn);
assertTrue(ids.isEmpty()); assertNothingToSendLazily(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -256,37 +249,23 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message, UNKNOWN, true, false, null); db.addMessage(txn, message, UNKNOWN, true, false, null);
// The message has not been validated, so it should not be sendable // The message has not been validated, so it should not be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, assertNothingToSendLazily(db, txn);
ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendEagerly(db, txn);
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId));
// Marking the message delivered should make it sendable // Marking the message delivered should make it sendable
db.setMessageState(txn, messageId, DELIVERED); db.setMessageState(txn, messageId, DELIVERED);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertOneMessageToSendLazily(db, txn);
assertEquals(singletonList(messageId), ids); assertOneMessageToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
assertEquals(message.getRawLength(),
db.getUnackedMessageBytesToSend(txn, contactId));
// Marking the message invalid should make it unsendable // Marking the message invalid should make it unsendable
db.setMessageState(txn, messageId, INVALID); db.setMessageState(txn, messageId, INVALID);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendLazily(db, txn);
assertTrue(ids.isEmpty()); assertNothingToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId));
// Marking the message pending should make it unsendable // Marking the message pending should make it unsendable
db.setMessageState(txn, messageId, PENDING); db.setMessageState(txn, messageId, PENDING);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendLazily(db, txn);
assertTrue(ids.isEmpty()); assertNothingToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -305,45 +284,28 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message, DELIVERED, true, false, null); db.addMessage(txn, message, DELIVERED, true, false, null);
// The group is invisible, so the message should not be sendable // The group is invisible, so the message should not be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, assertNothingToSendLazily(db, txn);
ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendEagerly(db, txn);
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId));
// Making the group visible should not make the message sendable // Making the group visible should not make the message sendable
db.addGroupVisibility(txn, contactId, groupId, false); db.addGroupVisibility(txn, contactId, groupId, false);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendLazily(db, txn);
assertTrue(ids.isEmpty()); assertNothingToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId));
// Sharing the group should make the message sendable // Sharing the group should make the message sendable
db.setGroupVisibility(txn, contactId, groupId, true); db.setGroupVisibility(txn, contactId, groupId, true);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertOneMessageToSendEagerly(db, txn);
assertEquals(singletonList(messageId), ids); assertOneMessageToSendLazily(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
assertEquals(message.getRawLength(),
db.getUnackedMessageBytesToSend(txn, contactId));
// Unsharing the group should make the message unsendable // Unsharing the group should make the message unsendable
db.setGroupVisibility(txn, contactId, groupId, false); db.setGroupVisibility(txn, contactId, groupId, false);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendLazily(db, txn);
assertTrue(ids.isEmpty()); assertNothingToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId));
// Making the group invisible should make the message unsendable // Making the group invisible should make the message unsendable
db.removeGroupVisibility(txn, contactId, groupId); db.removeGroupVisibility(txn, contactId, groupId);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendLazily(db, txn);
assertTrue(ids.isEmpty()); assertNothingToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -363,21 +325,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message, DELIVERED, false, false, null); db.addMessage(txn, message, DELIVERED, false, false, null);
// The message is not shared, so it should not be sendable // The message is not shared, so it should not be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, assertNothingToSendLazily(db, txn);
ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendEagerly(db, txn);
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId));
// Sharing the message should make it sendable // Sharing the message should make it sendable
db.setMessageShared(txn, messageId, true); db.setMessageShared(txn, messageId, true);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertOneMessageToSendLazily(db, txn);
assertEquals(singletonList(messageId), ids); assertOneMessageToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
assertEquals(message.getRawLength(),
db.getUnackedMessageBytesToSend(txn, contactId));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -397,19 +351,17 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message, DELIVERED, true, false, null); db.addMessage(txn, message, DELIVERED, true, false, null);
// The message is sendable, but too large to send // The message is sendable, but too large to send
assertOneMessageToSendLazily(db, txn);
assertOneMessageToSendEagerly(db, txn);
Collection<MessageId> ids = Collection<MessageId> ids =
db.getMessagesToSend(txn, contactId, message.getRawLength() - 1, db.getMessagesToSend(txn, contactId, message.getRawLength() - 1,
MAX_LATENCY); MAX_LATENCY);
assertTrue(ids.isEmpty()); assertTrue(ids.isEmpty());
assertEquals(message.getRawLength(),
db.getUnackedMessageBytesToSend(txn, contactId));
// The message is just the right size to send // The message is just the right size to send
ids = db.getMessagesToSend(txn, contactId, message.getRawLength(), ids = db.getMessagesToSend(txn, contactId, message.getRawLength(),
MAX_LATENCY); MAX_LATENCY);
assertEquals(singletonList(messageId), ids); assertEquals(singletonList(messageId), ids);
assertEquals(message.getRawLength(),
db.getUnackedMessageBytesToSend(txn, contactId));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -427,6 +379,12 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, false); db.addGroupVisibility(txn, contactId, groupId, false);
// Initially there should be nothing to send
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
// Add some messages to ack // Add some messages to ack
Message message1 = getMessage(groupId); Message message1 = getMessage(groupId);
MessageId messageId1 = message1.getId(); MessageId messageId1 = message1.getId();
@@ -434,6 +392,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message1, DELIVERED, true, false, contactId); db.addMessage(txn, message1, DELIVERED, true, false, contactId);
// Both message IDs should be returned // Both message IDs should be returned
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234); Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
assertEquals(asList(messageId, messageId1), ids); assertEquals(asList(messageId, messageId1), ids);
@@ -441,6 +403,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1)); db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
// Both message IDs should have been removed // Both message IDs should have been removed
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
assertEquals(emptyList(), db.getMessagesToAck(txn, assertEquals(emptyList(), db.getMessagesToAck(txn,
contactId, 1234)); contactId, 1234));
@@ -449,6 +415,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.raiseAckFlag(txn, contactId, messageId1); db.raiseAckFlag(txn, contactId, messageId1);
// Both message IDs should be returned // Both message IDs should be returned
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
ids = db.getMessagesToAck(txn, contactId, 1234); ids = db.getMessagesToAck(txn, contactId, 1234);
assertEquals(asList(messageId, messageId1), ids); assertEquals(asList(messageId, messageId1), ids);
@@ -469,22 +439,25 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, false, null); db.addMessage(txn, message, DELIVERED, true, false, null);
// Retrieve the message from the database and mark it as sent // The message should be sendable via lazy or eager retransmission
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, assertOneMessageToSendLazily(db, txn);
ONE_MEGABYTE, MAX_LATENCY); assertOneMessageToSendEagerly(db, txn);
assertEquals(singletonList(messageId), ids);
// Mark the message as sent
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY); db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
// The message should no longer be sendable // The message should no longer be sendable via lazy retransmission,
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); // but it should still be sendable via eager retransmission
assertTrue(ids.isEmpty()); assertNothingToSendLazily(db, txn);
assertOneMessageToSendEagerly(db, txn);
// Pretend that the message was acked // Mark the message as acked
db.raiseSeenFlag(txn, contactId, messageId); db.raiseSeenFlag(txn, contactId, messageId);
// The message still should not be sendable // The message still should not be sendable via lazy or eager
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); // retransmission
assertTrue(ids.isEmpty()); assertNothingToSendLazily(db, txn);
assertNothingToSendEagerly(db, txn);
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -1957,11 +1930,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(db.containsVisibleMessage(txn, contactId, messageId)); assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
// The message should be sendable // The message should be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, assertOneMessageToSendLazily(db, txn);
ONE_MEGABYTE, MAX_LATENCY); assertOneMessageToSendEagerly(db, txn);
assertEquals(singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
// The message should be available // The message should be available
Message m = db.getMessage(txn, messageId); Message m = db.getMessage(txn, messageId);
@@ -1977,10 +1947,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(db.containsVisibleMessage(txn, contactId, messageId)); assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
// The message should not be sendable // The message should not be sendable
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); assertNothingToSendLazily(db, txn);
assertTrue(ids.isEmpty()); assertNothingToSendEagerly(db, txn);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
// Requesting the message should throw an exception // Requesting the message should throw an exception
try { try {
@@ -2609,6 +2577,50 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
deleteTestDirectory(testDir); deleteTestDirectory(testDir);
} }
private void assertNothingToSendLazily(Database<Connection> db,
Connection txn) throws Exception {
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
Collection<MessageId> ids =
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertTrue(ids.isEmpty());
}
private void assertOneMessageToSendLazily(Database<Connection> db,
Connection txn) throws Exception {
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
Collection<MessageId> ids =
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
}
private void assertNothingToSendEagerly(Database<Connection> db,
Connection txn) throws Exception {
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
Map<MessageId, Integer> unacked =
db.getUnackedMessagesToSend(txn, contactId);
assertTrue(unacked.isEmpty());
assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId));
}
private void assertOneMessageToSendEagerly(Database<Connection> db,
Connection txn) throws Exception {
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
Map<MessageId, Integer> unacked =
db.getUnackedMessagesToSend(txn, contactId);
assertEquals(singleton(messageId), unacked.keySet());
assertEquals(message.getRawLength(), unacked.get(messageId).intValue());
assertEquals(message.getRawLength(),
db.getUnackedMessageBytesToSend(txn, contactId));
}
private static class StoppedClock implements Clock { private static class StoppedClock implements Clock {
private final long time; private final long time;