3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-13 02:35:52 +00:00

Confidence objects now pin themselves when a listener is attached, eliminating a certain class of GC-triggered heisenbug.

This commit is contained in:
Mike Hearn 2015-03-01 20:18:23 +01:00
parent 5a824f8411
commit 750f469bd3
3 changed files with 37 additions and 3 deletions

View File

@ -174,6 +174,15 @@ public class TransactionConfidence implements Serializable {
public void onConfidenceChanged(TransactionConfidence confidence, ChangeReason reason);
}
// This is used to ensure that confidence objects which aren't referenced from anywhere but which have an event
// listener set on them don't become eligible for garbage collection. Otherwise the TxConfidenceTable, which only
// has weak references to these objects, would not be enough to keep the event listeners working as transactions
// propagate around the network - it cannot know directly if the API user is interested in the object, so it uses
// heap reachability as a proxy for interest.
//
// We add ourselves to this set when a listener is added and remove ourselves when the listener list is empty.
private static final Set<TransactionConfidence> pinnedConfidenceObjects = Collections.synchronizedSet(new HashSet<TransactionConfidence>());
/**
* <p>Adds an event listener that will be run when this confidence object is updated. The listener will be locked and
* is likely to be invoked on a peer thread.</p>
@ -186,6 +195,7 @@ public class TransactionConfidence implements Serializable {
public void addEventListener(Listener listener, Executor executor) {
checkNotNull(listener);
listeners.addIfAbsent(new ListenerRegistration<Listener>(listener, executor));
pinnedConfidenceObjects.add(this);
}
/**
@ -204,7 +214,10 @@ public class TransactionConfidence implements Serializable {
public boolean removeEventListener(Listener listener) {
checkNotNull(listener);
return ListenerRegistration.removeFromList(listener, listeners);
boolean removed = ListenerRegistration.removeFromList(listener, listeners);
if (listeners.isEmpty())
pinnedConfidenceObjects.remove(this);
return removed;
}
/**

View File

@ -33,6 +33,7 @@ public class ListenerRegistration<T> {
this.executor = checkNotNull(executor);
}
/** Returns true if the listener was removed, else false. */
public static <T> boolean removeFromList(T listener, List<? extends ListenerRegistration<T>> list) {
checkNotNull(listener);

View File

@ -35,7 +35,8 @@ public class TxConfidenceTableTest {
@Before
public void setup() throws Exception {
BriefLogFormatter.init();
table = Context.getOrCreate().getConfidenceTable();
Context context = new Context();
table = context.getConfidenceTable();
Address to = new ECKey().toAddress(params);
Address change = new ECKey().toAddress(params);
@ -48,7 +49,26 @@ public class TxConfidenceTableTest {
address2 = new PeerAddress(InetAddress.getByAddress(new byte[] { 127, 0, 0, 2 }));
address3 = new PeerAddress(InetAddress.getByAddress(new byte[] { 127, 0, 0, 3 }));
}
@Test
public void pinHandlers() throws Exception {
Transaction tx = new Transaction(params, tx1.bitcoinSerialize());
Sha256Hash hash = tx.getHash();
table.seen(hash, address1);
assertEquals(1, tx.getConfidence().numBroadcastPeers());
final int[] seen = new int[1];
tx.getConfidence().addEventListener(new TransactionConfidence.Listener() {
@Override
public void onConfidenceChanged(TransactionConfidence confidence, ChangeReason reason) {
seen[0] = confidence.numBroadcastPeers();
}
}, Threading.SAME_THREAD);
tx = null;
System.gc();
table.seen(hash, address2);
assertEquals(2, seen[0]);
}
@Test
public void invAndDownload() throws Exception {
// Base case: we see a transaction announced twice and then download it. The count is in the confidence object.