Skip to content

Conversation

@aaron-steinfeld
Copy link
Contributor

Description

previously, a pending update could be retrieved and modified after it
has started updating, causing it to npe and leak memory

previously, a pending update could be retrieved and modified after it
has started updating, causing it to npe and leak memory
@codecov
Copy link

codecov bot commented Jul 20, 2021

Codecov Report

Merging #112 (8231626) into main (88a7e4c) will increase coverage by 0.04%.
The diff coverage is 85.71%.

Impacted file tree graph

@@             Coverage Diff              @@
##               main     #112      +/-   ##
============================================
+ Coverage     59.74%   59.78%   +0.04%     
+ Complexity      296      295       -1     
============================================
  Files            39       39              
  Lines          2971     2979       +8     
  Branches        368      368              
============================================
+ Hits           1775     1781       +6     
- Misses         1025     1026       +1     
- Partials        171      172       +1     
Flag Coverage Δ
integration 59.78% <85.71%> (+0.04%) ⬆️
unit 39.94% <85.71%> (+0.09%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...data/service/rxclient/EntityDataCachingClient.java 96.82% <85.71%> (-3.18%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 88a7e4c...8231626. Read the comment docs.

@github-actions

This comment has been minimized.

avinashkolluru
avinashkolluru previously approved these changes Jul 20, 2021
@avinashkolluru avinashkolluru self-requested a review July 20, 2021 19:52
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@aaron-steinfeld aaron-steinfeld merged commit 2cbcbc0 into main Jul 20, 2021
@aaron-steinfeld aaron-steinfeld deleted the fix-caching-client-thread-safe branch July 20, 2021 20:10
@github-actions
Copy link

Unit Test Results

  30 files  ±0    30 suites  ±0   21s ⏱️ +2s
141 tests ±0  141 ✔️ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 2cbcbc0. ± Comparison against base commit 88a7e4c.

lock.lock();
EntityDataCachingClient.this.pendingEntityUpdates.remove(entityKey);
} finally {
lock.unlock();
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it ok to unlock immediately after removal or should we do the rest of the processing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should be - I initially had it on the whole method, but since the write lock is exclusive it seems like it'd be better to scope it. As soon as the pending update is removed from the map, future reads should be good to go as they'll trigger the creation of a new update.

Copy link
Contributor

Choose a reason for hiding this comment

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

ok.

}
Single<Entity> updateResult =
EntityDataCachingClient.this.createOrUpdateEntity(entityKey, condition).cache();
EntityDataCachingClient.this.cache.put(entityKey, updateResult);
Copy link

@satish-mittal satish-mittal Jul 21, 2021

Choose a reason for hiding this comment

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

The above method at L133 EntityDataCachingClient.this.createOrUpdateEntity(entityKey, condition).cache(); internally already adds the updated result into the cache:

  private Single<Entity> createOrUpdateEntity(
      EntityKey entityKey, UpsertCondition upsertCondition) {
    Single<Entity> updateResult =
        this.upsertEntityWithoutCaching(entityKey, upsertCondition).cache();
    EntityDataCachingClient.this.cache.put(entityKey, updateResult);
    return updateResult;
  }

Hence the operation in L134 seems redundant.

lock.lock();
this.pendingEntityUpdates
.computeIfAbsent(entityKey, unused -> new PendingEntityUpdate())
.addNewUpdate(entityKey, singleSubject, condition, maximumUpsertDelay);

Choose a reason for hiding this comment

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

Trying to understand the code. Given the read lock, multiple threads will be allowed to go ahead and perform 2 operations concurrently:

  1. Add a new entry within pendingEntityUpdates ConcurrentMap (if absent).
  2. Mutate the pendingEntityUpdate instance fetched from the map as part of addNewUpdate().

While step 1) is thread-safe given concurrentHashMap, step 2) is not thread-safe. Multiple threads may end up mutating List<SingleObserver<Entity>> responseObservers (since LinkedList is not thread-safe), along with overwriting other references like
private Disposable updateExecutionTimer; private Instant currentDeadline; private UpsertCondition condition;

We need to synchronize the mutation of pendingEntityUpdate instance, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that's certainly fair. the issues we were seeing were due to interactions between 1 + 2, but as you point out multiple addNewUpdate calls are not safe currently. Will follow up with another change in there, hopefully to support concurrency without locking.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Follow up in #114

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.

5 participants