Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(2/6) Remove onBatch interfaces in P2PDataStore #3621

Merged
merged 8 commits into from
Nov 26, 2019

Conversation

julianknutsen
Copy link
Contributor

@julianknutsen julianknutsen commented Nov 17, 2019

Patches start 78211a6

Before:
update-lists-perf

After:
update-lists-perf-after

A bug was introduced in #3148 where ProposalListPresentation would incorrectly call updateLists() when the proposal state didn't change. This pull request should help #3143 (the target of #3148) in a more maintainable way that removes the bug and UI performance issues by calling updateLists() less often. There is still an outstanding bug that updateLists() can take seconds to run on each invocation.

Initial Bug #3143
If the P2PDataStore removes multiple items during the expiration work, each item is signaled separately. The ProposalService is a listener for these removes and would eventually call updateLists() for each remove.

Exacerbating Issue
Old TempProposals were not correctly deleted from disk. They would be recreated at startup and the expire code would always remove them. This was fixed with #3150.

Follow Up Bug #3148
The fix for the initial bug was to create a new listener interface on P2PDataStore that would signal when the expire code starts and ends. The ProposalListPresentation would subscribe to this interface and remove its listener from the ProposalService so that all of the extra onRemoved() calls would not trigger updateLists().

The code did remove the proposal listener when the expire code started, but it had a bug where it would ALWAYS call updateLists() when the expire code finished if ANY TempProposal object was received (not necessarily a new one).

In the pre-fix code, the ProposalListPresentation code only called updateList() when the ProposalService proposal list changed. The ProposalService had validation to ensure that it only signaled listeners if the Proposal was new. By going around the ProposalService and directly listening to the P2PDataStore, the new code was running every time an existing Proposal object was seen.

Duplicate Proposal objects (same payload different sequence number) can be added during startup, on restart(), or republished on startup by owners MyProposalListService::rePublishMyProposalsOnceWellConnected(). The effect is that the next call to the expiration code would cause updateLists() to run when it didn't need to.

Fix
The root of the original performance issue is straightforward. Don't update listeners of the P2PDataStore or the ProposalService on each remove when batching them is possible.

This pull request implements batching by:

  • Modifying the onAdded() and onRemoved() listener functions to take a Collection of ProtectedStorageEntry objects instead of just one.
  • Combining all remove() operations done in P2PDataStore::removeExpiredEntries() to a single onRemoved() call that includes all of the removed ProtectedStorageEntry objects.
  • Using this new behavior in ProposalService to only update its listeners once for each onRemoved() call.
  • Reverting the onBatch listener changes

The end result is that any number of removes from P2PDataStore::removeExpiredEntries() will result in 1 call to ProposalListPresentation::updateLists() which was the original intention of the fix.

It also cleans up one of the existing code smells for the P2PDataStorage layer that requires listeners to understand if and when removeExpiredEntries() is called and if they should do something special.

@julianknutsen julianknutsen force-pushed the batch-listener branch 2 times, most recently from b008b26 to d1ab3c0 Compare November 18, 2019 20:17
@julianknutsen julianknutsen marked this pull request as ready for review November 18, 2019 20:18
@julianknutsen julianknutsen changed the title [WIP] Remove onBatch interfaces in P2PDataStore Remove onBatch interfaces in P2PDataStore Nov 18, 2019
freimair
freimair previously approved these changes Nov 19, 2019
Copy link
Member

@freimair freimair left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utAck

@ripcurlx
Copy link
Contributor

utAck

Commit 833611f is also part of this PR #3628 (review)

Copy link
Contributor

@ripcurlx ripcurlx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides #3621 (review) everything looks good.

Instead of using a subclass that overwrites a value, utilize Guice
to inject the real value of 10000 in the app and let the tests overwrite
it with their own.
Remove unused imports and clean up some access modifiers now that
the final test structure is complete
Previously, this interface was called each time an item was changed. This
required listeners to understand performance implications of multiple
adds or removes in a short time span.

Instead, give each listener the ability to process a list of added or
removed entrys which can help them avoid performance issues.

This patch is just a refactor. Each listener is called once for each
ProtectedStorageEntry. Future patches will change this.
Minor performance overhead for constructing MapEntry and Collections
of one element, but keeps the code cleaner and all removes can still
use the same logic to remove from map, delete from data store, signal
listeners, etc.

The MapEntry type is used instead of Pair since it will require less
operations when this is eventually used in the removeExpiredEntries path.
…batch

All current users still call this one-at-a-time. But, it gives the ability
for the expire code path to remove in a batch.
This will cause HashMapChangedListeners to receive just one onRemoved()
call for the expire work instead of multiple onRemoved() calls for each
item.

This required a bit of updating for the remove validation in tests so
that it correctly compares onRemoved with multiple items.
…ch removes

bisq-network#3143 identified an issue that tempProposals listeners were being
signaled once for each item that was removed during the P2PDataStore
operation that expired old TempProposal objects. Some of the listeners
are very expensive (ProposalListPresentation::updateLists()) which results
in large UI performance issues.

Now that the infrastructure is in place to receive updates from the
P2PDataStore in a batch, the ProposalService can apply all of the removes
received from the P2PDataStore at once. This results in only 1 onChanged()
callback for each listener.

The end result is that updateLists() is only called once and the performance
problems are reduced.

This removes the need for bisq-network#3148 and those interfaces will be removed in
the next patch.
Now that the only user of this interface has been removed, go ahead
and delete it. This is a partial revert of
f5d75c4 that includes the code that was
added into ProposalService that subscribed to the P2PDataStore.
@julianknutsen julianknutsen changed the title Remove onBatch interfaces in P2PDataStore (2/5) Remove onBatch interfaces in P2PDataStore Nov 20, 2019
Copy link
Contributor

@ripcurlx ripcurlx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK

@julianknutsen julianknutsen changed the title (2/5) Remove onBatch interfaces in P2PDataStore (2/6) Remove onBatch interfaces in P2PDataStore Nov 22, 2019
@ripcurlx ripcurlx merged commit a8139f3 into bisq-network:master Nov 26, 2019
@julianknutsen julianknutsen deleted the batch-listener branch November 26, 2019 16:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants