Improved encapsulation of thread synchronisation as follows

- replaced use of Object instance mutex with a private final Lock object
- replaced Object signaling with specific condition signalling
This commit is contained in:
Abraham Kiggundu
2014-12-23 23:55:56 +03:00
parent 276dcb1038
commit b074978472
19 changed files with 1001 additions and 478 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
build
.gradle
.metadata
*.tmp

View File

@@ -11,6 +11,8 @@ import static java.util.logging.Level.WARNING;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import javax.inject.Inject;
@@ -65,6 +67,8 @@ Service, EventListener {
private volatile Settings settings = new Settings();
private final Lock synchLock = new ReentrantLock();
@Inject
public AndroidNotificationManagerImpl(DatabaseComponent db,
@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
@@ -103,20 +107,32 @@ Service, EventListener {
if(e instanceof SettingsUpdatedEvent) loadSettings();
}
public synchronized void showPrivateMessageNotification(ContactId c) {
public void showPrivateMessageNotification(ContactId c) {
synchLock.lock();
try{
Integer count = contactCounts.get(c);
if(count == null) contactCounts.put(c, 1);
else contactCounts.put(c, count + 1);
privateTotal++;
updatePrivateMessageNotification();
}
finally{
synchLock.unlock();
}
}
public synchronized void clearPrivateMessageNotification(ContactId c) {
public void clearPrivateMessageNotification(ContactId c) {
synchLock.lock();
try{
Integer count = contactCounts.remove(c);
if(count == null) return; // Already cleared
privateTotal -= count;
updatePrivateMessageNotification();
}
finally{
synchLock.unlock();
}
}
// Locking: this
private void updatePrivateMessageNotification() {
@@ -180,20 +196,32 @@ Service, EventListener {
return defaults;
}
public synchronized void showGroupPostNotification(GroupId g) {
public void showGroupPostNotification(GroupId g) {
synchLock.lock();
try{
Integer count = groupCounts.get(g);
if(count == null) groupCounts.put(g, 1);
else groupCounts.put(g, count + 1);
groupTotal++;
updateGroupPostNotification();
}
finally{
synchLock.unlock();
}
}
public synchronized void clearGroupPostNotification(GroupId g) {
public void clearGroupPostNotification(GroupId g) {
synchLock.lock();
try{
Integer count = groupCounts.remove(g);
if(count == null) return; // Already cleared
groupTotal -= count;
updateGroupPostNotification();
}
finally{
synchLock.unlock();
}
}
// Locking: this
private void updateGroupPostNotification() {
@@ -238,18 +266,23 @@ Service, EventListener {
}
}
// Locking: this
private void clearGroupPostNotification() {
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
nm.cancel(GROUP_POST_NOTIFICATION_ID);
}
public synchronized void clearNotifications() {
public void clearNotifications() {
synchLock.lock();
try{
contactCounts.clear();
groupCounts.clear();
privateTotal = groupTotal = 0;
clearPrivateMessageNotification();
clearGroupPostNotification();
}
finally{
synchLock.unlock();
}
}
}

View File

@@ -4,6 +4,8 @@ import static java.util.logging.Level.INFO;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import org.briarproject.api.android.ReferenceManager;
@@ -13,13 +15,16 @@ class ReferenceManagerImpl implements ReferenceManager {
private static final Logger LOG =
Logger.getLogger(ReferenceManagerImpl.class.getName());
// Locking: this
private final Map<Class<?>, Map<Long, Object>> outerMap =
new HashMap<Class<?>, Map<Long, Object>>();
private long nextHandle = 0; // Locking: this
public synchronized <T> T getReference(long handle, Class<T> c) {
private final Lock synchLock = new ReentrantLock();
public <T> T getReference(long handle, Class<T> c) {
synchLock.lock();
try{
Map<Long, Object> innerMap = outerMap.get(c);
if(innerMap == null) {
if(LOG.isLoggable(INFO))
@@ -31,8 +36,15 @@ class ReferenceManagerImpl implements ReferenceManager {
Object o = innerMap.get(handle);
return c.cast(o);
}
finally{
synchLock.unlock();
}
public synchronized <T> long putReference(T reference, Class<T> c) {
}
public <T> long putReference(T reference, Class<T> c) {
synchLock.lock();
try{
Map<Long, Object> innerMap = outerMap.get(c);
if(innerMap == null) {
innerMap = new HashMap<Long, Object>();
@@ -46,8 +58,14 @@ class ReferenceManagerImpl implements ReferenceManager {
}
return handle;
}
finally{
synchLock.unlock();
}
}
public synchronized <T> T removeReference(long handle, Class<T> c) {
public <T> T removeReference(long handle, Class<T> c) {
synchLock.lock();
try{
Map<Long, Object> innerMap = outerMap.get(c);
if(innerMap == null) return null;
Object o = innerMap.remove(handle);
@@ -58,4 +76,9 @@ class ReferenceManagerImpl implements ReferenceManager {
}
return c.cast(o);
}
finally{
synchLock.unlock();
}
}
}

View File

@@ -1,5 +1,8 @@
package org.briarproject.crypto;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.briarproject.api.crypto.MessageDigest;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.digests.SHA256Digest;
@@ -24,19 +27,30 @@ class FortunaGenerator {
private final byte[] buffer = new byte[BLOCK_BYTES];
private final byte[] newKey = new byte[KEY_BYTES];
private final Lock synchLock = new ReentrantLock();
FortunaGenerator(byte[] seed) {
reseed(seed);
}
synchronized void reseed(byte[] seed) {
void reseed(byte[] seed) {
synchLock.lock();
try{
digest.update(key);
digest.update(seed);
digest.digest(key, 0, KEY_BYTES);
incrementCounter();
}
finally{
synchLock.unlock();
}
}
// Package access for testing
synchronized void incrementCounter() {
void incrementCounter() {
synchLock.lock();
try{
counter[0]++;
for(int i = 0; counter[i] == 0; i++) {
if(i + 1 == BLOCK_BYTES)
@@ -44,13 +58,26 @@ class FortunaGenerator {
counter[i + 1]++;
}
}
// Package access for testing
synchronized byte[] getCounter() {
return counter;
finally{
synchLock.unlock();
}
}
synchronized int nextBytes(byte[] dest, int off, int len) {
// Package access for testing
byte[] getCounter() {
synchLock.lock();
try{
return counter;
}
finally{
synchLock.unlock();
}
}
int nextBytes(byte[] dest, int off, int len) {
synchLock.lock();
try{
// Don't write more than the maximum number of bytes in one request
if(len > MAX_BYTES_PER_REQUEST) len = MAX_BYTES_PER_REQUEST;
cipher.init(true, new KeyParameter(key));
@@ -80,4 +107,8 @@ class FortunaGenerator {
// Return the number of bytes written
return len;
}
finally{
synchLock.unlock();
}
}
}

View File

@@ -1,5 +1,8 @@
package org.briarproject.crypto;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.briarproject.api.crypto.MessageDigest;
import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.util.ByteUtils;
@@ -11,6 +14,8 @@ class PseudoRandomImpl implements PseudoRandom {
private byte[] state;
private int offset;
private final Lock synchLock = new ReentrantLock();
PseudoRandomImpl(MessageDigest messageDigest, int seed1, int seed2) {
this.messageDigest = messageDigest;
byte[] seedBytes = new byte[8];
@@ -21,7 +26,9 @@ class PseudoRandomImpl implements PseudoRandom {
offset = 0;
}
public synchronized byte[] nextBytes(int bytes) {
public byte[] nextBytes(int bytes) {
synchLock.lock();
try{
byte[] b = new byte[bytes];
int half = state.length / 2;
int off = 0, len = b.length, available = half - offset;
@@ -38,4 +45,8 @@ class PseudoRandomImpl implements PseudoRandom {
offset += len;
return b;
}
finally{
synchLock.unlock();
}
}
}

View File

@@ -1,5 +1,8 @@
package org.briarproject.crypto;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.util.ByteUtils;
@@ -9,22 +12,37 @@ class SecretKeyImpl implements SecretKey {
private boolean erased = false; // Locking: this
private final Lock synchLock = new ReentrantLock();
SecretKeyImpl(byte[] key) {
this.key = key;
}
public synchronized byte[] getEncoded() {
public byte[] getEncoded() {
synchLock.lock();
try{
if(erased) throw new IllegalStateException();
return key;
}
finally{
synchLock.unlock();
}
}
public SecretKey copy() {
return new SecretKeyImpl(key.clone());
}
public synchronized void erase() {
public void erase() {
synchLock.lock();
try{
if(erased) throw new IllegalStateException();
ByteUtils.erase(key);
erased = true;
}
finally{
synchLock.unlock();
}
}
}

View File

@@ -27,6 +27,9 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import org.briarproject.api.Author;
@@ -323,6 +326,9 @@ abstract class JdbcDatabase implements Database<Connection> {
protected abstract Connection createConnection() throws SQLException;
protected abstract void flushBuffersToDisk(Statement s) throws SQLException;
private final Lock connectionsLock = new ReentrantLock();
private final Condition connectionsChanged = connectionsLock.newCondition();
JdbcDatabase(String hashType, String binaryType, String counterType,
String secretType, Clock clock) {
this.hashType = hashType;
@@ -431,19 +437,28 @@ abstract class JdbcDatabase implements Database<Connection> {
public Connection startTransaction() throws DbException {
Connection txn = null;
synchronized(connections) {
connectionsLock.lock();
try {
if(closed) throw new DbClosedException();
txn = connections.poll();
}
finally{
connectionsLock.unlock();
}
try {
if(txn == null) {
// Open a new connection
txn = createConnection();
if(txn == null) throw new DbException();
txn.setAutoCommit(false);
synchronized(connections) {
connectionsLock.lock();
try {
openConnections++;
}
finally{
connectionsLock.unlock();
}
}
} catch(SQLException e) {
throw new DbException(e);
@@ -455,9 +470,13 @@ abstract class JdbcDatabase implements Database<Connection> {
public void abortTransaction(Connection txn) {
try {
txn.rollback();
synchronized(connections) {
connectionsLock.lock();
try {
connections.add(txn);
connections.notifyAll();
connectionsChanged.signalAll();
}
finally{
connectionsLock.unlock();
}
} catch(SQLException e) {
// Try to close the connection
@@ -468,11 +487,14 @@ abstract class JdbcDatabase implements Database<Connection> {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e1.toString(), e1);
}
// Whatever happens, allow the database to close
synchronized(connections) {
connectionsLock.lock();
try {
openConnections--;
connections.notifyAll();
}
connectionsChanged.signalAll();
}
finally{
connectionsLock.unlock();
} }
}
public void commitTransaction(Connection txn) throws DbException {
@@ -486,9 +508,13 @@ abstract class JdbcDatabase implements Database<Connection> {
tryToClose(s);
throw new DbException(e);
}
synchronized(connections) {
connectionsLock.lock();
try{
connections.add(txn);
connections.notifyAll();
connectionsChanged.signalAll();
}
finally{
connectionsLock.unlock();
}
}
@@ -502,14 +528,15 @@ abstract class JdbcDatabase implements Database<Connection> {
protected void closeAllConnections() throws SQLException {
boolean interrupted = false;
synchronized(connections) {
connectionsLock.lock();
try{
closed = true;
for(Connection c : connections) c.close();
openConnections -= connections.size();
connections.clear();
while(openConnections > 0) {
try {
connections.wait();
connectionsChanged.await();
} catch(InterruptedException e) {
LOG.warning("Interrupted while closing connections");
interrupted = true;
@@ -519,6 +546,10 @@ abstract class JdbcDatabase implements Database<Connection> {
connections.clear();
}
}
finally{
connectionsLock.unlock();
}
if(interrupted) Thread.currentThread().interrupt();
}

View File

@@ -10,6 +10,8 @@ import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import org.briarproject.api.Author;
@@ -61,6 +63,8 @@ class ConnectorGroup extends Thread implements InvitationTask {
private final AtomicBoolean connected;
private final CountDownLatch localConfirmationLatch;
private final Lock synchLock = new ReentrantLock();
/*
* All of the following require locking: this. We don't want to call the
* listeners with a lock held, but we need to avoid a race condition in
@@ -104,13 +108,19 @@ class ConnectorGroup extends Thread implements InvitationTask {
localConfirmationLatch = new CountDownLatch(1);
}
public synchronized InvitationState addListener(InvitationListener l) {
public InvitationState addListener(InvitationListener l) {
synchLock.lock();
try{
listeners.add(l);
return new InvitationState(localInvitationCode, remoteInvitationCode,
localConfirmationCode, remoteConfirmationCode, connected.get(),
connectionFailed, localCompared, remoteCompared, localMatched,
remoteMatched, remoteName);
}
finally{
synchLock.unlock();
}
}
public void removeListener(InvitationListener l) {
listeners.remove(l);
@@ -130,9 +140,13 @@ class ConnectorGroup extends Thread implements InvitationTask {
localProps = db.getLocalProperties();
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
synchronized(this) {
synchLock.lock();
try {
connectionFailed = true;
}
finally{
synchLock.unlock();
}
for(InvitationListener l : listeners) l.connectionFailed();
return;
}
@@ -163,9 +177,13 @@ class ConnectorGroup extends Thread implements InvitationTask {
}
// If none of the threads connected, inform the listeners
if(!connected.get()) {
synchronized(this) {
synchLock.lock();
try {
connectionFailed = true;
}
finally{
synchLock.unlock();
}
for(InvitationListener l : listeners) l.connectionFailed();
}
}
@@ -193,18 +211,26 @@ class ConnectorGroup extends Thread implements InvitationTask {
}
public void localConfirmationSucceeded() {
synchronized(this) {
synchLock.lock();
try {
localCompared = true;
localMatched = true;
}
finally{
synchLock.unlock();
}
localConfirmationLatch.countDown();
}
public void localConfirmationFailed() {
synchronized(this) {
synchLock.lock();
try {
localCompared = true;
localMatched = false;
}
finally{
synchLock.unlock();
}
localConfirmationLatch.countDown();
}
@@ -216,10 +242,14 @@ class ConnectorGroup extends Thread implements InvitationTask {
}
void keyAgreementSucceeded(int localCode, int remoteCode) {
synchronized(this) {
synchLock.lock();
try {
localConfirmationCode = localCode;
remoteConfirmationCode = remoteCode;
}
finally{
synchLock.unlock();
}
for(InvitationListener l : listeners)
l.keyAgreementSucceeded(localCode, remoteCode);
}
@@ -230,32 +260,48 @@ class ConnectorGroup extends Thread implements InvitationTask {
boolean waitForLocalConfirmationResult() throws InterruptedException {
localConfirmationLatch.await(CONFIRMATION_TIMEOUT, MILLISECONDS);
synchronized(this) {
synchLock.lock();
try {
return localMatched;
}
finally{
synchLock.unlock();
}
}
void remoteConfirmationSucceeded() {
synchronized(this) {
synchLock.lock();
try {
remoteCompared = true;
remoteMatched = true;
}
finally{
synchLock.unlock();
}
for(InvitationListener l : listeners) l.remoteConfirmationSucceeded();
}
void remoteConfirmationFailed() {
synchronized(this) {
synchLock.lock();
try {
remoteCompared = true;
remoteMatched = false;
}
finally{
synchLock.unlock();
}
for(InvitationListener l : listeners) l.remoteConfirmationFailed();
}
void pseudonymExchangeSucceeded(Author remoteAuthor) {
String name = remoteAuthor.getName();
synchronized(this) {
synchLock.lock();
try {
remoteName = name;
}
finally{
synchLock.unlock();
}
for(InvitationListener l : listeners)
l.pseudonymExchangeSucceeded(name);
}

View File

@@ -2,6 +2,8 @@ package org.briarproject.lifecycle;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.briarproject.api.lifecycle.ShutdownManager;
@@ -11,25 +13,41 @@ class ShutdownManagerImpl implements ShutdownManager {
private int nextHandle = 0; // Locking: this
private final Lock synchLock = new ReentrantLock();
ShutdownManagerImpl() {
hooks = new HashMap<Integer, Thread>();
}
public synchronized int addShutdownHook(Runnable r) {
public int addShutdownHook(Runnable r) {
synchLock.lock();
try{
int handle = nextHandle++;
Thread hook = createThread(r);
hooks.put(handle, hook);
Runtime.getRuntime().addShutdownHook(hook);
return handle;
}
finally{
synchLock.unlock();
}
}
protected Thread createThread(Runnable r) {
return new Thread(r, "ShutdownManager");
}
public synchronized boolean removeShutdownHook(int handle) {
public boolean removeShutdownHook(int handle) {
synchLock.lock();
try{
Thread hook = hooks.remove(handle);
if(hook == null) return false;
else return Runtime.getRuntime().removeShutdownHook(hook);
}
finally{
synchLock.unlock();
}
}
}

View File

@@ -8,6 +8,8 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import org.briarproject.api.ContactId;
@@ -30,6 +32,8 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
// Locking: this
private final Map<ContactId, Integer> contactCounts;
private final Lock synchLock = new ReentrantLock();
@Inject
ConnectionRegistryImpl(EventBus eventBus) {
this.eventBus = eventBus;
@@ -40,7 +44,8 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
public void registerConnection(ContactId c, TransportId t) {
LOG.info("Connection registered");
boolean firstConnection = false;
synchronized(this) {
synchLock.lock();
try {
Map<ContactId, Integer> m = connections.get(t);
if(m == null) {
m = new HashMap<ContactId, Integer>();
@@ -57,6 +62,10 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
contactCounts.put(c, count + 1);
}
}
finally{
synchLock.unlock();
}
if(firstConnection) {
LOG.info("Contact connected");
eventBus.broadcast(new ContactConnectedEvent(c));
@@ -66,7 +75,8 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
public void unregisterConnection(ContactId c, TransportId t) {
LOG.info("Connection unregistered");
boolean lastConnection = false;
synchronized(this) {
synchLock.lock();
try {
Map<ContactId, Integer> m = connections.get(t);
if(m == null) throw new IllegalArgumentException();
Integer count = m.remove(c);
@@ -85,22 +95,40 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
contactCounts.put(c, count - 1);
}
}
finally{
synchLock.unlock();
}
if(lastConnection) {
LOG.info("Contact disconnected");
eventBus.broadcast(new ContactDisconnectedEvent(c));
}
}
public synchronized Collection<ContactId> getConnectedContacts(
public Collection<ContactId> getConnectedContacts(
TransportId t) {
synchLock.lock();
try{
Map<ContactId, Integer> m = connections.get(t);
if(m == null) return Collections.emptyList();
List<ContactId> ids = new ArrayList<ContactId>(m.keySet());
if(LOG.isLoggable(INFO)) LOG.info(ids.size() + " contacts connected");
return Collections.unmodifiableList(ids);
}
finally{
synchLock.unlock();
}
public synchronized boolean isConnected(ContactId c) {
}
public boolean isConnected(ContactId c) {
synchLock.lock();
try{
return contactCounts.containsKey(c);
}
finally{
synchLock.unlock();
}
}
}

View File

@@ -5,6 +5,10 @@ import java.util.Comparator;
import java.util.Iterator;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.briarproject.api.reliability.ReadHandler;
import org.briarproject.api.system.Clock;
@@ -23,6 +27,8 @@ class Receiver implements ReadHandler {
private long nextSequenceNumber = 1;
private volatile boolean valid = true;
private Lock synchLock = new ReentrantLock();
private Condition dataFrameAvailable = synchLock.newCondition();
Receiver(Clock clock, Sender sender) {
this.sender = sender;
@@ -30,12 +36,14 @@ class Receiver implements ReadHandler {
dataFrames = new TreeSet<Data>(new SequenceNumberComparator());
}
synchronized Data read() throws IOException, InterruptedException {
Data read() throws IOException, InterruptedException {
synchLock.lock();
try{
long now = clock.currentTimeMillis(), end = now + READ_TIMEOUT;
while(now < end && valid) {
if(dataFrames.isEmpty()) {
// Wait for a data frame
wait(end - now);
dataFrameAvailable.await(end - now, TimeUnit.MILLISECONDS);
} else {
Data d = dataFrames.first();
if(d.getSequenceNumber() == nextSequenceNumber) {
@@ -47,7 +55,7 @@ class Receiver implements ReadHandler {
return d;
} else {
// Wait for the next in-order data frame
wait(end - now);
dataFrameAvailable.await(end - now, TimeUnit.MILLISECONDS);
}
}
now = clock.currentTimeMillis();
@@ -55,11 +63,19 @@ class Receiver implements ReadHandler {
if(valid) throw new IOException("Read timed out");
throw new IOException("Connection closed");
}
finally{
synchLock.unlock();
}
}
void invalidate() {
valid = false;
synchronized(this) {
notifyAll();
synchLock.lock();
try {
dataFrameAvailable.signalAll();
}
finally{
synchLock.unlock();
}
}
@@ -79,7 +95,9 @@ class Receiver implements ReadHandler {
}
}
private synchronized void handleData(byte[] b) throws IOException {
private void handleData(byte[] b) throws IOException {
synchLock.lock();
try{
if(b.length < Data.MIN_LENGTH || b.length > Data.MAX_LENGTH) {
// Ignore data frame with invalid length
return;
@@ -106,17 +124,21 @@ class Receiver implements ReadHandler {
}
if(dataFrames.add(d)) {
windowSize -= payloadLength;
notifyAll();
dataFrameAvailable.signalAll();
}
} else if(sequenceNumber < finalSequenceNumber) {
if(dataFrames.add(d)) {
windowSize -= payloadLength;
notifyAll();
dataFrameAvailable.signalAll();
}
}
// Acknowledge the data frame even if it's a duplicate
sender.sendAck(sequenceNumber, windowSize);
}
finally{
synchLock.unlock();
}
}
private static class SequenceNumberComparator implements Comparator<Data> {

View File

@@ -5,6 +5,10 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.briarproject.api.reliability.WriteHandler;
import org.briarproject.api.system.Clock;
@@ -31,6 +35,9 @@ class Sender {
private long lastWindowUpdateOrProbe = Long.MAX_VALUE;
private boolean dataWaiting = false;
private Lock synchLock = new ReentrantLock();
private Condition sendWindowAvailable = synchLock.newCondition();
Sender(Clock clock, WriteHandler writeHandler) {
this.clock = clock;
this.writeHandler = writeHandler;
@@ -58,7 +65,8 @@ class Sender {
long sequenceNumber = a.getSequenceNumber();
long now = clock.currentTimeMillis();
Outstanding fastRetransmit = null;
synchronized(this) {
synchLock.lock();
try {
// Remove the acked data frame if it's outstanding
int foundIndex = -1;
Iterator<Outstanding> it = outstanding.iterator();
@@ -96,6 +104,9 @@ class Sender {
// If space has become available, notify any waiting writers
if(windowSize > oldWindowSize || foundIndex != -1) notifyAll();
}
finally{
synchLock.unlock();
}
// Fast retransmission
if(fastRetransmit != null)
writeHandler.handleWrite(fastRetransmit.data.getBuffer());
@@ -105,7 +116,8 @@ class Sender {
long now = clock.currentTimeMillis();
List<Outstanding> retransmit = null;
boolean sendProbe = false;
synchronized(this) {
synchLock.lock();
try {
if(outstanding.isEmpty()) {
if(dataWaiting && now - lastWindowUpdateOrProbe > rto) {
sendProbe = true;
@@ -135,6 +147,9 @@ class Sender {
}
}
}
finally{
synchLock.unlock();
}
// Send a window probe if necessary
if(sendProbe) {
byte[] buf = new byte[Data.MIN_LENGTH];
@@ -151,12 +166,13 @@ class Sender {
void write(Data d) throws IOException, InterruptedException {
int payloadLength = d.getPayloadLength();
synchronized(this) {
synchLock.lock();
try {
// Wait for space in the window
long now = clock.currentTimeMillis(), end = now + WRITE_TIMEOUT;
while(now < end && outstandingBytes + payloadLength >= windowSize) {
dataWaiting = true;
wait(end - now);
sendWindowAvailable.await(end - now, TimeUnit.MILLISECONDS);
now = clock.currentTimeMillis();
}
if(outstandingBytes + payloadLength >= windowSize)
@@ -165,11 +181,20 @@ class Sender {
outstandingBytes += payloadLength;
dataWaiting = false;
}
finally{
synchLock.unlock();
}
writeHandler.handleWrite(d.getBuffer());
}
synchronized void flush() throws IOException, InterruptedException {
while(dataWaiting || !outstanding.isEmpty()) wait();
void flush() throws IOException, InterruptedException {
synchLock.lock();
try{
while(dataWaiting || !outstanding.isEmpty()) sendWindowAvailable.await();
}
finally{
synchLock.unlock();
}
}
private static class Outstanding {

View File

@@ -11,6 +11,8 @@ import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimerTask;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import javax.inject.Inject;
@@ -56,6 +58,8 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
private final Map<EndpointKey, TemporarySecret> currentSecrets;
private final Map<EndpointKey, TemporarySecret> newSecrets;
private final Lock synchLock = new ReentrantLock();
@Inject
KeyManagerImpl(CryptoComponent crypto, DatabaseComponent db,
EventBus eventBus, TagRecogniser tagRecogniser, Clock clock,
@@ -72,7 +76,9 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
newSecrets = new HashMap<EndpointKey, TemporarySecret>();
}
public synchronized boolean start() {
public boolean start() {
synchLock.lock();
try {
eventBus.addListener(this);
// Load the temporary secrets and transport latencies from the database
Collection<TemporarySecret> secrets;
@@ -108,6 +114,10 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
timer.scheduleAtFixedRate(this, MS_BETWEEN_CHECKS, MS_BETWEEN_CHECKS);
return true;
}
finally{
synchLock.unlock();
}
}
// Assigns secrets to the appropriate maps and returns any dead secrets
// Locking: this
@@ -215,7 +225,9 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
return created;
}
public synchronized boolean stop() {
public boolean stop() {
synchLock.lock();
try{
eventBus.removeListener(this);
timer.cancel();
tagRecogniser.removeSecrets();
@@ -225,6 +237,10 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
removeAndEraseSecrets(newSecrets);
return true;
}
finally{
synchLock.unlock();
}
}
// Locking: this
private void removeAndEraseSecrets(Map<?, TemporarySecret> m) {
@@ -232,8 +248,10 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
m.clear();
}
public synchronized StreamContext getStreamContext(ContactId c,
public StreamContext getStreamContext(ContactId c,
TransportId t) {
synchLock.lock();
try{
TemporarySecret s = currentSecrets.get(new EndpointKey(c, t));
if(s == null) {
LOG.info("No secret for endpoint");
@@ -254,9 +272,15 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
byte[] secret = s.getSecret().clone();
return new StreamContext(c, t, secret, streamNumber, s.getAlice());
}
finally{
synchLock.unlock();
}
}
public synchronized void endpointAdded(Endpoint ep, long maxLatency,
public void endpointAdded(Endpoint ep, long maxLatency,
byte[] initialSecret) {
synchLock.lock();
try{
maxLatencies.put(ep.getTransportId(), maxLatency);
// Work out which rotation period we're in
long elapsed = clock.currentTimeMillis() - ep.getEpoch();
@@ -292,9 +316,15 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
tagRecogniser.addSecret(s2);
tagRecogniser.addSecret(s3);
}
finally{
synchLock.unlock();
}
}
@Override
public synchronized void run() {
public void run() {
synchLock.lock();
try{
// Rebuild the maps because we may be running a whole period late
Collection<TemporarySecret> secrets = new ArrayList<TemporarySecret>();
secrets.addAll(oldSecrets.values());
@@ -326,6 +356,10 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
for(TemporarySecret s : created) tagRecogniser.addSecret(s);
}
}
finally{
synchLock.unlock();
}
}
public void eventOccurred(Event e) {
if(e instanceof ContactRemovedEvent) {
@@ -407,11 +441,15 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
public void run() {
ContactId c = event.getContactId();
tagRecogniser.removeSecrets(c);
synchronized(KeyManagerImpl.this) {
synchLock.lock();
try {
removeAndEraseSecrets(c, oldSecrets);
removeAndEraseSecrets(c, currentSecrets);
removeAndEraseSecrets(c, newSecrets);
}
finally{
synchLock.unlock();
}
}
}
@@ -425,9 +463,13 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
@Override
public void run() {
synchronized(KeyManagerImpl.this) {
synchLock.lock();
try {
maxLatencies.put(event.getTransportId(), event.getMaxLatency());
}
finally{
synchLock.unlock();
}
}
}
@@ -443,12 +485,16 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
public void run() {
TransportId t = event.getTransportId();
tagRecogniser.removeSecrets(t);
synchronized(KeyManagerImpl.this) {
synchLock.lock();
try {
maxLatencies.remove(t);
removeAndEraseSecrets(t, oldSecrets);
removeAndEraseSecrets(t, currentSecrets);
removeAndEraseSecrets(t, newSecrets);
}
finally{
synchLock.unlock();
}
}
}
}

View File

@@ -2,6 +2,8 @@ package org.briarproject.transport;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.inject.Inject;
@@ -21,6 +23,9 @@ class TagRecogniserImpl implements TagRecogniser {
// Locking: this
private final Map<TransportId, TransportTagRecogniser> recognisers;
private final Lock synchLock = new ReentrantLock();
@Inject
TagRecogniserImpl(CryptoComponent crypto, DatabaseComponent db) {
this.crypto = crypto;
@@ -31,9 +36,13 @@ class TagRecogniserImpl implements TagRecogniser {
public StreamContext recogniseTag(TransportId t, byte[] tag)
throws DbException {
TransportTagRecogniser r;
synchronized(this) {
synchLock.lock();
try {
r = recognisers.get(t);
}
finally{
synchLock.unlock();
}
if(r == null) return null;
return r.recogniseTag(tag);
}
@@ -41,35 +50,63 @@ class TagRecogniserImpl implements TagRecogniser {
public void addSecret(TemporarySecret s) {
TransportId t = s.getTransportId();
TransportTagRecogniser r;
synchronized(this) {
synchLock.lock();
try {
r = recognisers.get(t);
if(r == null) {
r = new TransportTagRecogniser(crypto, db, t);
recognisers.put(t, r);
}
}
finally{
synchLock.unlock();
}
r.addSecret(s);
}
public void removeSecret(ContactId c, TransportId t, long period) {
TransportTagRecogniser r;
synchronized(this) {
synchLock.lock();
try {
r = recognisers.get(t);
}
finally{
synchLock.unlock();
}
if(r != null) r.removeSecret(c, period);
}
public synchronized void removeSecrets(ContactId c) {
public void removeSecrets(ContactId c) {
synchLock.lock();
try{
for(TransportTagRecogniser r : recognisers.values())
r.removeSecrets(c);
}
public synchronized void removeSecrets(TransportId t) {
recognisers.remove(t);
finally{
synchLock.unlock();
}
}
public synchronized void removeSecrets() {
public void removeSecrets(TransportId t) {
synchLock.lock();
try{
recognisers.remove(t);
}
finally{
synchLock.unlock();
}
}
public void removeSecrets() {
synchLock.lock();
try{
for(TransportTagRecogniser r : recognisers.values())
r.removeSecrets();
}
finally{
synchLock.unlock();
}
}
}

View File

@@ -6,6 +6,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.briarproject.api.Bytes;
import org.briarproject.api.ContactId;
@@ -30,6 +32,8 @@ class TransportTagRecogniser {
private final Map<Bytes, TagContext> tagMap; // Locking: this
private final Map<RemovalKey, RemovalContext> removalMap; // Locking: this
private final Lock synchLock = new ReentrantLock();
TransportTagRecogniser(CryptoComponent crypto, DatabaseComponent db,
TransportId transportId) {
this.crypto = crypto;
@@ -39,7 +43,9 @@ class TransportTagRecogniser {
removalMap = new HashMap<RemovalKey, RemovalContext>();
}
synchronized StreamContext recogniseTag(byte[] tag) throws DbException {
StreamContext recogniseTag(byte[] tag) throws DbException {
synchLock.lock();
try{
TagContext t = tagMap.remove(new Bytes(tag));
if(t == null) return null; // The tag was not expected
// Update the reordering window and the expected tags
@@ -65,8 +71,14 @@ class TransportTagRecogniser {
return new StreamContext(t.contactId, transportId, secret,
t.streamNumber, t.alice);
}
finally{
synchLock.unlock();
}
}
synchronized void addSecret(TemporarySecret s) {
void addSecret(TemporarySecret s) {
synchLock.lock();
try{
ContactId contactId = s.getContactId();
boolean alice = s.getAlice();
long period = s.getPeriod();
@@ -89,13 +101,23 @@ class TransportTagRecogniser {
RemovalContext r = new RemovalContext(window, secret, alice);
removalMap.put(new RemovalKey(contactId, period), r);
}
finally{
synchLock.unlock();
}
}
synchronized void removeSecret(ContactId contactId, long period) {
void removeSecret(ContactId contactId, long period) {
synchLock.lock();
try{
RemovalKey k = new RemovalKey(contactId, period);
RemovalContext removed = removalMap.remove(k);
if(removed == null) throw new IllegalArgumentException();
removeSecret(removed);
}
finally{
synchLock.unlock();
}
}
// Locking: this
private void removeSecret(RemovalContext r) {
@@ -110,18 +132,30 @@ class TransportTagRecogniser {
key.erase();
}
synchronized void removeSecrets(ContactId c) {
void removeSecrets(ContactId c) {
synchLock.lock();
try{
Collection<RemovalKey> keysToRemove = new ArrayList<RemovalKey>();
for(RemovalKey k : removalMap.keySet())
if(k.contactId.equals(c)) keysToRemove.add(k);
for(RemovalKey k : keysToRemove) removeSecret(k.contactId, k.period);
}
finally{
synchLock.unlock();
}
}
synchronized void removeSecrets() {
void removeSecrets() {
synchLock.lock();
try{
for(RemovalContext r : removalMap.values()) removeSecret(r);
assert tagMap.isEmpty();
removalMap.clear();
}
finally{
synchLock.unlock();
}
}
private static class TagContext {

View File

@@ -8,6 +8,8 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import org.briarproject.util.OsUtils;
@@ -39,6 +41,9 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
private boolean initialised = false; // Locking: this
private final Lock synchLock = new ReentrantLock();
WindowsShutdownManagerImpl() {
// Use the Unicode versions of Win32 API calls
Map<String, Object> m = new HashMap<String, Object>();
@@ -48,10 +53,16 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
}
@Override
public synchronized int addShutdownHook(Runnable r) {
public int addShutdownHook(Runnable r) {
synchLock.lock();
try {
if(!initialised) initialise();
return super.addShutdownHook(r);
}
finally{
synchLock.unlock();
}
}
@Override
protected Thread createThread(Runnable r) {
@@ -69,7 +80,9 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
}
// Package access for testing
synchronized void runShutdownHooks() {
void runShutdownHooks() {
synchLock.lock();
try {
boolean interrupted = false;
// Start each hook in its own thread
for(Thread hook : hooks.values()) hook.start();
@@ -84,6 +97,10 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
}
if(interrupted) Thread.currentThread().interrupt();
}
finally{
synchLock.unlock();
}
}
private class EventLoop extends Thread {

View File

@@ -4,6 +4,10 @@ import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
@@ -14,11 +18,14 @@ class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
private final Executor ioExecutor;
private final RemovableDriveFinder finder;
private final long pollingInterval;
private final Object pollingLock = new Object();
private volatile boolean running = false;
private volatile Callback callback = null;
private final Lock synchLock = new ReentrantLock();
private final Condition stopPolling = synchLock.newCondition();
public PollingRemovableDriveMonitor(Executor ioExecutor,
RemovableDriveFinder finder, long pollingInterval) {
this.ioExecutor = ioExecutor;
@@ -34,8 +41,12 @@ class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
public void stop() throws IOException {
running = false;
synchronized(pollingLock) {
pollingLock.notifyAll();
synchLock.lock();
try {
stopPolling.signalAll();
}
finally {
synchLock.unlock();
}
}
@@ -43,8 +54,12 @@ class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
try {
Collection<File> drives = finder.findRemovableDrives();
while(running) {
synchronized(pollingLock) {
pollingLock.wait(pollingInterval);
synchLock.lock();
try {
stopPolling.await(pollingInterval, TimeUnit.MILLISECONDS);
}
finally{
synchLock.unlock();
}
if(!running) return;
Collection<File> newDrives = finder.findRemovableDrives();

View File

@@ -4,6 +4,9 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import net.contentobjects.jnotify.JNotify;
import net.contentobjects.jnotify.JNotifyListener;
@@ -22,6 +25,10 @@ JNotifyListener {
protected abstract String[] getPathsToWatch();
//TODO: rationalise this in a further refactor
private final Lock synchLock = new ReentrantLock();
private static final Lock staticSynchLock = new ReentrantLock();
private static Throwable tryLoad() {
try {
Class.forName("net.contentobjects.jnotify.JNotify");
@@ -33,13 +40,19 @@ JNotifyListener {
}
}
public static synchronized void checkEnabled() throws IOException {
public static void checkEnabled() throws IOException {
staticSynchLock.lock();
try {
if(!triedLoad) {
loadError = tryLoad();
triedLoad = true;
}
if(loadError != null) throw new IOException(loadError.toString());
}
finally{
staticSynchLock.unlock();
}
}
public void start(Callback callback) throws IOException {
checkEnabled();
@@ -49,19 +62,24 @@ JNotifyListener {
if(new File(path).exists())
watches.add(JNotify.addWatch(path, mask, false, this));
}
synchronized(this) {
synchLock.lock();
try {
assert !started;
assert this.callback == null;
started = true;
this.callback = callback;
this.watches.addAll(watches);
}
finally{
synchLock.unlock();
}
}
public void stop() throws IOException {
checkEnabled();
List<Integer> watches;
synchronized(this) {
synchLock.lock();
try {
assert started;
assert callback != null;
started = false;
@@ -69,14 +87,21 @@ JNotifyListener {
watches = new ArrayList<Integer>(this.watches);
this.watches.clear();
}
finally{
synchLock.unlock();
}
for(Integer w : watches) JNotify.removeWatch(w);
}
public void fileCreated(int wd, String rootPath, String name) {
Callback callback;
synchronized(this) {
synchLock.lock();
try {
callback = this.callback;
}
finally{
synchLock.unlock();
}
if(callback != null)
callback.driveInserted(new File(rootPath + "/" + name));
}

View File

@@ -10,6 +10,10 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import jssc.SerialPortEvent;
@@ -45,6 +49,11 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
private ReliabilityLayer reliability = null; // Locking: this
private boolean initialised = false, connected = false; // Locking: this
private final Lock synchLock = new ReentrantLock();
private final Condition connectedStateChanged = synchLock.newCondition();
private final Condition initialisedStateChanged = synchLock.newCondition();
ModemImpl(Executor executor, ReliabilityLayerFactory reliabilityFactory,
Clock clock, Callback callback, SerialPort port) {
this.executor = executor;
@@ -91,15 +100,19 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
// Wait for the event thread to receive "OK"
boolean success = false;
try {
synchronized(this) {
synchLock.lock();
try {
long now = clock.currentTimeMillis();
long end = now + OK_TIMEOUT;
while(now < end && !initialised) {
wait(end - now);
initialisedStateChanged.await(end - now, TimeUnit.MILLISECONDS);
now = clock.currentTimeMillis();
}
success = initialised;
}
finally{
synchLock.unlock();
}
} catch(InterruptedException e) {
tryToClose(port);
Thread.currentThread().interrupt();
@@ -123,11 +136,16 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
public void stop() throws IOException {
LOG.info("Stopping");
synchLock.lock();
try {
// Wake any threads that are waiting to connect
synchronized(this) {
initialised = false;
connected = false;
notifyAll();
initialisedStateChanged.signalAll();
connectedStateChanged.signalAll();
}
finally{
synchLock.unlock();
}
// Hang up if necessary and close the port
try {
@@ -148,7 +166,8 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
// Locking: stateChange
private void hangUpInner() throws IOException {
ReliabilityLayer reliability;
synchronized(this) {
synchLock.lock();
try {
if(this.reliability == null) {
LOG.info("Not hanging up - already on the hook");
return;
@@ -157,6 +176,9 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
this.reliability = null;
connected = false;
}
finally{
synchLock.unlock();
}
reliability.stop();
LOG.info("Hanging up");
try {
@@ -182,7 +204,8 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
try {
ReliabilityLayer reliability =
reliabilityFactory.createReliabilityLayer(this);
synchronized(this) {
synchLock.lock();
try {
if(!initialised) {
LOG.info("Not dialling - modem not initialised");
return false;
@@ -193,6 +216,9 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
}
this.reliability = reliability;
}
finally{
synchLock.unlock();
}
reliability.start();
LOG.info("Dialling");
try {
@@ -204,15 +230,19 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
}
// Wait for the event thread to receive "CONNECT"
try {
synchronized(this) {
synchLock.lock();
try {
long now = clock.currentTimeMillis();
long end = now + CONNECT_TIMEOUT;
while(now < end && initialised && !connected) {
wait(end - now);
connectedStateChanged.await(end - now, TimeUnit.MILLISECONDS);
now = clock.currentTimeMillis();
}
if(connected) return true;
}
finally{
synchLock.unlock();
}
} catch(InterruptedException e) {
tryToClose(port);
Thread.currentThread().interrupt();
@@ -227,18 +257,26 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
public InputStream getInputStream() throws IOException {
ReliabilityLayer reliability;
synchronized(this) {
synchLock.lock();
try {
reliability = this.reliability;
}
finally{
synchLock.unlock();
}
if(reliability == null) throw new IOException("Not connected");
return reliability.getInputStream();
}
public OutputStream getOutputStream() throws IOException {
ReliabilityLayer reliability;
synchronized(this) {
synchLock.lock();
try {
reliability = this.reliability;
}
finally{
synchLock.unlock();
}
if(reliability == null) throw new IOException("Not connected");
return reliability.getOutputStream();
}
@@ -288,9 +326,13 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
private boolean handleData(byte[] b) throws IOException {
ReliabilityLayer reliability;
synchronized(this) {
synchLock.lock();
try {
reliability = this.reliability;
}
finally{
synchLock.unlock();
}
if(reliability == null) return false;
reliability.handleRead(b);
return true;
@@ -309,9 +351,13 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
lineLen = 0;
if(LOG.isLoggable(INFO)) LOG.info("Modem status: " + s);
if(s.startsWith("CONNECT")) {
synchronized(this) {
synchLock.lock();
try {
connected = true;
notifyAll();
connectedStateChanged.signalAll();
}
finally{
synchLock.unlock();
}
// There might be data in the buffer as well as text
int off = i + 1;
@@ -323,14 +369,22 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
return;
} else if(s.equals("BUSY") || s.equals("NO DIALTONE")
|| s.equals("NO CARRIER")) {
synchronized(this) {
synchLock.lock();
try {
connected = false;
notifyAll();
connectedStateChanged.signalAll();
}
finally{
synchLock.unlock();
}
} else if(s.equals("OK")) {
synchronized(this) {
synchLock.lock();
try {
initialised = true;
notifyAll();
initialisedStateChanged.signalAll();
}
finally{
synchLock.unlock();
}
} else if(s.equals("RING")) {
executor.execute(new Runnable() {
@@ -358,7 +412,8 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
try {
ReliabilityLayer reliability =
reliabilityFactory.createReliabilityLayer(this);
synchronized(this) {
synchLock.lock();
try {
if(!initialised) {
LOG.info("Not answering - modem not initialised");
return;
@@ -369,6 +424,9 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
}
this.reliability = reliability;
}
finally{
synchLock.unlock();
}
reliability.start();
LOG.info("Answering");
try {
@@ -380,15 +438,19 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
// Wait for the event thread to receive "CONNECT"
boolean success = false;
try {
synchronized(this) {
synchLock.lock();
try {
long now = clock.currentTimeMillis();
long end = now + CONNECT_TIMEOUT;
while(now < end && initialised && !connected) {
wait(end - now);
connectedStateChanged.await(end - now, TimeUnit.MILLISECONDS);
now = clock.currentTimeMillis();
}
success = connected;
}
finally{
synchLock.unlock();
}
} catch(InterruptedException e) {
tryToClose(port);
Thread.currentThread().interrupt();