mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-13 10:45:51 +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:
parent
5a824f8411
commit
750f469bd3
@ -174,6 +174,15 @@ public class TransactionConfidence implements Serializable {
|
|||||||
public void onConfidenceChanged(TransactionConfidence confidence, ChangeReason reason);
|
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
|
* <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>
|
* 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) {
|
public void addEventListener(Listener listener, Executor executor) {
|
||||||
checkNotNull(listener);
|
checkNotNull(listener);
|
||||||
listeners.addIfAbsent(new ListenerRegistration<Listener>(listener, executor));
|
listeners.addIfAbsent(new ListenerRegistration<Listener>(listener, executor));
|
||||||
|
pinnedConfidenceObjects.add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -204,7 +214,10 @@ public class TransactionConfidence implements Serializable {
|
|||||||
|
|
||||||
public boolean removeEventListener(Listener listener) {
|
public boolean removeEventListener(Listener listener) {
|
||||||
checkNotNull(listener);
|
checkNotNull(listener);
|
||||||
return ListenerRegistration.removeFromList(listener, listeners);
|
boolean removed = ListenerRegistration.removeFromList(listener, listeners);
|
||||||
|
if (listeners.isEmpty())
|
||||||
|
pinnedConfidenceObjects.remove(this);
|
||||||
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,6 +33,7 @@ public class ListenerRegistration<T> {
|
|||||||
this.executor = checkNotNull(executor);
|
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) {
|
public static <T> boolean removeFromList(T listener, List<? extends ListenerRegistration<T>> list) {
|
||||||
checkNotNull(listener);
|
checkNotNull(listener);
|
||||||
|
|
||||||
|
@ -35,7 +35,8 @@ public class TxConfidenceTableTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setup() throws Exception {
|
public void setup() throws Exception {
|
||||||
BriefLogFormatter.init();
|
BriefLogFormatter.init();
|
||||||
table = Context.getOrCreate().getConfidenceTable();
|
Context context = new Context();
|
||||||
|
table = context.getConfidenceTable();
|
||||||
|
|
||||||
Address to = new ECKey().toAddress(params);
|
Address to = new ECKey().toAddress(params);
|
||||||
Address change = 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 }));
|
address2 = new PeerAddress(InetAddress.getByAddress(new byte[] { 127, 0, 0, 2 }));
|
||||||
address3 = new PeerAddress(InetAddress.getByAddress(new byte[] { 127, 0, 0, 3 }));
|
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
|
@Test
|
||||||
public void invAndDownload() throws Exception {
|
public void invAndDownload() throws Exception {
|
||||||
// Base case: we see a transaction announced twice and then download it. The count is in the confidence object.
|
// Base case: we see a transaction announced twice and then download it. The count is in the confidence object.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user